root/chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Round
  2. GetInitialWidgetBounds
  3. CompareGdkRectangles
  4. GdkRectMatchesTabFaviconBounds
  5. type_
  6. TabAnimation
  7. type
  8. Start
  9. Stop
  10. set_layout_on_completion
  11. GetCurrentTabWidth
  12. AnimationProgressed
  13. AnimationEnded
  14. AnimationCanceled
  15. GetGapWidth
  16. GetDuration
  17. GetWidthForTab
  18. GenerateStartAndEndWidths
  19. index_
  20. GetWidthForTab
  21. index_
  22. index
  23. GetWidthForTab
  24. AnimationEnded
  25. start_tab_b_bounds_
  26. AnimationProgressed
  27. GetDuration
  28. AnimationEnded
  29. GetDuration
  30. GetWidthForTab
  31. InitStartState
  32. index_
  33. GetDuration
  34. GetWidthForTab
  35. to_index_
  36. AnimationProgressed
  37. AnimationEnded
  38. GetGapWidth
  39. GetDuration
  40. GetWidthForTab
  41. layout_factory_
  42. Init
  43. Show
  44. Hide
  45. IsActiveDropTarget
  46. Layout
  47. SchedulePaint
  48. SetBounds
  49. UpdateLoadingAnimations
  50. IsCompatibleWith
  51. IsAnimating
  52. DestroyDragController
  53. DestroyDraggedTab
  54. GetIdealBounds
  55. SetVerticalOffset
  56. GetTabStripOriginForWidget
  57. GetWidgetForViewID
  58. TabInsertedAt
  59. TabDetachedAt
  60. ActiveTabChanged
  61. TabSelectionChanged
  62. TabMoved
  63. TabChangedAt
  64. TabReplacedAt
  65. TabMiniStateChanged
  66. TabBlockedStateChanged
  67. IsTabActive
  68. IsTabSelected
  69. IsTabDetached
  70. GetCurrentTabWidths
  71. IsTabPinned
  72. ActivateTab
  73. ToggleTabSelection
  74. ExtendTabSelection
  75. CloseTab
  76. IsCommandEnabledForTab
  77. ExecuteCommandForTab
  78. StartHighlightTabsForCommand
  79. StopHighlightTabsForCommand
  80. StopAllHighlighting
  81. MaybeStartDrag
  82. ContinueDrag
  83. EndDrag
  84. HasAvailableDragActions
  85. GetThemeProvider
  86. GetTabStripMenuControllerForTab
  87. WillProcessEvent
  88. DidProcessEvent
  89. Observe
  90. GetTabCount
  91. GetMiniTabCount
  92. GetAvailableWidthForTabs
  93. GetIndexOfTab
  94. GetTabAt
  95. GetTabAtAdjustForAnimation
  96. RemoveTabAt
  97. HandleGlobalMouseMoveEvent
  98. GenerateIdealBounds
  99. LayoutNewTabButton
  100. GetDesiredTabWidths
  101. GetTabHOffset
  102. tab_start_x
  103. ResizeLayoutTabs
  104. IsCursorInTabStripZone
  105. ReStack
  106. AddMessageLoopObserver
  107. RemoveMessageLoopObserver
  108. GetDropBounds
  109. UpdateDropIndex
  110. SetDropIndex
  111. CompleteDrop
  112. GetDropArrowImage
  113. point_down
  114. OnExposeEvent
  115. SetContainerColorMap
  116. SetContainerTransparency
  117. SetContainerShapeMask
  118. CreateContainer
  119. DestroyContainer
  120. StopAnimation
  121. AnimationLayout
  122. StartInsertTabAnimation
  123. StartRemoveTabAnimation
  124. StartMoveTabAnimation
  125. StartResizeLayoutAnimation
  126. StartMiniTabAnimation
  127. StartMiniMoveTabAnimation
  128. FinishAnimation
  129. OnMap
  130. OnExpose
  131. OnSizeAllocate
  132. OnDragMotion
  133. OnDragDrop
  134. OnDragLeave
  135. OnDragDataReceived
  136. OnNewTabClicked
  137. SetTabBounds
  138. CanPaintOnlyFavicons
  139. PaintOnlyFavicons
  140. MakeNewTabButton
  141. SetNewTabButtonBackground

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"

#include <gtk/gtk.h>

#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/i18n/rtl.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_classifier.h"
#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
#include "chrome/browser/autocomplete/autocomplete_input.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h"
#include "chrome/browser/ui/gtk/tabs/tab_strip_menu_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/dragdrop/gtk_dnd_util.h"
#include "ui/base/gtk/gtk_screen_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/point.h"

using base::UserMetricsAction;
using content::WebContents;

namespace {

const int kDefaultAnimationDurationMs = 100;
const int kResizeLayoutAnimationDurationMs = 166;
const int kReorderAnimationDurationMs = 166;
const int kMiniTabAnimationDurationMs = 150;

const int kNewTabButtonHOffset = -5;
const int kNewTabButtonVOffset = 5;

// The delay between when the mouse leaves the tabstrip and the resize animation
// is started.
const int kResizeTabsTimeMs = 300;

// A very short time just to make sure we don't clump up our Layout() calls
// during slow window resizes.
const int kLayoutAfterSizeAllocateMs = 10;

// The range outside of the tabstrip where the pointer must enter/leave to
// start/stop the resize animation.
const int kTabStripAnimationVSlop = 40;

// The horizontal offset from one tab to the next, which results in overlapping
// tabs.
const int kTabHOffset = -16;

// Inverse ratio of the width of a tab edge to the width of the tab. When
// hovering over the left or right edge of a tab, the drop indicator will
// point between tabs.
const int kTabEdgeRatioInverse = 4;

// Size of the drop indicator.
static int drop_indicator_width;
static int drop_indicator_height;

inline int Round(double x) {
  return static_cast<int>(x + 0.5);
}

// widget->allocation is not guaranteed to be set.  After window creation,
// we pick up the normal bounds by connecting to the configure-event signal.
gfx::Rect GetInitialWidgetBounds(GtkWidget* widget) {
  GtkRequisition request;
  gtk_widget_size_request(widget, &request);
  return gfx::Rect(0, 0, request.width, request.height);
}

// Sort rectangles based on their x position.  We don't care about y position
// so we don't bother breaking ties.
int CompareGdkRectangles(const void* p1, const void* p2) {
  int p1_x = static_cast<const GdkRectangle*>(p1)->x;
  int p2_x = static_cast<const GdkRectangle*>(p2)->x;
  if (p1_x < p2_x)
    return -1;
  else if (p1_x == p2_x)
    return 0;
  return 1;
}

bool GdkRectMatchesTabFaviconBounds(const GdkRectangle& gdk_rect, TabGtk* tab) {
  gfx::Rect favicon_bounds = tab->favicon_bounds();
  return gdk_rect.x == favicon_bounds.x() + tab->x() &&
      gdk_rect.y == favicon_bounds.y() + tab->y() &&
      gdk_rect.width == favicon_bounds.width() &&
      gdk_rect.height == favicon_bounds.height();
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
//
// TabAnimation
//
//  A base class for all TabStrip animations.
//
class TabStripGtk::TabAnimation : public gfx::AnimationDelegate {
 public:
  friend class TabStripGtk;

  // Possible types of animation.
  enum Type {
    INSERT,
    REMOVE,
    MOVE,
    RESIZE,
    MINI,
    MINI_MOVE
  };

  TabAnimation(TabStripGtk* tabstrip, Type type)
      : tabstrip_(tabstrip),
        animation_(this),
        start_selected_width_(0),
        start_unselected_width_(0),
        end_selected_width_(0),
        end_unselected_width_(0),
        layout_on_completion_(false),
        type_(type) {
  }
  virtual ~TabAnimation() {}

  Type type() const { return type_; }

  void Start() {
    animation_.SetSlideDuration(GetDuration());
    animation_.SetTweenType(gfx::Tween::EASE_OUT);
    if (!animation_.IsShowing()) {
      animation_.Reset();
      animation_.Show();
    }
  }

  void Stop() {
    animation_.Stop();
  }

  void set_layout_on_completion(bool layout_on_completion) {
    layout_on_completion_ = layout_on_completion;
  }

  // Retrieves the width for the Tab at the specified index if an animation is
  // active.
  static double GetCurrentTabWidth(TabStripGtk* tabstrip,
                                   TabStripGtk::TabAnimation* animation,
                                   int index) {
    TabGtk* tab = tabstrip->GetTabAt(index);
    double tab_width;
    if (tab->mini()) {
      tab_width = TabGtk::GetMiniWidth();
    } else {
      double unselected, selected;
      tabstrip->GetCurrentTabWidths(&unselected, &selected);
      tab_width = tab->IsActive() ? selected : unselected;
    }

    if (animation) {
      double specified_tab_width = animation->GetWidthForTab(index);
      if (specified_tab_width != -1)
        tab_width = specified_tab_width;
    }

    return tab_width;
  }

  // Overridden from gfx::AnimationDelegate:
  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
    tabstrip_->AnimationLayout(end_unselected_width_);
  }

  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
    tabstrip_->FinishAnimation(this, layout_on_completion_);
    // This object is destroyed now, so we can't do anything else after this.
  }

  virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
    AnimationEnded(animation);
  }

  // Returns the gap before the tab at the specified index. Subclass if during
  // an animation you need to insert a gap before a tab.
  virtual double GetGapWidth(int index) {
    return 0;
  }

 protected:
  // Returns the duration of the animation.
  virtual int GetDuration() const {
    return kDefaultAnimationDurationMs;
  }

  // Subclasses override to return the width of the Tab at the specified index
  // at the current animation frame. -1 indicates the default width should be
  // used for the Tab.
  virtual double GetWidthForTab(int index) const {
    return -1;  // Use default.
  }

  // Figure out the desired start and end widths for the specified pre- and
  // post- animation tab counts.
  void GenerateStartAndEndWidths(int start_tab_count, int end_tab_count,
                                 int start_mini_count,
                                 int end_mini_count) {
    tabstrip_->GetDesiredTabWidths(start_tab_count, start_mini_count,
                                   &start_unselected_width_,
                                   &start_selected_width_);
    double standard_tab_width =
        static_cast<double>(TabRendererGtk::GetStandardSize().width());

    if ((end_tab_count - start_tab_count) > 0 &&
        start_unselected_width_ < standard_tab_width) {
      double minimum_tab_width = static_cast<double>(
          TabRendererGtk::GetMinimumUnselectedSize().width());
      start_unselected_width_ -= minimum_tab_width / start_tab_count;
    }

    tabstrip_->GenerateIdealBounds();
    tabstrip_->GetDesiredTabWidths(end_tab_count, end_mini_count,
                                   &end_unselected_width_,
                                   &end_selected_width_);
  }

  TabStripGtk* tabstrip_;
  gfx::SlideAnimation animation_;

  double start_selected_width_;
  double start_unselected_width_;
  double end_selected_width_;
  double end_unselected_width_;

 private:
  // True if a complete re-layout is required upon completion of the animation.
  // Subclasses set this if they don't perform a complete layout
  // themselves and canceling the animation may leave the strip in an
  // inconsistent state.
  bool layout_on_completion_;

  const Type type_;

  DISALLOW_COPY_AND_ASSIGN(TabAnimation);
};

////////////////////////////////////////////////////////////////////////////////

// Handles insertion of a Tab at |index|.
class InsertTabAnimation : public TabStripGtk::TabAnimation {
 public:
  InsertTabAnimation(TabStripGtk* tabstrip, int index)
      : TabAnimation(tabstrip, INSERT),
        index_(index) {
    int tab_count = tabstrip->GetTabCount();
    int end_mini_count = tabstrip->GetMiniTabCount();
    int start_mini_count = end_mini_count;
    if (index < end_mini_count)
      start_mini_count--;
    GenerateStartAndEndWidths(tab_count - 1, tab_count, start_mini_count,
                              end_mini_count);
  }
  virtual ~InsertTabAnimation() {}

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual double GetWidthForTab(int index) const OVERRIDE {
    if (index == index_) {
      bool is_selected = tabstrip_->model()->active_index() == index;
      double start_width, target_width;
      if (index < tabstrip_->GetMiniTabCount()) {
        start_width = TabGtk::GetMinimumSelectedSize().width();
        target_width = TabGtk::GetMiniWidth();
      } else {
        target_width =
            is_selected ? end_unselected_width_ : end_selected_width_;
        start_width =
            is_selected ? TabGtk::GetMinimumSelectedSize().width() :
                          TabGtk::GetMinimumUnselectedSize().width();
      }

      double delta = target_width - start_width;
      if (delta > 0)
        return start_width + (delta * animation_.GetCurrentValue());

      return start_width;
    }

    if (tabstrip_->GetTabAt(index)->mini())
      return TabGtk::GetMiniWidth();

    if (tabstrip_->GetTabAt(index)->IsActive()) {
      double delta = end_selected_width_ - start_selected_width_;
      return start_selected_width_ + (delta * animation_.GetCurrentValue());
    }

    double delta = end_unselected_width_ - start_unselected_width_;
    return start_unselected_width_ + (delta * animation_.GetCurrentValue());
  }

 private:
  int index_;

  DISALLOW_COPY_AND_ASSIGN(InsertTabAnimation);
};

////////////////////////////////////////////////////////////////////////////////

// Handles removal of a Tab from |index|
class RemoveTabAnimation : public TabStripGtk::TabAnimation {
 public:
  RemoveTabAnimation(TabStripGtk* tabstrip, int index, WebContents* contents)
      : TabAnimation(tabstrip, REMOVE),
        index_(index) {
    int tab_count = tabstrip->GetTabCount();
    int start_mini_count = tabstrip->GetMiniTabCount();
    int end_mini_count = start_mini_count;
    if (index < start_mini_count)
      end_mini_count--;
    GenerateStartAndEndWidths(tab_count, tab_count - 1, start_mini_count,
                              end_mini_count);
    // If the last non-mini-tab is being removed we force a layout on
    // completion. This is necessary as the value returned by GetTabHOffset
    // changes once the tab is actually removed (which happens at the end of
    // the animation), and unless we layout GetTabHOffset won't be called after
    // the removal.
    // We do the same when the last mini-tab is being removed for the same
    // reason.
    set_layout_on_completion(start_mini_count > 0 &&
                             (end_mini_count == 0 ||
                              (start_mini_count == end_mini_count &&
                               tab_count == start_mini_count + 1)));
  }

  virtual ~RemoveTabAnimation() {}

  // Returns the index of the tab being removed.
  int index() const { return index_; }

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual double GetWidthForTab(int index) const OVERRIDE {
    TabGtk* tab = tabstrip_->GetTabAt(index);

    if (index == index_) {
      // The tab(s) being removed are gradually shrunken depending on the state
      // of the animation.
      if (tab->mini()) {
        return animation_.CurrentValueBetween(TabGtk::GetMiniWidth(),
                                              -kTabHOffset);
      }

      // Removed animated Tabs are never selected.
      double start_width = start_unselected_width_;
      // Make sure target_width is at least abs(kTabHOffset), otherwise if
      // less than kTabHOffset during layout tabs get negatively offset.
      double target_width =
          std::max(abs(kTabHOffset),
                   TabGtk::GetMinimumUnselectedSize().width() + kTabHOffset);
      return animation_.CurrentValueBetween(start_width, target_width);
    }

    if (tab->mini())
      return TabGtk::GetMiniWidth();

    if (tabstrip_->available_width_for_tabs_ != -1 &&
        index_ != tabstrip_->GetTabCount() - 1) {
      return TabStripGtk::TabAnimation::GetWidthForTab(index);
    }

    // All other tabs are sized according to the start/end widths specified at
    // the start of the animation.
    if (tab->IsActive()) {
      double delta = end_selected_width_ - start_selected_width_;
      return start_selected_width_ + (delta * animation_.GetCurrentValue());
    }

    double delta = end_unselected_width_ - start_unselected_width_;
    return start_unselected_width_ + (delta * animation_.GetCurrentValue());
  }

  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
    tabstrip_->RemoveTabAt(index_);
    TabStripGtk::TabAnimation::AnimationEnded(animation);
  }

 private:
  int index_;

  DISALLOW_COPY_AND_ASSIGN(RemoveTabAnimation);
};

////////////////////////////////////////////////////////////////////////////////

// Handles the movement of a Tab from one position to another.
class MoveTabAnimation : public TabStripGtk::TabAnimation {
 public:
  MoveTabAnimation(TabStripGtk* tabstrip, int tab_a_index, int tab_b_index)
      : TabAnimation(tabstrip, MOVE),
        start_tab_a_bounds_(tabstrip_->GetIdealBounds(tab_b_index)),
        start_tab_b_bounds_(tabstrip_->GetIdealBounds(tab_a_index)) {
    tab_a_ = tabstrip_->GetTabAt(tab_a_index);
    tab_b_ = tabstrip_->GetTabAt(tab_b_index);

    // Since we don't do a full TabStrip re-layout, we need to force a full
    // layout upon completion since we're not guaranteed to be in a good state
    // if for example the animation is canceled.
    set_layout_on_completion(true);
  }
  virtual ~MoveTabAnimation() {}

  // Overridden from gfx::AnimationDelegate:
  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
    // Position Tab A
    double distance = start_tab_b_bounds_.x() - start_tab_a_bounds_.x();
    double delta = distance * animation_.GetCurrentValue();
    double new_x = start_tab_a_bounds_.x() + delta;
    gfx::Rect bounds(Round(new_x), start_tab_a_bounds_.y(), tab_a_->width(),
        tab_a_->height());
    tabstrip_->SetTabBounds(tab_a_, bounds);

    // Position Tab B
    distance = start_tab_a_bounds_.x() - start_tab_b_bounds_.x();
    delta = distance * animation_.GetCurrentValue();
    new_x = start_tab_b_bounds_.x() + delta;
    bounds = gfx::Rect(Round(new_x), start_tab_b_bounds_.y(), tab_b_->width(),
        tab_b_->height());
    tabstrip_->SetTabBounds(tab_b_, bounds);
  }

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual int GetDuration() const OVERRIDE {
    return kReorderAnimationDurationMs;
  }

 private:
  // The two tabs being exchanged.
  TabGtk* tab_a_;
  TabGtk* tab_b_;

  // ...and their bounds.
  gfx::Rect start_tab_a_bounds_;
  gfx::Rect start_tab_b_bounds_;

  DISALLOW_COPY_AND_ASSIGN(MoveTabAnimation);
};

////////////////////////////////////////////////////////////////////////////////

// Handles the animated resize layout of the entire TabStrip from one width
// to another.
class ResizeLayoutAnimation : public TabStripGtk::TabAnimation {
 public:
  explicit ResizeLayoutAnimation(TabStripGtk* tabstrip)
      : TabAnimation(tabstrip, RESIZE) {
    int tab_count = tabstrip->GetTabCount();
    int mini_tab_count = tabstrip->GetMiniTabCount();
    GenerateStartAndEndWidths(tab_count, tab_count, mini_tab_count,
                              mini_tab_count);
    InitStartState();
  }
  virtual ~ResizeLayoutAnimation() {}

  // Overridden from gfx::AnimationDelegate:
  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
    tabstrip_->needs_resize_layout_ = false;
    TabStripGtk::TabAnimation::AnimationEnded(animation);
  }

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual int GetDuration() const OVERRIDE {
    return kResizeLayoutAnimationDurationMs;
  }

  virtual double GetWidthForTab(int index) const OVERRIDE {
    TabGtk* tab = tabstrip_->GetTabAt(index);

    if (tab->mini())
      return TabGtk::GetMiniWidth();

    if (tab->IsActive()) {
      return animation_.CurrentValueBetween(start_selected_width_,
                                            end_selected_width_);
    }

    return animation_.CurrentValueBetween(start_unselected_width_,
                                          end_unselected_width_);
  }

 private:
  // We need to start from the current widths of the Tabs as they were last
  // laid out, _not_ the last known good state, which is what'll be done if we
  // don't measure the Tab sizes here and just go with the default TabAnimation
  // behavior...
  void InitStartState() {
    for (int i = 0; i < tabstrip_->GetTabCount(); ++i) {
      TabGtk* current_tab = tabstrip_->GetTabAt(i);
      if (!current_tab->mini()) {
        if (current_tab->IsActive()) {
          start_selected_width_ = current_tab->width();
        } else {
          start_unselected_width_ = current_tab->width();
        }
      }
    }
  }

  DISALLOW_COPY_AND_ASSIGN(ResizeLayoutAnimation);
};

// Handles a tabs mini-state changing while the tab does not change position
// in the model.
class MiniTabAnimation : public TabStripGtk::TabAnimation {
 public:
  MiniTabAnimation(TabStripGtk* tabstrip, int index)
      : TabAnimation(tabstrip, MINI),
        index_(index) {
    int tab_count = tabstrip->GetTabCount();
    int start_mini_count = tabstrip->GetMiniTabCount();
    int end_mini_count = start_mini_count;
    if (tabstrip->GetTabAt(index)->mini())
      start_mini_count--;
    else
      start_mini_count++;
    tabstrip_->GetTabAt(index)->set_animating_mini_change(true);
    GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
                              end_mini_count);
  }

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual int GetDuration() const OVERRIDE {
    return kMiniTabAnimationDurationMs;
  }

  virtual double GetWidthForTab(int index) const OVERRIDE {
    TabGtk* tab = tabstrip_->GetTabAt(index);

    if (index == index_) {
      if (tab->mini()) {
        return animation_.CurrentValueBetween(
            start_selected_width_,
            static_cast<double>(TabGtk::GetMiniWidth()));
      } else {
        return animation_.CurrentValueBetween(
            static_cast<double>(TabGtk::GetMiniWidth()),
            end_selected_width_);
      }
    } else if (tab->mini()) {
      return TabGtk::GetMiniWidth();
    }

    if (tab->IsActive()) {
      return animation_.CurrentValueBetween(start_selected_width_,
                                            end_selected_width_);
    }

    return animation_.CurrentValueBetween(start_unselected_width_,
                                          end_unselected_width_);
  }

 private:
  // Index of the tab whose mini-state changed.
  int index_;

  DISALLOW_COPY_AND_ASSIGN(MiniTabAnimation);
};

////////////////////////////////////////////////////////////////////////////////

// Handles the animation when a tabs mini-state changes and the tab moves as a
// result.
class MiniMoveAnimation : public TabStripGtk::TabAnimation {
 public:
  MiniMoveAnimation(TabStripGtk* tabstrip,
                    int from_index,
                    int to_index,
                    const gfx::Rect& start_bounds)
      : TabAnimation(tabstrip, MINI_MOVE),
        tab_(tabstrip->GetTabAt(to_index)),
        start_bounds_(start_bounds),
        from_index_(from_index),
        to_index_(to_index) {
    int tab_count = tabstrip->GetTabCount();
    int start_mini_count = tabstrip->GetMiniTabCount();
    int end_mini_count = start_mini_count;
    if (tabstrip->GetTabAt(to_index)->mini())
      start_mini_count--;
    else
      start_mini_count++;
    GenerateStartAndEndWidths(tab_count, tab_count, start_mini_count,
                              end_mini_count);
    target_bounds_ = tabstrip->GetIdealBounds(to_index);
    tab_->set_animating_mini_change(true);
  }

  // Overridden from gfx::AnimationDelegate:
  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
    // Do the normal layout.
    TabAnimation::AnimationProgressed(animation);

    // Then special case the position of the tab being moved.
    int x = animation_.CurrentValueBetween(start_bounds_.x(),
                                           target_bounds_.x());
    int width = animation_.CurrentValueBetween(start_bounds_.width(),
                                               target_bounds_.width());
    gfx::Rect tab_bounds(x, start_bounds_.y(), width,
                         start_bounds_.height());
    tabstrip_->SetTabBounds(tab_, tab_bounds);
  }

  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
    tabstrip_->needs_resize_layout_ = false;
    TabStripGtk::TabAnimation::AnimationEnded(animation);
  }

  virtual double GetGapWidth(int index) OVERRIDE {
    if (to_index_ < from_index_) {
      // The tab was made mini.
      if (index == to_index_) {
        double current_size =
            animation_.CurrentValueBetween(0, target_bounds_.width());
        if (current_size < -kTabHOffset)
          return -(current_size + kTabHOffset);
      } else if (index == from_index_ + 1) {
        return animation_.CurrentValueBetween(start_bounds_.width(), 0);
      }
    } else {
      // The tab was was made a normal tab.
      if (index == from_index_) {
        return animation_.CurrentValueBetween(
            TabGtk::GetMiniWidth() + kTabHOffset, 0);
      }
    }
    return 0;
  }

 protected:
  // Overridden from TabStripGtk::TabAnimation:
  virtual int GetDuration() const OVERRIDE {
    return kReorderAnimationDurationMs;
  }

  virtual double GetWidthForTab(int index) const OVERRIDE {
    TabGtk* tab = tabstrip_->GetTabAt(index);

    if (index == to_index_)
      return animation_.CurrentValueBetween(0, target_bounds_.width());

    if (tab->mini())
      return TabGtk::GetMiniWidth();

    if (tab->IsActive()) {
      return animation_.CurrentValueBetween(start_selected_width_,
                                            end_selected_width_);
    }

    return animation_.CurrentValueBetween(start_unselected_width_,
                                          end_unselected_width_);
  }

 private:
  // The tab being moved.
  TabGtk* tab_;

  // Initial bounds of tab_.
  gfx::Rect start_bounds_;

  // Target bounds.
  gfx::Rect target_bounds_;

  // Start and end indices of the tab.
  int from_index_;
  int to_index_;

  DISALLOW_COPY_AND_ASSIGN(MiniMoveAnimation);
};

////////////////////////////////////////////////////////////////////////////////
// TabStripGtk, public:

// static
const int TabStripGtk::mini_to_non_mini_gap_ = 3;

TabStripGtk::TabStripGtk(TabStripModel* model, BrowserWindowGtk* window)
    : current_unselected_width_(TabGtk::GetStandardSize().width()),
      current_selected_width_(TabGtk::GetStandardSize().width()),
      available_width_for_tabs_(-1),
      needs_resize_layout_(false),
      tab_vertical_offset_(0),
      model_(model),
      window_(window),
      theme_service_(GtkThemeService::GetFrom(model->profile())),
      added_as_message_loop_observer_(false),
      hover_tab_selector_(model),
      weak_factory_(this),
      layout_factory_(this) {
}

TabStripGtk::~TabStripGtk() {
  model_->RemoveObserver(this);
  tabstrip_.Destroy();

  // Free any remaining tabs.  This is needed to free the very last tab,
  // because it is not animated on close.  This also happens when all of the
  // tabs are closed at once.
  std::vector<TabData>::iterator iterator = tab_data_.begin();
  for (; iterator < tab_data_.end(); iterator++) {
    delete iterator->tab;
  }

  tab_data_.clear();

  // Make sure we unhook ourselves as a message loop observer so that we don't
  // crash in the case where the user closes the last tab in a window.
  RemoveMessageLoopObserver();
}

void TabStripGtk::Init() {
  model_->AddObserver(this);

  tabstrip_.Own(gtk_fixed_new());
  ViewIDUtil::SetID(tabstrip_.get(), VIEW_ID_TAB_STRIP);
  // We want the tab strip to be horizontally shrinkable, so that the Chrome
  // window can be resized freely.
  gtk_widget_set_size_request(tabstrip_.get(), 0,
                              TabGtk::GetMinimumUnselectedSize().height());
  gtk_widget_set_app_paintable(tabstrip_.get(), TRUE);
  gtk_drag_dest_set(tabstrip_.get(), GTK_DEST_DEFAULT_ALL,
                    NULL, 0,
                    static_cast<GdkDragAction>(
                        GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK));
  static const int targets[] = { ui::TEXT_URI_LIST,
                                 ui::NETSCAPE_URL,
                                 ui::TEXT_PLAIN,
                                 -1 };
  ui::SetDestTargetList(tabstrip_.get(), targets);

  g_signal_connect(tabstrip_.get(), "map",
                   G_CALLBACK(OnMapThunk), this);
  g_signal_connect(tabstrip_.get(), "expose-event",
                   G_CALLBACK(OnExposeThunk), this);
  g_signal_connect(tabstrip_.get(), "size-allocate",
                   G_CALLBACK(OnSizeAllocateThunk), this);
  g_signal_connect(tabstrip_.get(), "drag-motion",
                   G_CALLBACK(OnDragMotionThunk), this);
  g_signal_connect(tabstrip_.get(), "drag-drop",
                   G_CALLBACK(OnDragDropThunk), this);
  g_signal_connect(tabstrip_.get(), "drag-leave",
                   G_CALLBACK(OnDragLeaveThunk), this);
  g_signal_connect(tabstrip_.get(), "drag-data-received",
                   G_CALLBACK(OnDragDataReceivedThunk), this);

  newtab_button_.reset(MakeNewTabButton());
  newtab_surface_bounds_.SetRect(0, 0, newtab_button_->SurfaceWidth(),
                                 newtab_button_->SurfaceHeight());

  gtk_widget_show_all(tabstrip_.get());

  bounds_ = GetInitialWidgetBounds(tabstrip_.get());

  if (drop_indicator_width == 0) {
    // Direction doesn't matter, both images are the same size.
    GdkPixbuf* drop_image = GetDropArrowImage(true)->ToGdkPixbuf();
    drop_indicator_width = gdk_pixbuf_get_width(drop_image);
    drop_indicator_height = gdk_pixbuf_get_height(drop_image);
  }

  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(theme_service_));
  theme_service_->InitThemesFor(this);

  ViewIDUtil::SetDelegateForWidget(widget(), this);
}

void TabStripGtk::Show() {
  gtk_widget_show(tabstrip_.get());
}

void TabStripGtk::Hide() {
  gtk_widget_hide(tabstrip_.get());
}

bool TabStripGtk::IsActiveDropTarget() const {
  for (int i = 0; i < GetTabCount(); ++i) {
    TabGtk* tab = GetTabAt(i);
    if (tab->dragging())
      return true;
  }
  return false;
}

void TabStripGtk::Layout() {
  // Called from:
  // - window resize
  // - animation completion
  StopAnimation();

  GenerateIdealBounds();
  int tab_count = GetTabCount();
  int tab_right = 0;
  for (int i = 0; i < tab_count; ++i) {
    const gfx::Rect& bounds = tab_data_.at(i).ideal_bounds;
    TabGtk* tab = GetTabAt(i);
    tab->set_animating_mini_change(false);
    tab->set_vertical_offset(tab_vertical_offset_);
    SetTabBounds(tab, bounds);
    tab_right = bounds.right();
    tab_right += GetTabHOffset(i + 1);
  }

  LayoutNewTabButton(static_cast<double>(tab_right), current_unselected_width_);
}

void TabStripGtk::SchedulePaint() {
  gtk_widget_queue_draw(tabstrip_.get());
}

void TabStripGtk::SetBounds(const gfx::Rect& bounds) {
  bounds_ = bounds;
}

void TabStripGtk::UpdateLoadingAnimations() {
  for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
    TabGtk* current_tab = GetTabAt(i);
    if (current_tab->closing()) {
      --index;
    } else {
      TabRendererGtk::AnimationState state;
      content::WebContents* web_contents = model_->GetWebContentsAt(index);
      if (!web_contents|| !web_contents->IsLoading()) {
        state = TabGtk::ANIMATION_NONE;
      } else if (web_contents->IsWaitingForResponse()) {
        state = TabGtk::ANIMATION_WAITING;
      } else {
        state = TabGtk::ANIMATION_LOADING;
      }
      if (current_tab->ValidateLoadingAnimation(state)) {
        // Queue the tab's icon area to be repainted.
        gfx::Rect favicon_bounds = current_tab->favicon_bounds();
        gtk_widget_queue_draw_area(tabstrip_.get(),
            favicon_bounds.x() + current_tab->x(),
            favicon_bounds.y() + current_tab->y(),
            favicon_bounds.width(),
            favicon_bounds.height());
      }
    }
  }
}

bool TabStripGtk::IsCompatibleWith(TabStripGtk* other) {
  return model_->profile() == other->model()->profile();
}

bool TabStripGtk::IsAnimating() const {
  return active_animation_.get() != NULL;
}

void TabStripGtk::DestroyDragController() {
  drag_controller_.reset();
}

void TabStripGtk::DestroyDraggedTab(TabGtk* tab) {
  // We could be running an animation that references this Tab.
  StopAnimation();

  // Make sure we leave the tab_data_ vector in a consistent state, otherwise
  // we'll be pointing to tabs that have been deleted and removed from the
  // child view list.
  std::vector<TabData>::iterator it = tab_data_.begin();
  for (; it != tab_data_.end(); ++it) {
    if (it->tab == tab) {
      if (!model_->closing_all())
        NOTREACHED() << "Leaving in an inconsistent state!";
      tab_data_.erase(it);
      break;
    }
  }

  gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), tab->widget());
  // If we delete the dragged source tab here, the DestroyDragWidget posted
  // task will be run after the tab is deleted, leading to a crash.
  base::MessageLoop::current()->DeleteSoon(FROM_HERE, tab);

  // Force a layout here, because if we've just quickly drag detached a Tab,
  // the stopping of the active animation above may have left the TabStrip in a
  // bad (visual) state.
  Layout();
}

gfx::Rect TabStripGtk::GetIdealBounds(int index) {
  DCHECK(index >= 0 && index < GetTabCount());
  return tab_data_.at(index).ideal_bounds;
}

void TabStripGtk::SetVerticalOffset(int offset) {
  tab_vertical_offset_ = offset;
  Layout();
}

gfx::Point TabStripGtk::GetTabStripOriginForWidget(GtkWidget* target) {
  int x, y;
  GtkAllocation widget_allocation;
  gtk_widget_get_allocation(widget(), &widget_allocation);
  if (!gtk_widget_translate_coordinates(widget(), target,
      -widget_allocation.x, 0, &x, &y)) {
    // If the tab strip isn't showing, give the coordinates relative to the
    // toplevel instead.
    if (!gtk_widget_translate_coordinates(
        gtk_widget_get_toplevel(widget()), target, 0, 0, &x, &y)) {
      NOTREACHED();
    }
  }
  if (!gtk_widget_get_has_window(target)) {
    GtkAllocation target_allocation;
    gtk_widget_get_allocation(target, &target_allocation);
    x += target_allocation.x;
    y += target_allocation.y;
  }
  return gfx::Point(x, y);
}

////////////////////////////////////////////////////////////////////////////////
// ViewIDUtil::Delegate implementation

GtkWidget* TabStripGtk::GetWidgetForViewID(ViewID view_id) {
  if (GetTabCount() > 0) {
    if (view_id == VIEW_ID_TAB_LAST) {
      return GetTabAt(GetTabCount() - 1)->widget();
    } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
      int index = view_id - VIEW_ID_TAB_0;
      if (index >= 0 && index < GetTabCount()) {
        return GetTabAt(index)->widget();
      } else {
        return NULL;
      }
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////////////////
// TabStripGtk, TabStripModelObserver implementation:

void TabStripGtk::TabInsertedAt(WebContents* contents,
                                int index,
                                bool foreground) {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::TabInsertedAt");

  DCHECK(contents);
  DCHECK(index == TabStripModel::kNoTab || model_->ContainsIndex(index));

  StopAnimation();

  bool contains_tab = false;
  TabGtk* tab = NULL;
  // First see if this Tab is one that was dragged out of this TabStrip and is
  // now being dragged back in. In this case, the DraggedTabController actually
  // has the Tab already constructed and we can just insert it into our list
  // again.
  if (IsDragSessionActive()) {
    tab = drag_controller_->GetDraggedTabForContents(contents);
    if (tab) {
      // If the Tab was detached, it would have been animated closed but not
      // removed, so we need to reset this property.
      tab->set_closing(false);
      tab->ValidateLoadingAnimation(TabRendererGtk::ANIMATION_NONE);
      tab->SetVisible(true);
    }

    // See if we're already in the list. We don't want to add ourselves twice.
    std::vector<TabData>::const_iterator iter = tab_data_.begin();
    for (; iter != tab_data_.end() && !contains_tab; ++iter) {
      if (iter->tab == tab)
        contains_tab = true;
    }
  }

  if (!tab)
    tab = new TabGtk(this);

  // Only insert if we're not already in the list.
  if (!contains_tab) {
    TabData d = { tab, gfx::Rect() };
    tab_data_.insert(tab_data_.begin() + index, d);
    tab->UpdateData(contents, model_->IsAppTab(index), false);
  }
  tab->set_mini(model_->IsMiniTab(index));
  tab->set_app(model_->IsAppTab(index));
  tab->SetBlocked(model_->IsTabBlocked(index));

  if (gtk_widget_get_parent(tab->widget()) != tabstrip_.get())
    gtk_fixed_put(GTK_FIXED(tabstrip_.get()), tab->widget(), 0, 0);

  // Don't animate the first tab; it looks weird.
  if (GetTabCount() > 1) {
    StartInsertTabAnimation(index);
    // We added the tab at 0x0, we need to force an animation step otherwise
    // if GTK paints before the animation event the tab is painted at 0x0
    // which is most likely not where it should be positioned.
    active_animation_->AnimationProgressed(NULL);
  } else {
    Layout();
  }

  ReStack();
}

void TabStripGtk::TabDetachedAt(WebContents* contents, int index) {
  GenerateIdealBounds();
  StartRemoveTabAnimation(index, contents);
  // Have to do this _after_ calling StartRemoveTabAnimation, so that any
  // previous remove is completed fully and index is valid in sync with the
  // model index.
  GetTabAt(index)->set_closing(true);
}

void TabStripGtk::ActiveTabChanged(WebContents* old_contents,
                                   WebContents* new_contents,
                                   int index,
                                   int reason) {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::ActiveTabChanged");
  ReStack();
}

void TabStripGtk::TabSelectionChanged(TabStripModel* tab_strip_model,
                                      const ui::ListSelectionModel& old_model) {
  // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
  // a different size to the selected ones.
  bool tiny_tabs = current_unselected_width_ != current_selected_width_;
  if (!IsAnimating() && (!needs_resize_layout_ || tiny_tabs))
    Layout();

  if (model_->active_index() >= 0)
    GetTabAt(model_->active_index())->SchedulePaint();

  if (old_model.active() >= 0) {
    GetTabAt(old_model.active())->SchedulePaint();
    GetTabAt(old_model.active())->StopMiniTabTitleAnimation();
  }

  std::vector<int> indices_affected;
  std::insert_iterator<std::vector<int> > it1(indices_affected,
                                              indices_affected.begin());
  std::set_symmetric_difference(
      old_model.selected_indices().begin(),
      old_model.selected_indices().end(),
      model_->selection_model().selected_indices().begin(),
      model_->selection_model().selected_indices().end(),
      it1);
  for (std::vector<int>::iterator it = indices_affected.begin();
       it != indices_affected.end(); ++it) {
    // SchedulePaint() has already been called for the active tab and
    // the previously active tab (if it still exists).
    if (*it != model_->active_index() && *it != old_model.active())
      GetTabAtAdjustForAnimation(*it)->SchedulePaint();
  }

  ui::ListSelectionModel::SelectedIndices no_longer_selected =
      base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
          old_model.selected_indices(),
          model_->selection_model().selected_indices());
  for (std::vector<int>::iterator it = no_longer_selected.begin();
       it != no_longer_selected.end(); ++it) {
    GetTabAtAdjustForAnimation(*it)->StopMiniTabTitleAnimation();
  }
}

void TabStripGtk::TabMoved(WebContents* contents,
                           int from_index,
                           int to_index) {
  gfx::Rect start_bounds = GetIdealBounds(from_index);
  TabGtk* tab = GetTabAt(from_index);
  tab_data_.erase(tab_data_.begin() + from_index);
  TabData data = {tab, gfx::Rect()};
  tab->set_mini(model_->IsMiniTab(to_index));
  tab->SetBlocked(model_->IsTabBlocked(to_index));
  tab_data_.insert(tab_data_.begin() + to_index, data);
  GenerateIdealBounds();
  StartMoveTabAnimation(from_index, to_index);
  ReStack();
}

void TabStripGtk::TabChangedAt(WebContents* contents,
                               int index,
                               TabChangeType change_type) {
  // Index is in terms of the model. Need to make sure we adjust that index in
  // case we have an animation going.
  TabGtk* tab = GetTabAtAdjustForAnimation(index);
  if (change_type == TITLE_NOT_LOADING) {
    if (tab->mini() && !tab->IsActive())
      tab->StartMiniTabTitleAnimation();
    // We'll receive another notification of the change asynchronously.
    return;
  }
  tab->UpdateData(contents,
                  model_->IsAppTab(index),
                  change_type == LOADING_ONLY);
  tab->UpdateFromModel();
}

void TabStripGtk::TabReplacedAt(TabStripModel* tab_strip_model,
                                WebContents* old_contents,
                                WebContents* new_contents,
                                int index) {
  TabChangedAt(new_contents, index, ALL);
}

void TabStripGtk::TabMiniStateChanged(WebContents* contents, int index) {
  // Don't do anything if we've already picked up the change from TabMoved.
  if (GetTabAt(index)->mini() == model_->IsMiniTab(index))
    return;

  GetTabAt(index)->set_mini(model_->IsMiniTab(index));
  // Don't animate if the window isn't visible yet. The window won't be visible
  // when dragging a mini-tab to a new window.
  if (window_ && window_->window() &&
      gtk_widget_get_visible(GTK_WIDGET(window_->window()))) {
    StartMiniTabAnimation(index);
  } else {
    Layout();
  }
}

void TabStripGtk::TabBlockedStateChanged(WebContents* contents, int index) {
  GetTabAt(index)->SetBlocked(model_->IsTabBlocked(index));
}

////////////////////////////////////////////////////////////////////////////////
// TabStripGtk, TabGtk::TabDelegate implementation:

bool TabStripGtk::IsTabActive(const TabGtk* tab) const {
  if (tab->closing())
    return false;

  return GetIndexOfTab(tab) == model_->active_index();
}

bool TabStripGtk::IsTabSelected(const TabGtk* tab) const {
  if (tab->closing())
    return false;

  return model_->IsTabSelected(GetIndexOfTab(tab));
}

bool TabStripGtk::IsTabDetached(const TabGtk* tab) const {
  if (drag_controller_.get())
    return drag_controller_->IsTabDetached(tab);
  return false;
}

void TabStripGtk::GetCurrentTabWidths(double* unselected_width,
                                      double* selected_width) const {
  *unselected_width = current_unselected_width_;
  *selected_width = current_selected_width_;
}

bool TabStripGtk::IsTabPinned(const TabGtk* tab) const {
  if (tab->closing())
    return false;

  return model_->IsTabPinned(GetIndexOfTab(tab));
}

void TabStripGtk::ActivateTab(TabGtk* tab) {
  int index = GetIndexOfTab(tab);
  if (model_->ContainsIndex(index))
    model_->ActivateTabAt(index, true);
}

void TabStripGtk::ToggleTabSelection(TabGtk* tab) {
  int index = GetIndexOfTab(tab);
  model_->ToggleSelectionAt(index);
}

void TabStripGtk::ExtendTabSelection(TabGtk* tab) {
  int index = GetIndexOfTab(tab);
  if (model_->ContainsIndex(index))
    model_->ExtendSelectionTo(index);
}

void TabStripGtk::CloseTab(TabGtk* tab) {
  int tab_index = GetIndexOfTab(tab);
  if (model_->ContainsIndex(tab_index)) {
    TabGtk* last_tab = GetTabAt(GetTabCount() - 1);
    // Limit the width available to the TabStrip for laying out Tabs, so that
    // Tabs are not resized until a later time (when the mouse pointer leaves
    // the TabStrip).
    available_width_for_tabs_ = GetAvailableWidthForTabs(last_tab);
    needs_resize_layout_ = true;
    // We hook into the message loop in order to receive mouse move events when
    // the mouse is outside of the tabstrip.  We unhook once the resize layout
    // animation is started.
    AddMessageLoopObserver();
    model_->CloseWebContentsAt(tab_index,
                               TabStripModel::CLOSE_USER_GESTURE |
                               TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
  }
}

bool TabStripGtk::IsCommandEnabledForTab(
    TabStripModel::ContextMenuCommand command_id, const TabGtk* tab) const {
  int index = GetIndexOfTab(tab);
  if (model_->ContainsIndex(index))
    return model_->IsContextMenuCommandEnabled(index, command_id);
  return false;
}

void TabStripGtk::ExecuteCommandForTab(
    TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
  int index = GetIndexOfTab(tab);
  if (model_->ContainsIndex(index))
    model_->ExecuteContextMenuCommand(index, command_id);
}

void TabStripGtk::StartHighlightTabsForCommand(
    TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
  if (command_id == TabStripModel::CommandCloseOtherTabs ||
      command_id == TabStripModel::CommandCloseTabsToRight) {
    NOTIMPLEMENTED();
  }
}

void TabStripGtk::StopHighlightTabsForCommand(
    TabStripModel::ContextMenuCommand command_id, TabGtk* tab) {
  if (command_id == TabStripModel::CommandCloseTabsToRight ||
      command_id == TabStripModel::CommandCloseOtherTabs) {
    // Just tell all Tabs to stop pulsing - it's safe.
    StopAllHighlighting();
  }
}

void TabStripGtk::StopAllHighlighting() {
  // TODO(jhawkins): Hook up animations.
  NOTIMPLEMENTED();
}

void TabStripGtk::MaybeStartDrag(TabGtk* tab, const gfx::Point& point) {
  // Don't accidentally start any drag operations during animations if the
  // mouse is down.
  if (IsAnimating() || tab->closing() || !HasAvailableDragActions())
    return;

  std::vector<TabGtk*> tabs;
  for (size_t i = 0; i < model()->selection_model().size(); i++) {
    TabGtk* selected_tab =
        GetTabAtAdjustForAnimation(
            model()->selection_model().selected_indices()[i]);
    if (!selected_tab->closing())
      tabs.push_back(selected_tab);
  }

  drag_controller_.reset(new DraggedTabControllerGtk(this, tab, tabs));
  drag_controller_->CaptureDragInfo(point);
}

void TabStripGtk::ContinueDrag(GdkDragContext* context) {
  // We can get called even if |MaybeStartDrag| wasn't called in the event of
  // a TabStrip animation when the mouse button is down. In this case we should
  // _not_ continue the drag because it can lead to weird bugs.
  if (drag_controller_.get())
    drag_controller_->Drag();
}

bool TabStripGtk::EndDrag(bool canceled) {
  return drag_controller_.get() ? drag_controller_->EndDrag(canceled) : false;
}

bool TabStripGtk::HasAvailableDragActions() const {
  return model_->delegate()->GetDragActions() != 0;
}

GtkThemeService* TabStripGtk::GetThemeProvider() {
  return theme_service_;
}

TabStripMenuController* TabStripGtk::GetTabStripMenuControllerForTab(
    TabGtk* tab) {
  return new TabStripMenuController(tab, model(), GetIndexOfTab(tab));
}

///////////////////////////////////////////////////////////////////////////////
// TabStripGtk, MessageLoop::Observer implementation:

void TabStripGtk::WillProcessEvent(GdkEvent* event) {
  // Nothing to do.
}

void TabStripGtk::DidProcessEvent(GdkEvent* event) {
  switch (event->type) {
    case GDK_MOTION_NOTIFY:
    case GDK_LEAVE_NOTIFY:
      HandleGlobalMouseMoveEvent();
      break;
    default:
      break;
  }
}

///////////////////////////////////////////////////////////////////////////////
// TabStripGtk, content::NotificationObserver implementation:

void TabStripGtk::Observe(int type,
                          const content::NotificationSource& source,
                          const content::NotificationDetails& details) {
  DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
  SetNewTabButtonBackground();
}

////////////////////////////////////////////////////////////////////////////////
// TabStripGtk, private:

int TabStripGtk::GetTabCount() const {
  return static_cast<int>(tab_data_.size());
}

int TabStripGtk::GetMiniTabCount() const {
  int mini_count = 0;
  for (size_t i = 0; i < tab_data_.size(); ++i) {
    if (tab_data_[i].tab->mini())
      mini_count++;
    else
      return mini_count;
  }
  return mini_count;
}

int TabStripGtk::GetAvailableWidthForTabs(TabGtk* last_tab) const {
  if (!base::i18n::IsRTL())
    return last_tab->x() - bounds_.x() + last_tab->width();
  else
    return bounds_.width() - last_tab->x();
}

int TabStripGtk::GetIndexOfTab(const TabGtk* tab) const {
  for (int i = 0, index = 0; i < GetTabCount(); ++i, ++index) {
    TabGtk* current_tab = GetTabAt(i);
    if (current_tab->closing()) {
      --index;
    } else if (current_tab == tab) {
      return index;
    }
  }
  return -1;
}

TabGtk* TabStripGtk::GetTabAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK_LT(index, GetTabCount());
  return tab_data_.at(index).tab;
}

TabGtk* TabStripGtk::GetTabAtAdjustForAnimation(int index) const {
  if (active_animation_.get() &&
      active_animation_->type() == TabAnimation::REMOVE &&
      index >=
      static_cast<RemoveTabAnimation*>(active_animation_.get())->index()) {
    index++;
  }
  return GetTabAt(index);
}

void TabStripGtk::RemoveTabAt(int index) {
  TabGtk* removed = tab_data_.at(index).tab;

  // Remove the Tab from the TabStrip's list.
  tab_data_.erase(tab_data_.begin() + index);

  if (!removed->dragging()) {
    gtk_container_remove(GTK_CONTAINER(tabstrip_.get()), removed->widget());
    delete removed;
  }
}

void TabStripGtk::HandleGlobalMouseMoveEvent() {
  if (!IsCursorInTabStripZone()) {
    // Mouse moved outside the tab slop zone, start a timer to do a resize
    // layout after a short while...
    if (!weak_factory_.HasWeakPtrs()) {
      base::MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          base::Bind(&TabStripGtk::ResizeLayoutTabs,
                     weak_factory_.GetWeakPtr()),
          base::TimeDelta::FromMilliseconds(kResizeTabsTimeMs));
    }
  } else {
    // Mouse moved quickly out of the tab strip and then into it again, so
    // cancel the timer so that the strip doesn't move when the mouse moves
    // back over it.
    weak_factory_.InvalidateWeakPtrs();
  }
}

void TabStripGtk::GenerateIdealBounds() {
  int tab_count = GetTabCount();
  double unselected, selected;
  GetDesiredTabWidths(tab_count, GetMiniTabCount(), &unselected, &selected);

  current_unselected_width_ = unselected;
  current_selected_width_ = selected;

  // NOTE: This currently assumes a tab's height doesn't differ based on
  // selected state or the number of tabs in the strip!
  int tab_height = TabGtk::GetStandardSize().height();
  double tab_x = tab_start_x();
  for (int i = 0; i < tab_count; ++i) {
    TabGtk* tab = GetTabAt(i);
    double tab_width = unselected;
    if (tab->mini())
      tab_width = TabGtk::GetMiniWidth();
    else if (tab->IsActive())
      tab_width = selected;
    double end_of_tab = tab_x + tab_width;
    int rounded_tab_x = Round(tab_x);
    gfx::Rect state(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
                    tab_height);
    tab_data_.at(i).ideal_bounds = state;
    tab_x = end_of_tab + GetTabHOffset(i + 1);
  }
}

void TabStripGtk::LayoutNewTabButton(double last_tab_right,
                                     double unselected_width) {
  GtkWidget* toplevel = gtk_widget_get_ancestor(widget(), GTK_TYPE_WINDOW);
  bool is_maximized = false;
  if (toplevel) {
    GdkWindow* gdk_window = gtk_widget_get_window(toplevel);
    is_maximized = (gdk_window_get_state(gdk_window) &
                    GDK_WINDOW_STATE_MAXIMIZED) != 0;
  }

  int y = is_maximized ? 0 : kNewTabButtonVOffset;
  int height = newtab_surface_bounds_.height() + kNewTabButtonVOffset - y;

  gfx::Rect bounds(0, y, newtab_surface_bounds_.width(), height);
  int delta = abs(Round(unselected_width) - TabGtk::GetStandardSize().width());
  if (delta > 1 && !needs_resize_layout_) {
    // We're shrinking tabs, so we need to anchor the New Tab button to the
    // right edge of the TabStrip's bounds, rather than the right edge of the
    // right-most Tab, otherwise it'll bounce when animating.
    bounds.set_x(bounds_.width() - newtab_button_->WidgetAllocation().width);
  } else {
    bounds.set_x(Round(last_tab_right - kTabHOffset) + kNewTabButtonHOffset);
  }
  bounds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));

  gtk_fixed_move(GTK_FIXED(tabstrip_.get()), newtab_button_->widget(),
                 bounds.x(), bounds.y());
  gtk_widget_set_size_request(newtab_button_->widget(), bounds.width(),
                              bounds.height());
}

void TabStripGtk::GetDesiredTabWidths(int tab_count,
                                      int mini_tab_count,
                                      double* unselected_width,
                                      double* selected_width) const {
  DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
  const double min_unselected_width =
      TabGtk::GetMinimumUnselectedSize().width();
  const double min_selected_width =
      TabGtk::GetMinimumSelectedSize().width();

  *unselected_width = min_unselected_width;
  *selected_width = min_selected_width;

  if (tab_count == 0) {
    // Return immediately to avoid divide-by-zero below.
    return;
  }

  // Determine how much space we can actually allocate to tabs.
  GtkAllocation tabstrip_allocation;
  gtk_widget_get_allocation(tabstrip_.get(), &tabstrip_allocation);
  int available_width = tabstrip_allocation.width;
  if (available_width_for_tabs_ < 0) {
    available_width = bounds_.width();
    available_width -=
        (kNewTabButtonHOffset + newtab_button_->WidgetAllocation().width);
  } else {
    // Interesting corner case: if |available_width_for_tabs_| > the result
    // of the calculation in the conditional arm above, the strip is in
    // overflow.  We can either use the specified width or the true available
    // width here; the first preserves the consistent "leave the last tab under
    // the user's mouse so they can close many tabs" behavior at the cost of
    // prolonging the glitchy appearance of the overflow state, while the second
    // gets us out of overflow as soon as possible but forces the user to move
    // their mouse for a few tabs' worth of closing.  We choose visual
    // imperfection over behavioral imperfection and select the first option.
    available_width = available_width_for_tabs_;
  }

  if (mini_tab_count > 0) {
    available_width -= mini_tab_count * (TabGtk::GetMiniWidth() + kTabHOffset);
    tab_count -= mini_tab_count;
    if (tab_count == 0) {
      *selected_width = *unselected_width = TabGtk::GetStandardSize().width();
      return;
    }
    // Account for gap between the last mini-tab and first normal tab.
    available_width -= mini_to_non_mini_gap_;
  }

  // Calculate the desired tab widths by dividing the available space into equal
  // portions.  Don't let tabs get larger than the "standard width" or smaller
  // than the minimum width for each type, respectively.
  const int total_offset = kTabHOffset * (tab_count - 1);
  const double desired_tab_width = std::min(
      (static_cast<double>(available_width - total_offset) /
       static_cast<double>(tab_count)),
      static_cast<double>(TabGtk::GetStandardSize().width()));

  *unselected_width = std::max(desired_tab_width, min_unselected_width);
  *selected_width = std::max(desired_tab_width, min_selected_width);

  // When there are multiple tabs, we'll have one selected and some unselected
  // tabs.  If the desired width was between the minimum sizes of these types,
  // try to shrink the tabs with the smaller minimum.  For example, if we have
  // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5.  If
  // selected tabs have a minimum width of 4 and unselected tabs have a minimum
  // width of 1, the above code would set *unselected_width = 2.5,
  // *selected_width = 4, which results in a total width of 11.5.  Instead, we
  // want to set *unselected_width = 2, *selected_width = 4, for a total width
  // of 10.
  if (tab_count > 1) {
    if ((min_unselected_width < min_selected_width) &&
        (desired_tab_width < min_selected_width)) {
      double calc_width =
          static_cast<double>(
              available_width - total_offset - min_selected_width) /
          static_cast<double>(tab_count - 1);
      *unselected_width = std::max(calc_width, min_unselected_width);
    } else if ((min_unselected_width > min_selected_width) &&
               (desired_tab_width < min_unselected_width)) {
      *selected_width = std::max(available_width - total_offset -
          (min_unselected_width * (tab_count - 1)), min_selected_width);
    }
  }
}

int TabStripGtk::GetTabHOffset(int tab_index) {
  if (tab_index < GetTabCount() && GetTabAt(tab_index - 1)->mini() &&
      !GetTabAt(tab_index)->mini()) {
    return mini_to_non_mini_gap_ + kTabHOffset;
  }
  return kTabHOffset;
}

int TabStripGtk::tab_start_x() const {
  return 0;
}

void TabStripGtk::ResizeLayoutTabs() {
  weak_factory_.InvalidateWeakPtrs();
  layout_factory_.InvalidateWeakPtrs();

  // It is critically important that this is unhooked here, otherwise we will
  // keep spying on messages forever.
  RemoveMessageLoopObserver();

  available_width_for_tabs_ = -1;
  int mini_tab_count = GetMiniTabCount();
  if (mini_tab_count == GetTabCount()) {
    // Only mini tabs, we know the tab widths won't have changed (all mini-tabs
    // have the same width), so there is nothing to do.
    return;
  }
  TabGtk* first_tab = GetTabAt(mini_tab_count);
  double unselected, selected;
  GetDesiredTabWidths(GetTabCount(), mini_tab_count, &unselected, &selected);
  int w = Round(first_tab->IsActive() ? selected : unselected);

  // We only want to run the animation if we're not already at the desired
  // size.
  if (abs(first_tab->width() - w) > 1)
    StartResizeLayoutAnimation();
}

bool TabStripGtk::IsCursorInTabStripZone() const {
  gfx::Point tabstrip_topleft;
  gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &tabstrip_topleft);

  gfx::Rect bds = bounds();
  bds.set_origin(tabstrip_topleft);
  bds.set_height(bds.height() + kTabStripAnimationVSlop);

  GdkScreen* screen = gdk_screen_get_default();
  GdkDisplay* display = gdk_screen_get_display(screen);
  gint x, y;
  gdk_display_get_pointer(display, NULL, &x, &y, NULL);
  gfx::Point cursor_point(x, y);

  return bds.Contains(cursor_point);
}

void TabStripGtk::ReStack() {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::ReStack");

  if (!gtk_widget_get_realized(tabstrip_.get())) {
    // If the window isn't realized yet, we can't stack them yet. It will be
    // done by the OnMap signal handler.
    return;
  }
  int tab_count = GetTabCount();
  TabGtk* active_tab = NULL;
  for (int i = tab_count - 1; i >= 0; --i) {
    TabGtk* tab = GetTabAt(i);
    if (tab->IsActive())
      active_tab = tab;
    else
      tab->Raise();
  }
  if (active_tab)
    active_tab->Raise();
}

void TabStripGtk::AddMessageLoopObserver() {
  if (!added_as_message_loop_observer_) {
    base::MessageLoopForUI::current()->AddObserver(this);
    added_as_message_loop_observer_ = true;
  }
}

void TabStripGtk::RemoveMessageLoopObserver() {
  if (added_as_message_loop_observer_) {
    base::MessageLoopForUI::current()->RemoveObserver(this);
    added_as_message_loop_observer_ = false;
  }
}

gfx::Rect TabStripGtk::GetDropBounds(int drop_index,
                                     bool drop_before,
                                     bool* is_beneath) {
  DCHECK_NE(drop_index, -1);
  int center_x;
  if (drop_index < GetTabCount()) {
    TabGtk* tab = GetTabAt(drop_index);
    gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
    // TODO(sky): update these for pinned tabs.
    if (drop_before)
      center_x = bounds.x() - (kTabHOffset / 2);
    else
      center_x = bounds.x() + (bounds.width() / 2);
  } else {
    TabGtk* last_tab = GetTabAt(drop_index - 1);
    gfx::Rect bounds = last_tab->GetNonMirroredBounds(tabstrip_.get());
    center_x = bounds.x() + bounds.width() + (kTabHOffset / 2);
  }

  center_x = gtk_util::MirroredXCoordinate(tabstrip_.get(), center_x);

  // Determine the screen bounds.
  gfx::Point drop_loc(center_x - drop_indicator_width / 2,
                      -drop_indicator_height);
  gtk_util::ConvertWidgetPointToScreen(tabstrip_.get(), &drop_loc);
  gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
                        drop_indicator_height);

  // TODO(jhawkins): We always display the arrow underneath the tab because we
  // don't have custom frame support yet.
  *is_beneath = true;
  if (*is_beneath)
    drop_bounds.Offset(0, drop_bounds.height() + bounds().height());

  return drop_bounds;
}

void TabStripGtk::UpdateDropIndex(GdkDragContext* context, gint x, gint y) {
  // If the UI layout is right-to-left, we need to mirror the mouse
  // coordinates since we calculate the drop index based on the
  // original (and therefore non-mirrored) positions of the tabs.
  x = gtk_util::MirroredXCoordinate(tabstrip_.get(), x);
  // We don't allow replacing the urls of mini-tabs.
  for (int i = GetMiniTabCount(); i < GetTabCount(); ++i) {
    TabGtk* tab = GetTabAt(i);
    gfx::Rect bounds = tab->GetNonMirroredBounds(tabstrip_.get());
    const int tab_max_x = bounds.x() + bounds.width();
    const int hot_width = bounds.width() / kTabEdgeRatioInverse;
    if (x < tab_max_x) {
      if (x < bounds.x() + hot_width)
        SetDropIndex(i, true);
      else if (x >= tab_max_x - hot_width)
        SetDropIndex(i + 1, true);
      else
        SetDropIndex(i, false);
      return;
    }
  }

  // The drop isn't over a tab, add it to the end.
  SetDropIndex(GetTabCount(), true);
}

void TabStripGtk::SetDropIndex(int index, bool drop_before) {
  bool is_beneath;
  gfx::Rect drop_bounds = GetDropBounds(index, drop_before, &is_beneath);

  // Perform a delayed tab transition if hovering directly over a tab;
  // otherwise, cancel the pending one.
  if (index != -1 && !drop_before)
    hover_tab_selector_.StartTabTransition(index);
  else
    hover_tab_selector_.CancelTabTransition();

  if (!drop_info_.get()) {
    drop_info_.reset(new DropInfo(index, drop_before, !is_beneath));
  } else {
    if (!GTK_IS_WIDGET(drop_info_->container)) {
      drop_info_->CreateContainer();
    } else if (drop_info_->drop_index == index &&
               drop_info_->drop_before == drop_before) {
      return;
    }

    drop_info_->drop_index = index;
    drop_info_->drop_before = drop_before;
    if (is_beneath == drop_info_->point_down) {
      drop_info_->point_down = !is_beneath;
      drop_info_->drop_arrow = GetDropArrowImage(drop_info_->point_down);
    }
  }

  gtk_window_move(GTK_WINDOW(drop_info_->container),
                  drop_bounds.x(), drop_bounds.y());
  gtk_window_resize(GTK_WINDOW(drop_info_->container),
                    drop_bounds.width(), drop_bounds.height());
}

bool TabStripGtk::CompleteDrop(const guchar* data, bool is_plain_text) {
  if (!drop_info_.get())
    return false;

  const int drop_index = drop_info_->drop_index;
  const bool drop_before = drop_info_->drop_before;

  // Destroy the drop indicator.
  drop_info_.reset();

  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  GURL url;
  if (is_plain_text) {
    AutocompleteMatch match;
    AutocompleteClassifierFactory::GetForProfile(model_->profile())->Classify(
        base::UTF8ToUTF16(reinterpret_cast<const char*>(data)),
        false, false, AutocompleteInput::INVALID_SPEC, &match, NULL);
    url = match.destination_url;
  } else {
    std::string url_string(reinterpret_cast<const char*>(data));
    url = GURL(url_string.substr(0, url_string.find_first_of('\n')));
  }
  if (!url.is_valid())
    return false;

  chrome::NavigateParams params(window()->browser(), url,
                                content::PAGE_TRANSITION_LINK);
  params.tabstrip_index = drop_index;

  if (drop_before) {
    params.disposition = NEW_FOREGROUND_TAB;
  } else {
    params.disposition = CURRENT_TAB;
    params.source_contents = model_->GetWebContentsAt(drop_index);
  }

  chrome::Navigate(&params);

  return true;
}

// static
gfx::Image* TabStripGtk::GetDropArrowImage(bool is_down) {
  return &ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed(
      is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
}

// TabStripGtk::DropInfo -------------------------------------------------------

TabStripGtk::DropInfo::DropInfo(int drop_index, bool drop_before,
                                bool point_down)
    : drop_index(drop_index),
      drop_before(drop_before),
      point_down(point_down) {
  CreateContainer();
  drop_arrow = GetDropArrowImage(point_down);
}

TabStripGtk::DropInfo::~DropInfo() {
  DestroyContainer();
}

gboolean TabStripGtk::DropInfo::OnExposeEvent(GtkWidget* widget,
                                              GdkEventExpose* event) {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::DropInfo::OnExposeEvent");

  if (ui::IsScreenComposited()) {
    SetContainerTransparency();
  } else {
    SetContainerShapeMask();
  }

  cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
  gdk_cairo_rectangle(cr, &event->area);
  cairo_clip(cr);

  drop_arrow->ToCairo()->SetSource(cr, widget, 0, 0);
  cairo_paint(cr);

  cairo_destroy(cr);

  return FALSE;
}

// Sets the color map of the container window to allow the window to be
// transparent.
void TabStripGtk::DropInfo::SetContainerColorMap() {
  GdkScreen* screen = gtk_widget_get_screen(container);
  GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen);

  // If rgba is not available, use rgb instead.
  if (!colormap)
    colormap = gdk_screen_get_rgb_colormap(screen);

  gtk_widget_set_colormap(container, colormap);
}

// Sets full transparency for the container window.  This is used if
// compositing is available for the screen.
void TabStripGtk::DropInfo::SetContainerTransparency() {
  cairo_t* cairo_context = gdk_cairo_create(gtk_widget_get_window(container));
  if (!cairo_context)
      return;

  // Make the background of the dragged tab window fully transparent.  All of
  // the content of the window (child widgets) will be completely opaque.

  cairo_scale(cairo_context, static_cast<double>(drop_indicator_width),
              static_cast<double>(drop_indicator_height));
  cairo_set_source_rgba(cairo_context, 1.0f, 1.0f, 1.0f, 0.0f);
  cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
  cairo_paint(cairo_context);
  cairo_destroy(cairo_context);
}

// Sets the shape mask for the container window to emulate a transparent
// container window.  This is used if compositing is not available for the
// screen.
void TabStripGtk::DropInfo::SetContainerShapeMask() {
  // Create a 1bpp bitmap the size of |container|.
  GdkPixmap* pixmap = gdk_pixmap_new(NULL,
                                     drop_indicator_width,
                                     drop_indicator_height, 1);
  cairo_t* cairo_context = gdk_cairo_create(GDK_DRAWABLE(pixmap));

  // Set the transparency.
  cairo_set_source_rgba(cairo_context, 1, 1, 1, 0);

  // Blit the rendered bitmap into a pixmap.  Any pixel set in the pixmap will
  // be opaque in the container window.
  cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE);
  // We don't use CairoCachedSurface::SetSource() here because we're not
  // rendering on a display server.
  gdk_cairo_set_source_pixbuf(cairo_context, drop_arrow->ToGdkPixbuf(), 0, 0);
  cairo_paint(cairo_context);
  cairo_destroy(cairo_context);

  // Set the shape mask.
  GdkWindow* gdk_window = gtk_widget_get_window(container);
  gdk_window_shape_combine_mask(gdk_window, pixmap, 0, 0);
  g_object_unref(pixmap);
}

void TabStripGtk::DropInfo::CreateContainer() {
  container = gtk_window_new(GTK_WINDOW_POPUP);
  SetContainerColorMap();
  gtk_widget_set_app_paintable(container, TRUE);
  g_signal_connect(container, "expose-event",
                   G_CALLBACK(OnExposeEventThunk), this);
  gtk_widget_add_events(container, GDK_STRUCTURE_MASK);
  gtk_window_move(GTK_WINDOW(container), 0, 0);
  gtk_window_resize(GTK_WINDOW(container),
                    drop_indicator_width, drop_indicator_height);
  gtk_widget_show_all(container);
}

void TabStripGtk::DropInfo::DestroyContainer() {
  if (GTK_IS_WIDGET(container))
    gtk_widget_destroy(container);
}

void TabStripGtk::StopAnimation() {
  if (active_animation_.get())
    active_animation_->Stop();
}

// Called from:
// - animation tick
void TabStripGtk::AnimationLayout(double unselected_width) {
  int tab_height = TabGtk::GetStandardSize().height();
  double tab_x = tab_start_x();
  for (int i = 0; i < GetTabCount(); ++i) {
    TabAnimation* animation = active_animation_.get();
    if (animation)
      tab_x += animation->GetGapWidth(i);
    double tab_width = TabAnimation::GetCurrentTabWidth(this, animation, i);
    double end_of_tab = tab_x + tab_width;
    int rounded_tab_x = Round(tab_x);
    TabGtk* tab = GetTabAt(i);
    gfx::Rect bounds(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
                     tab_height);
    SetTabBounds(tab, bounds);
    tab_x = end_of_tab + GetTabHOffset(i + 1);
  }
  LayoutNewTabButton(tab_x, unselected_width);
}

void TabStripGtk::StartInsertTabAnimation(int index) {
  // The TabStrip can now use its entire width to lay out Tabs.
  available_width_for_tabs_ = -1;
  StopAnimation();
  active_animation_.reset(new InsertTabAnimation(this, index));
  active_animation_->Start();
}

void TabStripGtk::StartRemoveTabAnimation(int index, WebContents* contents) {
  if (active_animation_.get()) {
    // Some animations (e.g. MoveTabAnimation) cause there to be a Layout when
    // they're completed (which includes canceled). Since |tab_data_| is now
    // inconsistent with TabStripModel, doing this Layout will crash now, so
    // we ask the MoveTabAnimation to skip its Layout (the state will be
    // corrected by the RemoveTabAnimation we're about to initiate).
    active_animation_->set_layout_on_completion(false);
    active_animation_->Stop();
  }

  active_animation_.reset(new RemoveTabAnimation(this, index, contents));
  active_animation_->Start();
}

void TabStripGtk::StartMoveTabAnimation(int from_index, int to_index) {
  StopAnimation();
  active_animation_.reset(new MoveTabAnimation(this, from_index, to_index));
  active_animation_->Start();
}

void TabStripGtk::StartResizeLayoutAnimation() {
  StopAnimation();
  active_animation_.reset(new ResizeLayoutAnimation(this));
  active_animation_->Start();
}

void TabStripGtk::StartMiniTabAnimation(int index) {
  StopAnimation();
  active_animation_.reset(new MiniTabAnimation(this, index));
  active_animation_->Start();
}

void TabStripGtk::StartMiniMoveTabAnimation(int from_index,
                                            int to_index,
                                            const gfx::Rect& start_bounds) {
  StopAnimation();
  active_animation_.reset(
      new MiniMoveAnimation(this, from_index, to_index, start_bounds));
  active_animation_->Start();
}

void TabStripGtk::FinishAnimation(TabStripGtk::TabAnimation* animation,
                                  bool layout) {
  active_animation_.reset(NULL);

  // Reset the animation state of each tab.
  for (int i = 0, count = GetTabCount(); i < count; ++i)
    GetTabAt(i)->set_animating_mini_change(false);

  if (layout)
    Layout();
}

void TabStripGtk::OnMap(GtkWidget* widget) {
  ReStack();
}

gboolean TabStripGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::OnExpose");

  if (gdk_region_empty(event->region))
    return TRUE;

  // If we're only repainting favicons, optimize the paint path and only draw
  // the favicons.
  GdkRectangle* rects;
  gint num_rects;
  gdk_region_get_rectangles(event->region, &rects, &num_rects);
  qsort(rects, num_rects, sizeof(GdkRectangle), CompareGdkRectangles);
  std::vector<int> tabs_to_repaint;
  if (!IsDragSessionActive() &&
      CanPaintOnlyFavicons(rects, num_rects, &tabs_to_repaint)) {
    PaintOnlyFavicons(event, tabs_to_repaint);
    g_free(rects);
    return TRUE;
  }
  g_free(rects);

  // Ideally we'd like to only draw what's needed in the damage rect, but the
  // tab widgets overlap each other. To get the proper visual look, we need to
  // draw tabs from the rightmost to the leftmost tab. So if we have a dirty
  // rectangle in the center of the tabstrip, we'll have to draw all the tabs
  // to the left of it.
  //
  // TODO(erg): Figure out why we can't have clip rects that don't start from
  // x=0. jhawkins had a big comment here about how painting on one widget will
  // cause an expose-event to be sent to the widgets underneath, but that
  // should still obey clip rects, but doesn't seem to.
  if (active_animation_.get() || drag_controller_.get()) {
    // If we have an active animation or the tab is being dragged, no matter
    // what GTK tells us our dirty rectangles are, we need to redraw the entire
    // tabstrip.
    event->area.width = bounds_.width();
  } else {
    // Expand whatever dirty rectangle we were given to the area from the
    // leftmost edge of the tabstrip to the rightmost edge of the dirty
    // rectangle given.
    //
    // Doing this frees up CPU when redrawing the tabstrip with throbbing
    // tabs. The most likely tabs to throb are pinned or minitabs which live on
    // the very leftmost of the tabstrip.
    event->area.width += event->area.x;
  }

  event->area.x = 0;
  event->area.y = 0;
  event->area.height = bounds_.height();
  gdk_region_union_with_rect(event->region, &event->area);

  // Paint the New Tab button.
  gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
      newtab_button_->widget(), event);

  // Paint the tabs in reverse order, so they stack to the left.
  TabGtk* selected_tab = NULL;
  int tab_count = GetTabCount();
  for (int i = tab_count - 1; i >= 0; --i) {
    TabGtk* tab = GetTabAt(i);
    // We must ask the _Tab's_ model, not ourselves, because in some situations
    // the model will be different to this object, e.g. when a Tab is being
    // removed after its WebContents has been destroyed.
    if (!tab->IsActive()) {
      gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
                                     tab->widget(), event);
    } else {
      selected_tab = tab;
    }
  }

  // Paint the selected tab last, so it overlaps all the others.
  if (selected_tab) {
    gtk_container_propagate_expose(GTK_CONTAINER(tabstrip_.get()),
                                   selected_tab->widget(), event);
  }

  return TRUE;
}

void TabStripGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {
  TRACE_EVENT0("ui::gtk", "TabStripGtk::OnSizeAllocate");

  gfx::Rect bounds = gfx::Rect(allocation->x, allocation->y,
      allocation->width, allocation->height);

  // Nothing to do if the bounds are the same.  If we don't catch this, we'll
  // get an infinite loop of size-allocate signals.
  if (bounds_ == bounds)
    return;

  SetBounds(bounds);

  // No tabs, nothing to layout.  This happens when a browser window is created
  // and shown before tabs are added (as in a popup window).
  if (GetTabCount() == 0)
    return;

  // When there is only one tab, Layout() so we don't animate it. With more
  // tabs, we should always attempt a resize unless we already have one coming
  // up in our message loop.
  if (GetTabCount() == 1) {
    Layout();
  } else if (!layout_factory_.HasWeakPtrs()) {
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&TabStripGtk::Layout, layout_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(kLayoutAfterSizeAllocateMs));
  }
}

gboolean TabStripGtk::OnDragMotion(GtkWidget* widget, GdkDragContext* context,
                                   gint x, gint y, guint time) {
  UpdateDropIndex(context, x, y);
  return TRUE;
}

gboolean TabStripGtk::OnDragDrop(GtkWidget* widget, GdkDragContext* context,
                                 gint x, gint y, guint time) {
  if (!drop_info_.get())
    return FALSE;

  GdkAtom target = gtk_drag_dest_find_target(widget, context, NULL);
  if (target != GDK_NONE)
    gtk_drag_finish(context, FALSE, FALSE, time);
  else
    gtk_drag_get_data(widget, context, target, time);

  return TRUE;
}

gboolean TabStripGtk::OnDragLeave(GtkWidget* widget, GdkDragContext* context,
                                  guint time) {
  // Destroy the drop indicator.
  drop_info_->DestroyContainer();

  // Cancel any pending tab transition.
  hover_tab_selector_.CancelTabTransition();

  return FALSE;
}

gboolean TabStripGtk::OnDragDataReceived(GtkWidget* widget,
                                         GdkDragContext* context,
                                         gint x, gint y,
                                         GtkSelectionData* data,
                                         guint info, guint time) {
  bool success = false;

  if (info == ui::TEXT_URI_LIST ||
      info == ui::NETSCAPE_URL ||
      info == ui::TEXT_PLAIN) {
    success = CompleteDrop(gtk_selection_data_get_data(data),
                           info == ui::TEXT_PLAIN);
  }

  gtk_drag_finish(context, success, FALSE, time);
  return TRUE;
}

void TabStripGtk::OnNewTabClicked(GtkWidget* widget) {
  GdkEvent* event = gtk_get_current_event();
  DCHECK_EQ(event->type, GDK_BUTTON_RELEASE);
  int mouse_button = event->button.button;
  gdk_event_free(event);

  switch (mouse_button) {
    case 1:
      content::RecordAction(UserMetricsAction("NewTab_Button"));
      UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
                                TabStripModel::NEW_TAB_ENUM_COUNT);
      model_->delegate()->AddTabAt(GURL(), -1, true);
      break;
    case 2: {
      // On middle-click, try to parse the PRIMARY selection as a URL and load
      // it instead of creating a blank page.
      GURL url;
      if (!gtk_util::URLFromPrimarySelection(model_->profile(), &url))
        return;

      Browser* browser = window_->browser();
      DCHECK(browser);
      chrome::AddSelectedTabWithURL(
          browser, url, content::PAGE_TRANSITION_TYPED);
      break;
    }
    default:
      NOTREACHED() << "Got click on new tab button with unhandled mouse "
                   << "button " << mouse_button;
  }
}

void TabStripGtk::SetTabBounds(TabGtk* tab, const gfx::Rect& bounds) {
  gfx::Rect bds = bounds;
  bds.set_x(gtk_util::MirroredLeftPointForRect(tabstrip_.get(), bounds));
  tab->SetBounds(bds);
  gtk_fixed_move(GTK_FIXED(tabstrip_.get()), tab->widget(),
                 bds.x(), bds.y());
}

bool TabStripGtk::CanPaintOnlyFavicons(const GdkRectangle* rects,
    int num_rects, std::vector<int>* tabs_to_paint) {
  // |rects| are sorted so we just need to scan from left to right and compare
  // it to the tab favicon positions from left to right.
  int t = 0;
  for (int r = 0; r < num_rects; ++r) {
    while (t < GetTabCount()) {
      TabGtk* tab = GetTabAt(t);
      if (GdkRectMatchesTabFaviconBounds(rects[r], tab) &&
          tab->ShouldShowIcon()) {
        tabs_to_paint->push_back(t);
        ++t;
        break;
      }
      ++t;
    }
  }
  return static_cast<int>(tabs_to_paint->size()) == num_rects;
}

void TabStripGtk::PaintOnlyFavicons(GdkEventExpose* event,
                                    const std::vector<int>& tabs_to_paint) {
  cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
  for (size_t i = 0; i < tabs_to_paint.size(); ++i) {
    cairo_save(cr);
    GetTabAt(tabs_to_paint[i])->PaintFaviconArea(tabstrip_.get(), cr);
    cairo_restore(cr);
  }

  cairo_destroy(cr);
}

CustomDrawButton* TabStripGtk::MakeNewTabButton() {
  CustomDrawButton* button = new CustomDrawButton(IDR_NEWTAB_BUTTON,
      IDR_NEWTAB_BUTTON_P, IDR_NEWTAB_BUTTON_H, 0);

  gtk_widget_set_tooltip_text(button->widget(),
      l10n_util::GetStringUTF8(IDS_TOOLTIP_NEW_TAB).c_str());

  // Let the middle mouse button initiate clicks as well.
  gtk_util::SetButtonTriggersNavigation(button->widget());
  g_signal_connect(button->widget(), "clicked",
                   G_CALLBACK(OnNewTabClickedThunk), this);
  gtk_widget_set_can_focus(button->widget(), FALSE);
  gtk_fixed_put(GTK_FIXED(tabstrip_.get()), button->widget(), 0, 0);

  return button;
}

void TabStripGtk::SetNewTabButtonBackground() {
  SkColor color = theme_service_->GetColor(
      ThemeProperties::COLOR_BUTTON_BACKGROUND);
  SkBitmap background = theme_service_->GetImageNamed(
      IDR_THEME_WINDOW_CONTROL_BACKGROUND).AsBitmap();
  SkBitmap mask = theme_service_->GetImageNamed(
      IDR_NEWTAB_BUTTON_MASK).AsBitmap();
  newtab_button_->SetBackground(color, background, mask);
}

/* [<][>][^][v][top][bottom][index][help] */