tab_strip.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Use of this source code is governed by a BSD-style license that can be
3c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// found in the LICENSE file.
4c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
5c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/tab_strip.h"
6c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
7c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#if defined(OS_WIN)
8c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include <windowsx.h>
9c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#endif
10c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
11c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include <algorithm>
12c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include <iterator>
13c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include <string>
14c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include <vector>
15c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
16c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "base/compiler_specific.h"
17c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "base/metrics/histogram.h"
18c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "base/stl_util.h"
19c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "base/strings/utf_string_conversions.h"
20c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/defaults.h"
21c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/host_desktop.h"
22c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/tabs/tab_strip_model.h"
23c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/view_ids.h"
24c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
25c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/tab.h"
26c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
27c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
28c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
29c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/browser/ui/views/touch_uma/touch_uma.h"
30c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "chrome/grit/generated_resources.h"
31c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "content/public/browser/user_metrics.h"
32c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "grit/theme_resources.h"
33c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/accessibility/ax_view_state.h"
34c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/base/default_theme_provider.h"
35c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/base/dragdrop/drag_drop_types.h"
36c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/base/l10n/l10n_util.h"
37c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/base/models/list_selection_model.h"
38c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/base/resource/resource_bundle.h"
39c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/animation/animation_container.h"
40c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/animation/throb_animation.h"
41c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/canvas.h"
42c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/display.h"
43c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/image/image_skia.h"
44c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/image/image_skia_operations.h"
45c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/path.h"
46c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/rect_conversions.h"
47c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/screen.h"
48c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/size.h"
49c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/skia_util.h"
50c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/controls/image_view.h"
51c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/masked_targeter_delegate.h"
52c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/mouse_watcher_view_host.h"
53c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/rect_based_targeting_utils.h"
54c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/view_model_utils.h"
55c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/view_targeter.h"
56c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/widget/root_view.h"
57c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/widget/widget.h"
58c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/window/non_client_view.h"
59c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
60c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#if defined(OS_WIN)
61c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/gfx/win/hwnd_util.h"
62c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/widget/monitor_win.h"
63c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#include "ui/views/win/hwnd_util.h"
64c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#endif
65c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
66c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathusing base::UserMetricsAction;
67c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathusing ui::DropTargetEvent;
68c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
69c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathnamespace {
70c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
71c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kTabStripAnimationVSlop = 40;
72c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Inactive tabs in a native frame are slightly transparent.
73c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kGlassFrameInactiveTabAlpha = 200;
74c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// If there are multiple tabs selected then make non-selected inactive tabs
75c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// even more transparent.
76c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
77c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
78c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Alpha applied to all elements save the selected tabs.
79c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kInactiveTabAndNewTabButtonAlphaAsh = 230;
80c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kInactiveTabAndNewTabButtonAlpha = 255;
81c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
82c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Inverse ratio of the width of a tab edge to the width of the tab. When
83c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// hovering over the left or right edge of a tab, the drop indicator will
84c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// point between tabs.
85c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kTabEdgeRatioInverse = 4;
86c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
87c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Size of the drop indicator.
88c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic int drop_indicator_width;
89c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic int drop_indicator_height;
90c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
91c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic inline int Round(double x) {
92c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  // Why oh why is this not in a standard header?
93c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  return static_cast<int>(floor(x + 0.5));
94c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
95c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
96c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Max number of stacked tabs.
97c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kMaxStackedCount = 4;
98c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
99c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Padding between stacked tabs.
100c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathstatic const int kStackedPadding = 6;
101c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
102c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// See UpdateLayoutTypeFromMouseEvent() for a description of these.
103c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#if !defined(USE_ASH)
104c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kMouseMoveTimeMS = 200;
105c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kMouseMoveCountBeforeConsiderReal = 3;
106c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath#endif
107c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
108c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Amount of time we delay before resizing after a close from a touch.
109c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kTouchResizeLayoutTimeMS = 2000;
110c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
111c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Amount the left edge of a tab is offset from the rectangle of the tab's
112c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// favicon/title/close box.  Related to the width of IDR_TAB_ACTIVE_LEFT.
113c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Affects the size of the "V" between adjacent tabs.
114c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kTabHorizontalOffset = -26;
115c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
116c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Amount to adjust the clip by when the tab is stacked before the active index.
117c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kStackedTabLeftClip = 20;
118c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
119c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Amount to adjust the clip by when the tab is stacked after the active index.
120c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathconst int kStackedTabRightClip = 20;
121c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
122c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathbase::string16 GetClipboardText() {
123c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
124c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    return base::string16();
125c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
126c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  CHECK(clipboard);
127c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  base::string16 clipboard_text;
128c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
129c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  return clipboard_text;
130c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
131c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
132c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Animation delegate used for any automatic tab movement.  Hides the tab if it
133c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// is not fully visible within the tabstrip area, to prevent overflow clipping.
134c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathclass TabAnimationDelegate : public gfx::AnimationDelegate {
135c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath public:
136c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  TabAnimationDelegate(TabStrip* tab_strip, Tab* tab);
137c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual ~TabAnimationDelegate();
138c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
139c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
140c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
141c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath protected:
142c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  TabStrip* tab_strip() { return tab_strip_; }
143c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  Tab* tab() { return tab_; }
144c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
145c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath private:
146c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  TabStrip* const tab_strip_;
147c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  Tab* const tab_;
148c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
149c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate);
150c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath};
151c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
152c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan KamathTabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab)
153c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    : tab_strip_(tab_strip),
154c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath      tab_(tab) {
155c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
156c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
157c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan KamathTabAnimationDelegate::~TabAnimationDelegate() {
158c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
159c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
160c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathvoid TabAnimationDelegate::AnimationProgressed(
161c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    const gfx::Animation* animation) {
162c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_));
163c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
164c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
165c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Animation delegate used when a dragged tab is released. When done sets the
166c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// dragging state to false.
167c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathclass ResetDraggingStateDelegate : public TabAnimationDelegate {
168c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath public:
169c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab);
170c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual ~ResetDraggingStateDelegate();
171c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
172c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
173c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE;
174c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
175c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath private:
176c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
177c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath};
178c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
179c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan KamathResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip,
180c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath                                                       Tab* tab)
181c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    : TabAnimationDelegate(tab_strip, tab) {
182c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
183c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
184c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan KamathResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
185c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
186c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
187c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathvoid ResetDraggingStateDelegate::AnimationEnded(
188c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    const gfx::Animation* animation) {
1897faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  tab()->set_dragging(false);
1907faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  AnimationProgressed(animation);  // Forces tab visibility to update.
1917faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez}
1927faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez
193c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathvoid ResetDraggingStateDelegate::AnimationCanceled(
194c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    const gfx::Animation* animation) {
195c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  AnimationEnded(animation);
1967faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez}
1977faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez
1987faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez// If |dest| contains the point |point_in_source| the event handler from |dest|
1997faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez// is returned. Otherwise NULL is returned.
200c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathviews::View* ConvertPointToViewAndGetEventHandler(
201c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    views::View* source,
202c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    views::View* dest,
203c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    const gfx::Point& point_in_source) {
204c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  gfx::Point dest_point(point_in_source);
2057faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  views::View::ConvertPointToTarget(source, dest, &dest_point);
2067faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  return dest->HitTestPoint(dest_point) ?
2077faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez      dest->GetEventHandlerForPoint(dest_point) : NULL;
2087faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez}
209c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
210c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
211c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// should return NULL if it does not contain the point.
212c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathviews::View* ConvertPointToViewAndGetTooltipHandler(
213c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    views::View* source,
2147faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez    views::View* dest,
2157faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez    const gfx::Point& point_in_source) {
2167faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  gfx::Point dest_point(point_in_source);
2177faaa9f3f0df9d23790277834d426c3d992ac3baCarlos Hernandez  views::View::ConvertPointToTarget(source, dest, &dest_point);
218c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  return dest->GetTooltipHandlerForPoint(dest_point);
219c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
220c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
221c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan KamathTabDragController::EventSource EventSourceFromEvent(
222c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath    const ui::LocatedEvent& event) {
223c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
224c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath      TabDragController::EVENT_SOURCE_MOUSE;
225c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}
226c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
227c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath}  // namespace
228c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
229c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath///////////////////////////////////////////////////////////////////////////////
230c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath// NewTabButton
231c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath//
232c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath//  A subclass of button that hit-tests to the shape of the new tab button and
233c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath//  does custom drawing.
234c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
235c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamathclass NewTabButton : public views::ImageButton,
236c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath                     public views::MaskedTargeterDelegate {
237c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath public:
238c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
239c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  virtual ~NewTabButton();
240c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath
241c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  // Set the background offset used to match the background image to the frame
242c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  // image.
243c981c48f5bc9aefeffc0bcb0cc3934c2fae179ddNarayan Kamath  void set_background_offset(const gfx::Point& offset) {
244    background_offset_ = offset;
245  }
246
247 protected:
248  // views::View:
249#if defined(OS_WIN)
250  virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
251#endif
252  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
253
254  // ui::EventHandler:
255  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
256
257 private:
258  // views::MaskedTargeterDelegate:
259  virtual bool GetHitTestMask(gfx::Path* mask) const OVERRIDE;
260
261  bool ShouldWindowContentsBeTransparent() const;
262  gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
263                                    float scale) const;
264  gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
265                                  float scale) const;
266  gfx::ImageSkia GetImageForScale(float scale) const;
267
268  // Tab strip that contains this button.
269  TabStrip* tab_strip_;
270
271  // The offset used to paint the background image.
272  gfx::Point background_offset_;
273
274  // were we destroyed?
275  bool* destroyed_;
276
277  DISALLOW_COPY_AND_ASSIGN(NewTabButton);
278};
279
280NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
281    : views::ImageButton(listener),
282      tab_strip_(tab_strip),
283      destroyed_(NULL) {
284#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
285  set_triggerable_event_flags(triggerable_event_flags() |
286                              ui::EF_MIDDLE_MOUSE_BUTTON);
287#endif
288}
289
290NewTabButton::~NewTabButton() {
291  if (destroyed_)
292    *destroyed_ = true;
293}
294
295#if defined(OS_WIN)
296void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
297  if (event.IsOnlyRightMouseButton()) {
298    gfx::Point point = event.location();
299    views::View::ConvertPointToScreen(this, &point);
300    bool destroyed = false;
301    destroyed_ = &destroyed;
302    gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
303    if (destroyed)
304      return;
305
306    destroyed_ = NULL;
307    SetState(views::CustomButton::STATE_NORMAL);
308    return;
309  }
310  views::ImageButton::OnMouseReleased(event);
311}
312#endif
313
314void NewTabButton::OnPaint(gfx::Canvas* canvas) {
315  gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
316  canvas->DrawImageInt(image, 0, height() - image.height());
317}
318
319void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
320  // Consume all gesture events here so that the parent (Tab) does not
321  // start consuming gestures.
322  views::ImageButton::OnGestureEvent(event);
323  event->SetHandled();
324}
325
326bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
327  DCHECK(mask);
328
329  // When the button is sized to the top of the tab strip, we want the hit
330  // test mask to be defined as the complete (rectangular) bounds of the
331  // button.
332  if (tab_strip_->SizeTabButtonToTopOfTabStrip()) {
333    gfx::Rect button_bounds(GetContentsBounds());
334    button_bounds.set_x(GetMirroredXForRect(button_bounds));
335    mask->addRect(RectToSkRect(button_bounds));
336    return true;
337  }
338
339  SkScalar w = SkIntToScalar(width());
340  SkScalar v_offset = SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset);
341
342  // These values are defined by the shape of the new tab image. Should that
343  // image ever change, these values will need to be updated. They're so
344  // custom it's not really worth defining constants for.
345  // These values are correct for regular and USE_ASH versions of the image.
346  mask->moveTo(0, v_offset + 1);
347  mask->lineTo(w - 7, v_offset + 1);
348  mask->lineTo(w - 4, v_offset + 4);
349  mask->lineTo(w, v_offset + 16);
350  mask->lineTo(w - 1, v_offset + 17);
351  mask->lineTo(7, v_offset + 17);
352  mask->lineTo(4, v_offset + 13);
353  mask->lineTo(0, v_offset + 1);
354  mask->close();
355
356  return true;
357}
358
359bool NewTabButton::ShouldWindowContentsBeTransparent() const {
360  return GetWidget() &&
361         GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
362}
363
364gfx::ImageSkia NewTabButton::GetBackgroundImage(
365    views::CustomButton::ButtonState state,
366    float scale) const {
367  int background_id = 0;
368  if (ShouldWindowContentsBeTransparent()) {
369    background_id = IDR_THEME_TAB_BACKGROUND_V;
370  } else if (tab_strip_->controller()->IsIncognito()) {
371    background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
372  } else {
373    background_id = IDR_THEME_TAB_BACKGROUND;
374  }
375
376  int alpha = 0;
377  switch (state) {
378    case views::CustomButton::STATE_NORMAL:
379    case views::CustomButton::STATE_HOVERED:
380      alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
381                                                  : 255;
382      break;
383    case views::CustomButton::STATE_PRESSED:
384      alpha = 145;
385      break;
386    default:
387      NOTREACHED();
388      break;
389  }
390
391  gfx::ImageSkia* mask =
392      GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
393  int height = mask->height();
394  int width = mask->width();
395  // The canvas and mask has to use the same scale factor.
396  if (!mask->HasRepresentation(scale))
397    scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
398
399  gfx::Canvas canvas(gfx::Size(width, height), scale, false);
400
401  // For custom images the background starts at the top of the tab strip.
402  // Otherwise the background starts at the top of the frame.
403  gfx::ImageSkia* background =
404      GetThemeProvider()->GetImageSkiaNamed(background_id);
405  int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
406      0 : background_offset_.y();
407
408  // The new tab background is mirrored in RTL mode, but the theme background
409  // should never be mirrored. Mirror it here to compensate.
410  float x_scale = 1.0f;
411  int x = GetMirroredX() + background_offset_.x();
412  if (base::i18n::IsRTL()) {
413    x_scale = -1.0f;
414    // Offset by |width| such that the same region is painted as if there was no
415    // flip.
416    x += width;
417  }
418  canvas.TileImageInt(*background, x,
419                      TabStrip::kNewTabButtonVerticalOffset + offset_y,
420                      x_scale, 1.0f, 0, 0, width, height);
421
422  if (alpha != 255) {
423    SkPaint paint;
424    paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
425    paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
426    paint.setStyle(SkPaint::kFill_Style);
427    canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
428  }
429
430  // White highlight on hover.
431  if (state == views::CustomButton::STATE_HOVERED)
432    canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
433
434  return gfx::ImageSkiaOperations::CreateMaskedImage(
435      gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
436}
437
438gfx::ImageSkia NewTabButton::GetImageForState(
439    views::CustomButton::ButtonState state,
440    float scale) const {
441  const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
442        IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
443  gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
444
445  gfx::Canvas canvas(
446      gfx::Size(overlay->width(), overlay->height()),
447      scale,
448      false);
449  canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
450
451  // Draw the button border with a slight alpha.
452  const int kGlassFrameOverlayAlpha = 178;
453  const int kOpaqueFrameOverlayAlpha = 230;
454  uint8 alpha = ShouldWindowContentsBeTransparent() ?
455      kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha;
456  canvas.DrawImageInt(*overlay, 0, 0, alpha);
457
458  return gfx::ImageSkia(canvas.ExtractImageRep());
459}
460
461gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
462  if (!hover_animation_->is_animating())
463    return GetImageForState(state(), scale);
464  return gfx::ImageSkiaOperations::CreateBlendedImage(
465      GetImageForState(views::CustomButton::STATE_NORMAL, scale),
466      GetImageForState(views::CustomButton::STATE_HOVERED, scale),
467      hover_animation_->GetCurrentValue());
468}
469
470///////////////////////////////////////////////////////////////////////////////
471// TabStrip::RemoveTabDelegate
472//
473// AnimationDelegate used when removing a tab. Does the necessary cleanup when
474// done.
475class TabStrip::RemoveTabDelegate : public TabAnimationDelegate {
476 public:
477  RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
478
479  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
480  virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE;
481
482 private:
483  DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
484};
485
486TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
487                                               Tab* tab)
488    : TabAnimationDelegate(tab_strip, tab) {
489}
490
491void TabStrip::RemoveTabDelegate::AnimationEnded(
492    const gfx::Animation* animation) {
493  DCHECK(tab()->closing());
494  tab_strip()->RemoveAndDeleteTab(tab());
495
496  // Send the Container a message to simulate a mouse moved event at the current
497  // mouse position. This tickles the Tab the mouse is currently over to show
498  // the "hot" state of the close button.  Note that this is not required (and
499  // indeed may crash!) for removes spawned by non-mouse closes and
500  // drag-detaches.
501  if (!tab_strip()->IsDragSessionActive() &&
502      tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
503    // The widget can apparently be null during shutdown.
504    views::Widget* widget = tab_strip()->GetWidget();
505    if (widget)
506      widget->SynthesizeMouseMoveEvent();
507  }
508}
509
510void TabStrip::RemoveTabDelegate::AnimationCanceled(
511    const gfx::Animation* animation) {
512  AnimationEnded(animation);
513}
514
515///////////////////////////////////////////////////////////////////////////////
516// TabStrip, public:
517
518// static
519const char TabStrip::kViewClassName[] = "TabStrip";
520const int TabStrip::kNewTabButtonHorizontalOffset = -11;
521const int TabStrip::kNewTabButtonVerticalOffset = 7;
522const int TabStrip::kMiniToNonMiniGap = 3;
523const int TabStrip::kNewTabButtonAssetWidth = 34;
524const int TabStrip::kNewTabButtonAssetHeight = 18;
525
526TabStrip::TabStrip(TabStripController* controller)
527    : controller_(controller),
528      newtab_button_(NULL),
529      current_unselected_width_(Tab::GetStandardSize().width()),
530      current_selected_width_(Tab::GetStandardSize().width()),
531      available_width_for_tabs_(-1),
532      in_tab_close_(false),
533      animation_container_(new gfx::AnimationContainer()),
534      bounds_animator_(this),
535      stacked_layout_(false),
536      adjust_layout_(false),
537      reset_to_shrink_on_exit_(false),
538      mouse_move_count_(0),
539      immersive_style_(false) {
540  Init();
541  SetEventTargeter(
542      scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
543}
544
545TabStrip::~TabStrip() {
546  FOR_EACH_OBSERVER(TabStripObserver, observers_,
547                    TabStripDeleted(this));
548
549  // The animations may reference the tabs. Shut down the animation before we
550  // delete the tabs.
551  StopAnimating(false);
552
553  DestroyDragController();
554
555  // Make sure we unhook ourselves as a message loop observer so that we don't
556  // crash in the case where the user closes the window after closing a tab
557  // but before moving the mouse.
558  RemoveMessageLoopObserver();
559
560  // The children (tabs) may callback to us from their destructor. Delete them
561  // so that if they call back we aren't in a weird state.
562  RemoveAllChildViews(true);
563}
564
565void TabStrip::AddObserver(TabStripObserver* observer) {
566  observers_.AddObserver(observer);
567}
568
569void TabStrip::RemoveObserver(TabStripObserver* observer) {
570  observers_.RemoveObserver(observer);
571}
572
573void TabStrip::SetStackedLayout(bool stacked_layout) {
574  if (stacked_layout == stacked_layout_)
575    return;
576
577  const int active_index = controller_->GetActiveIndex();
578  int active_center = 0;
579  if (active_index != -1) {
580    active_center = ideal_bounds(active_index).x() +
581        ideal_bounds(active_index).width() / 2;
582  }
583  stacked_layout_ = stacked_layout;
584  SetResetToShrinkOnExit(false);
585  SwapLayoutIfNecessary();
586  // When transitioning to stacked try to keep the active tab centered.
587  if (touch_layout_ && active_index != -1) {
588    touch_layout_->SetActiveTabLocation(
589        active_center - ideal_bounds(active_index).width() / 2);
590    AnimateToIdealBounds();
591  }
592}
593
594gfx::Rect TabStrip::GetNewTabButtonBounds() {
595  return newtab_button_->bounds();
596}
597
598bool TabStrip::SizeTabButtonToTopOfTabStrip() {
599  // Extend the button to the screen edge in maximized and immersive fullscreen.
600  views::Widget* widget = GetWidget();
601  return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
602      (widget && (widget->IsMaximized() || widget->IsFullscreen()));
603}
604
605void TabStrip::StartHighlight(int model_index) {
606  tab_at(model_index)->StartPulse();
607}
608
609void TabStrip::StopAllHighlighting() {
610  for (int i = 0; i < tab_count(); ++i)
611    tab_at(i)->StopPulse();
612}
613
614void TabStrip::AddTabAt(int model_index,
615                        const TabRendererData& data,
616                        bool is_active) {
617  // Stop dragging when a new tab is added and dragging a window. Doing
618  // otherwise results in a confusing state if the user attempts to reattach. We
619  // could allow this and make TabDragController update itself during the add,
620  // but this comes up infrequently enough that it's not work the complexity.
621  if (drag_controller_.get() && !drag_controller_->is_mutating() &&
622      drag_controller_->is_dragging_window()) {
623    EndDrag(END_DRAG_COMPLETE);
624  }
625  Tab* tab = CreateTab();
626  tab->SetData(data);
627  UpdateTabsClosingMap(model_index, 1);
628  tabs_.Add(tab, model_index);
629  AddChildView(tab);
630
631  if (touch_layout_) {
632    GenerateIdealBoundsForMiniTabs(NULL);
633    int add_types = 0;
634    if (data.mini)
635      add_types |= StackedTabStripLayout::kAddTypeMini;
636    if (is_active)
637      add_types |= StackedTabStripLayout::kAddTypeActive;
638    touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
639  }
640
641  // Don't animate the first tab, it looks weird, and don't animate anything
642  // if the containing window isn't visible yet.
643  if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
644    StartInsertTabAnimation(model_index);
645  else
646    DoLayout();
647
648  SwapLayoutIfNecessary();
649
650  FOR_EACH_OBSERVER(TabStripObserver, observers_,
651                    TabStripAddedTabAt(this, model_index));
652}
653
654void TabStrip::MoveTab(int from_model_index,
655                       int to_model_index,
656                       const TabRendererData& data) {
657  DCHECK_GT(tabs_.view_size(), 0);
658  const Tab* last_tab = GetLastVisibleTab();
659  tab_at(from_model_index)->SetData(data);
660  if (touch_layout_) {
661    tabs_.MoveViewOnly(from_model_index, to_model_index);
662    int mini_count = 0;
663    GenerateIdealBoundsForMiniTabs(&mini_count);
664    touch_layout_->MoveTab(
665        from_model_index, to_model_index, controller_->GetActiveIndex(),
666        GetStartXForNormalTabs(), mini_count);
667  } else {
668    tabs_.Move(from_model_index, to_model_index);
669  }
670  StartMoveTabAnimation();
671  if (TabDragController::IsAttachedTo(this) &&
672      (last_tab != GetLastVisibleTab() || last_tab->dragging())) {
673    newtab_button_->SetVisible(false);
674  }
675  SwapLayoutIfNecessary();
676
677  FOR_EACH_OBSERVER(TabStripObserver, observers_,
678                    TabStripMovedTab(this, from_model_index, to_model_index));
679}
680
681void TabStrip::RemoveTabAt(int model_index) {
682  if (touch_layout_) {
683    Tab* tab = tab_at(model_index);
684    tab->set_closing(true);
685    int old_x = tabs_.ideal_bounds(model_index).x();
686    // We still need to paint the tab until we actually remove it. Put it in
687    // tabs_closing_map_ so we can find it.
688    RemoveTabFromViewModel(model_index);
689    touch_layout_->RemoveTab(model_index, GenerateIdealBoundsForMiniTabs(NULL),
690                             old_x);
691    ScheduleRemoveTabAnimation(tab);
692  } else if (in_tab_close_ && model_index != GetModelCount()) {
693    StartMouseInitiatedRemoveTabAnimation(model_index);
694  } else {
695    StartRemoveTabAnimation(model_index);
696  }
697  SwapLayoutIfNecessary();
698
699  FOR_EACH_OBSERVER(TabStripObserver, observers_,
700                    TabStripRemovedTabAt(this, model_index));
701}
702
703void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
704  Tab* tab = tab_at(model_index);
705  bool mini_state_changed = tab->data().mini != data.mini;
706  tab->SetData(data);
707
708  if (mini_state_changed) {
709    if (touch_layout_) {
710      int mini_tab_count = 0;
711      int start_x = GenerateIdealBoundsForMiniTabs(&mini_tab_count);
712      touch_layout_->SetXAndMiniCount(start_x, mini_tab_count);
713    }
714    if (GetWidget() && GetWidget()->IsVisible())
715      StartMiniTabAnimation();
716    else
717      DoLayout();
718  }
719  SwapLayoutIfNecessary();
720}
721
722bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
723  // Detached tabs should always be invisible (as they close).
724  if (tab->detached())
725    return false;
726
727  // When stacking tabs, all tabs should always be visible.
728  if (stacked_layout_)
729    return true;
730
731  // If the tab is currently clipped, it shouldn't be visible.  Note that we
732  // allow dragged tabs to draw over the "New Tab button" region as well,
733  // because either the New Tab button will be hidden, or the dragged tabs will
734  // be animating back to their normal positions and we don't want to hide them
735  // in the New Tab button region in case they re-appear after leaving it.
736  // (This prevents flickeriness.)  We never draw non-dragged tabs in New Tab
737  // button area, even when the button is invisible, so that they don't appear
738  // to "pop in" when the button disappears.
739  // TODO: Probably doesn't work for RTL
740  int right_edge = tab->bounds().right();
741  const int visible_width = tab->dragging() ? width() : tab_area_width();
742  if (right_edge > visible_width)
743    return false;
744
745  // Non-clipped dragging tabs should always be visible.
746  if (tab->dragging())
747    return true;
748
749  // Let all non-clipped closing tabs be visible.  These will probably finish
750  // closing before the user changes the active tab, so there's little reason to
751  // try and make the more complex logic below apply.
752  if (tab->closing())
753    return true;
754
755  // Now we need to check whether the tab isn't currently clipped, but could
756  // become clipped if we changed the active tab, widening either this tab or
757  // the tabstrip portion before it.
758
759  // Mini tabs don't change size when activated, so any tab in the mini tab
760  // region is safe.
761  if (tab->data().mini)
762    return true;
763
764  // If the active tab is on or before this tab, we're safe.
765  if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab))
766    return true;
767
768  // We need to check what would happen if the active tab were to move to this
769  // tab or before.
770  return (right_edge + current_selected_width_ - current_unselected_width_) <=
771      tab_area_width();
772}
773
774void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
775  if (!in_tab_close_ && IsAnimating()) {
776    // Cancel any current animations. We do this as remove uses the current
777    // ideal bounds and we need to know ideal bounds is in a good state.
778    StopAnimating(true);
779  }
780
781  if (!GetWidget())
782    return;
783
784  int model_count = GetModelCount();
785  if (model_count > 1 && model_index != model_count - 1) {
786    // The user is about to close a tab other than the last tab. Set
787    // available_width_for_tabs_ so that if we do a layout we don't position a
788    // tab past the end of the second to last tab. We do this so that as the
789    // user closes tabs with the mouse a tab continues to fall under the mouse.
790    Tab* last_tab = tab_at(model_count - 1);
791    Tab* tab_being_removed = tab_at(model_index);
792    available_width_for_tabs_ = last_tab->x() + last_tab->width() -
793        tab_being_removed->width() - kTabHorizontalOffset;
794    if (model_index == 0 && tab_being_removed->data().mini &&
795        !tab_at(1)->data().mini) {
796      available_width_for_tabs_ -= kMiniToNonMiniGap;
797    }
798  }
799
800  in_tab_close_ = true;
801  resize_layout_timer_.Stop();
802  if (source == CLOSE_TAB_FROM_TOUCH) {
803    StartResizeLayoutTabsFromTouchTimer();
804  } else {
805    AddMessageLoopObserver();
806  }
807}
808
809void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
810                            const ui::ListSelectionModel& new_selection) {
811  if (touch_layout_) {
812    touch_layout_->SetActiveIndex(new_selection.active());
813    // Only start an animation if we need to. Otherwise clicking on an
814    // unselected tab and dragging won't work because dragging is only allowed
815    // if not animating.
816    if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
817      AnimateToIdealBounds();
818    SchedulePaint();
819  } else {
820    // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
821    // a different size to the selected ones.
822    bool tiny_tabs = current_unselected_width_ != current_selected_width_;
823    if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
824      DoLayout();
825    } else {
826      SchedulePaint();
827    }
828  }
829
830  // Use STLSetDifference to get the indices of elements newly selected
831  // and no longer selected, since selected_indices() is always sorted.
832  ui::ListSelectionModel::SelectedIndices no_longer_selected =
833      base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
834          old_selection.selected_indices(),
835          new_selection.selected_indices());
836  ui::ListSelectionModel::SelectedIndices newly_selected =
837      base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
838          new_selection.selected_indices(),
839          old_selection.selected_indices());
840
841  // Fire accessibility events that reflect the changes to selection, and
842  // stop the mini tab title animation on tabs no longer selected.
843  for (size_t i = 0; i < no_longer_selected.size(); ++i) {
844    tab_at(no_longer_selected[i])->StopMiniTabTitleAnimation();
845    tab_at(no_longer_selected[i])->NotifyAccessibilityEvent(
846        ui::AX_EVENT_SELECTION_REMOVE, true);
847  }
848  for (size_t i = 0; i < newly_selected.size(); ++i) {
849    tab_at(newly_selected[i])->NotifyAccessibilityEvent(
850        ui::AX_EVENT_SELECTION_ADD, true);
851  }
852  tab_at(new_selection.active())->NotifyAccessibilityEvent(
853      ui::AX_EVENT_SELECTION, true);
854}
855
856void TabStrip::TabTitleChangedNotLoading(int model_index) {
857  Tab* tab = tab_at(model_index);
858  if (tab->data().mini && !tab->IsActive())
859    tab->StartMiniTabTitleAnimation();
860}
861
862int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
863  return tabs_.GetIndexOfView(tab);
864}
865
866int TabStrip::GetModelCount() const {
867  return controller_->GetCount();
868}
869
870bool TabStrip::IsValidModelIndex(int model_index) const {
871  return controller_->IsValidIndex(model_index);
872}
873
874bool TabStrip::IsDragSessionActive() const {
875  return drag_controller_.get() != NULL;
876}
877
878bool TabStrip::IsActiveDropTarget() const {
879  for (int i = 0; i < tab_count(); ++i) {
880    Tab* tab = tab_at(i);
881    if (tab->dragging())
882      return true;
883  }
884  return false;
885}
886
887bool TabStrip::IsTabStripEditable() const {
888  return !IsDragSessionActive() && !IsActiveDropTarget();
889}
890
891bool TabStrip::IsTabStripCloseable() const {
892  return !IsDragSessionActive();
893}
894
895void TabStrip::UpdateLoadingAnimations() {
896  controller_->UpdateLoadingAnimations();
897}
898
899bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
900  return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
901}
902
903bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
904  views::View* v = GetEventHandlerForRect(rect);
905
906  // If there is no control at this location, claim the hit was in the title
907  // bar to get a move action.
908  if (v == this)
909    return true;
910
911  // Check to see if the rect intersects the non-button parts of the new tab
912  // button. The button has a non-rectangular shape, so if it's not in the
913  // visual portions of the button we treat it as a click to the caption.
914  gfx::RectF rect_in_newtab_coords_f(rect);
915  View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
916  gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
917      rect_in_newtab_coords_f);
918  if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
919      !newtab_button_->HitTestRect(rect_in_newtab_coords))
920    return true;
921
922  // All other regions, including the new Tab button, should be considered part
923  // of the containing Window's client area so that regular events can be
924  // processed for them.
925  return false;
926}
927
928void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
929  for (int i = 0; i < tab_count(); ++i)
930    tab_at(i)->set_background_offset(offset);
931  newtab_button_->set_background_offset(offset);
932}
933
934void TabStrip::SetImmersiveStyle(bool enable) {
935  if (immersive_style_ == enable)
936    return;
937  immersive_style_ = enable;
938}
939
940bool TabStrip::IsAnimating() const {
941  return bounds_animator_.IsAnimating();
942}
943
944void TabStrip::StopAnimating(bool layout) {
945  if (!IsAnimating())
946    return;
947
948  bounds_animator_.Cancel();
949
950  if (layout)
951    DoLayout();
952}
953
954void TabStrip::FileSupported(const GURL& url, bool supported) {
955  if (drop_info_.get() && drop_info_->url == url)
956    drop_info_->file_supported = supported;
957}
958
959const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
960  return controller_->GetSelectionModel();
961}
962
963bool TabStrip::SupportsMultipleSelection() {
964  // TODO: currently only allow single selection in touch layout mode.
965  return touch_layout_ == NULL;
966}
967
968void TabStrip::SelectTab(Tab* tab) {
969  int model_index = GetModelIndexOfTab(tab);
970  if (IsValidModelIndex(model_index))
971    controller_->SelectTab(model_index);
972}
973
974void TabStrip::ExtendSelectionTo(Tab* tab) {
975  int model_index = GetModelIndexOfTab(tab);
976  if (IsValidModelIndex(model_index))
977    controller_->ExtendSelectionTo(model_index);
978}
979
980void TabStrip::ToggleSelected(Tab* tab) {
981  int model_index = GetModelIndexOfTab(tab);
982  if (IsValidModelIndex(model_index))
983    controller_->ToggleSelected(model_index);
984}
985
986void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
987  int model_index = GetModelIndexOfTab(tab);
988  if (IsValidModelIndex(model_index))
989    controller_->AddSelectionFromAnchorTo(model_index);
990}
991
992void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
993  if (tab->closing()) {
994    // If the tab is already closing, close the next tab. We do this so that the
995    // user can rapidly close tabs by clicking the close button and not have
996    // the animations interfere with that.
997    const int closed_tab_index = FindClosingTab(tab).first->first;
998    if (closed_tab_index < GetModelCount())
999      controller_->CloseTab(closed_tab_index, source);
1000    return;
1001  }
1002  int model_index = GetModelIndexOfTab(tab);
1003  if (IsValidModelIndex(model_index))
1004    controller_->CloseTab(model_index, source);
1005}
1006
1007void TabStrip::ToggleTabAudioMute(Tab* tab) {
1008  int model_index = GetModelIndexOfTab(tab);
1009  if (IsValidModelIndex(model_index))
1010    controller_->ToggleTabAudioMute(model_index);
1011}
1012
1013void TabStrip::ShowContextMenuForTab(Tab* tab,
1014                                     const gfx::Point& p,
1015                                     ui::MenuSourceType source_type) {
1016  controller_->ShowContextMenuForTab(tab, p, source_type);
1017}
1018
1019bool TabStrip::IsActiveTab(const Tab* tab) const {
1020  int model_index = GetModelIndexOfTab(tab);
1021  return IsValidModelIndex(model_index) &&
1022      controller_->IsActiveTab(model_index);
1023}
1024
1025bool TabStrip::IsTabSelected(const Tab* tab) const {
1026  int model_index = GetModelIndexOfTab(tab);
1027  return IsValidModelIndex(model_index) &&
1028      controller_->IsTabSelected(model_index);
1029}
1030
1031bool TabStrip::IsTabPinned(const Tab* tab) const {
1032  if (tab->closing())
1033    return false;
1034
1035  int model_index = GetModelIndexOfTab(tab);
1036  return IsValidModelIndex(model_index) &&
1037      controller_->IsTabPinned(model_index);
1038}
1039
1040void TabStrip::MaybeStartDrag(
1041    Tab* tab,
1042    const ui::LocatedEvent& event,
1043    const ui::ListSelectionModel& original_selection) {
1044  // Don't accidentally start any drag operations during animations if the
1045  // mouse is down... during an animation tabs are being resized automatically,
1046  // so the View system can misinterpret this easily if the mouse is down that
1047  // the user is dragging.
1048  if (IsAnimating() || tab->closing() ||
1049      controller_->HasAvailableDragActions() == 0) {
1050    return;
1051  }
1052
1053  // Do not do any dragging of tabs when using the super short immersive style.
1054  if (IsImmersiveStyle())
1055    return;
1056
1057  int model_index = GetModelIndexOfTab(tab);
1058  if (!IsValidModelIndex(model_index)) {
1059    CHECK(false);
1060    return;
1061  }
1062  Tabs tabs;
1063  int size_to_selected = 0;
1064  int x = tab->GetMirroredXInView(event.x());
1065  int y = event.y();
1066  // Build the set of selected tabs to drag and calculate the offset from the
1067  // first selected tab.
1068  for (int i = 0; i < tab_count(); ++i) {
1069    Tab* other_tab = tab_at(i);
1070    if (IsTabSelected(other_tab)) {
1071      tabs.push_back(other_tab);
1072      if (other_tab == tab) {
1073        size_to_selected = GetSizeNeededForTabs(tabs);
1074        x = size_to_selected - tab->width() + x;
1075      }
1076    }
1077  }
1078  DCHECK(!tabs.empty());
1079  DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1080  ui::ListSelectionModel selection_model;
1081  if (!original_selection.IsSelected(model_index))
1082    selection_model.Copy(original_selection);
1083  // Delete the existing DragController before creating a new one. We do this as
1084  // creating the DragController remembers the WebContents delegates and we need
1085  // to make sure the existing DragController isn't still a delegate.
1086  drag_controller_.reset();
1087  TabDragController::MoveBehavior move_behavior =
1088      TabDragController::REORDER;
1089  // Use MOVE_VISIBILE_TABS in the following conditions:
1090  // . Mouse event generated from touch and the left button is down (the right
1091  //   button corresponds to a long press, which we want to reorder).
1092  // . Gesture tap down and control key isn't down.
1093  // . Real mouse event and control is down. This is mostly for testing.
1094  DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1095         event.type() == ui::ET_GESTURE_TAP_DOWN);
1096  if (touch_layout_ &&
1097      ((event.type() == ui::ET_MOUSE_PRESSED &&
1098        (((event.flags() & ui::EF_FROM_TOUCH) &&
1099          static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1100         (!(event.flags() & ui::EF_FROM_TOUCH) &&
1101          static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1102       (event.type() == ui::ET_GESTURE_TAP_DOWN && !event.IsControlDown()))) {
1103    move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1104  }
1105
1106  drag_controller_.reset(new TabDragController);
1107  drag_controller_->Init(
1108      this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1109      move_behavior, EventSourceFromEvent(event));
1110}
1111
1112void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1113  if (drag_controller_.get() &&
1114      drag_controller_->event_source() == EventSourceFromEvent(event)) {
1115    gfx::Point screen_location(event.location());
1116    views::View::ConvertPointToScreen(view, &screen_location);
1117    drag_controller_->Drag(screen_location);
1118  }
1119}
1120
1121bool TabStrip::EndDrag(EndDragReason reason) {
1122  if (!drag_controller_.get())
1123    return false;
1124  bool started_drag = drag_controller_->started_drag();
1125  drag_controller_->EndDrag(reason);
1126  return started_drag;
1127}
1128
1129Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1130  gfx::Point local_point = tab_in_tab_coordinates;
1131  ConvertPointToTarget(tab, this, &local_point);
1132
1133  views::View* view = GetEventHandlerForPoint(local_point);
1134  if (!view)
1135    return NULL;  // No tab contains the point.
1136
1137  // Walk up the view hierarchy until we find a tab, or the TabStrip.
1138  while (view && view != this && view->id() != VIEW_ID_TAB)
1139    view = view->parent();
1140
1141  return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1142}
1143
1144void TabStrip::OnMouseEventInTab(views::View* source,
1145                                 const ui::MouseEvent& event) {
1146  UpdateStackedLayoutFromMouseEvent(source, event);
1147}
1148
1149bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1150  // Only touch layout needs to restrict the clip.
1151  if (!touch_layout_ && !IsStackingDraggedTabs())
1152    return true;
1153
1154  int index = GetModelIndexOfTab(tab);
1155  if (index == -1)
1156    return true;  // Tab is closing, paint it all.
1157
1158  int active_index = IsStackingDraggedTabs() ?
1159      controller_->GetActiveIndex() : touch_layout_->active_index();
1160  if (active_index == tab_count())
1161    active_index--;
1162
1163  if (index < active_index) {
1164    if (tab_at(index)->x() == tab_at(index + 1)->x())
1165      return false;
1166
1167    if (tab_at(index)->x() > tab_at(index + 1)->x())
1168      return true;  // Can happen during dragging.
1169
1170    clip->SetRect(
1171        0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + kStackedTabLeftClip,
1172        tab_at(index)->height());
1173  } else if (index > active_index && index > 0) {
1174    const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1175    const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1176    if (tab_bounds.x() == previous_tab_bounds.x())
1177      return false;
1178
1179    if (tab_bounds.x() < previous_tab_bounds.x())
1180      return true;  // Can happen during dragging.
1181
1182    if (previous_tab_bounds.right() + kTabHorizontalOffset != tab_bounds.x()) {
1183      int x = previous_tab_bounds.right() - tab_bounds.x() -
1184          kStackedTabRightClip;
1185      clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1186    }
1187  }
1188  return true;
1189}
1190
1191bool TabStrip::IsImmersiveStyle() const {
1192  return immersive_style_;
1193}
1194
1195void TabStrip::UpdateTabAccessibilityState(const Tab* tab,
1196                                           ui::AXViewState* state) {
1197  state->count = tab_count();
1198  state->index = GetModelIndexOfTab(tab);
1199}
1200
1201void TabStrip::MouseMovedOutOfHost() {
1202  ResizeLayoutTabs();
1203  if (reset_to_shrink_on_exit_) {
1204    reset_to_shrink_on_exit_ = false;
1205    SetStackedLayout(false);
1206    controller_->StackedLayoutMaybeChanged();
1207  }
1208}
1209
1210///////////////////////////////////////////////////////////////////////////////
1211// TabStrip, views::View overrides:
1212
1213void TabStrip::Layout() {
1214  // Only do a layout if our size changed.
1215  if (last_layout_size_ == size())
1216    return;
1217  if (IsDragSessionActive())
1218    return;
1219  DoLayout();
1220}
1221
1222void TabStrip::PaintChildren(gfx::Canvas* canvas,
1223                             const views::CullSet& cull_set) {
1224  // The view order doesn't match the paint order (tabs_ contains the tab
1225  // ordering). Additionally we need to paint the tabs that are closing in
1226  // |tabs_closing_map_|.
1227  Tab* active_tab = NULL;
1228  Tabs tabs_dragging;
1229  Tabs selected_tabs;
1230  int selected_tab_count = 0;
1231  bool is_dragging = false;
1232  int active_tab_index = -1;
1233
1234  const chrome::HostDesktopType host_desktop_type =
1235      chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1236  const int inactive_tab_alpha =
1237      (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) ?
1238      kInactiveTabAndNewTabButtonAlphaAsh : kInactiveTabAndNewTabButtonAlpha;
1239
1240  if (inactive_tab_alpha < 255)
1241    canvas->SaveLayerAlpha(inactive_tab_alpha);
1242
1243  PaintClosingTabs(canvas, tab_count(), cull_set);
1244
1245  for (int i = tab_count() - 1; i >= 0; --i) {
1246    Tab* tab = tab_at(i);
1247    if (tab->IsSelected())
1248      selected_tab_count++;
1249    if (tab->dragging() && !stacked_layout_) {
1250      is_dragging = true;
1251      if (tab->IsActive()) {
1252        active_tab = tab;
1253        active_tab_index = i;
1254      } else {
1255        tabs_dragging.push_back(tab);
1256      }
1257    } else if (!tab->IsActive()) {
1258      if (!tab->IsSelected()) {
1259        if (!stacked_layout_)
1260          tab->Paint(canvas, cull_set);
1261      } else {
1262        selected_tabs.push_back(tab);
1263      }
1264    } else {
1265      active_tab = tab;
1266      active_tab_index = i;
1267    }
1268    PaintClosingTabs(canvas, i, cull_set);
1269  }
1270
1271  // Draw from the left and then the right if we're in touch mode.
1272  if (stacked_layout_ && active_tab_index >= 0) {
1273    for (int i = 0; i < active_tab_index; ++i) {
1274      Tab* tab = tab_at(i);
1275      tab->Paint(canvas, cull_set);
1276    }
1277
1278    for (int i = tab_count() - 1; i > active_tab_index; --i) {
1279      Tab* tab = tab_at(i);
1280      tab->Paint(canvas, cull_set);
1281    }
1282  }
1283  if (inactive_tab_alpha < 255)
1284    canvas->Restore();
1285
1286  if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1287    // Make sure non-active tabs are somewhat transparent.
1288    SkPaint paint;
1289    // If there are multiple tabs selected, fade non-selected tabs more to make
1290    // the selected tabs more noticable.
1291    int alpha = selected_tab_count > 1 ?
1292        kGlassFrameInactiveTabAlphaMultiSelection :
1293        kGlassFrameInactiveTabAlpha;
1294    paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1295    paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1296    paint.setStyle(SkPaint::kFill_Style);
1297    // The tabstrip area overlaps the toolbar area by 2 px.
1298    canvas->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint);
1299  }
1300
1301  // Now selected but not active. We don't want these dimmed if using native
1302  // frame, so they're painted after initial pass.
1303  for (size_t i = 0; i < selected_tabs.size(); ++i)
1304    selected_tabs[i]->Paint(canvas, cull_set);
1305
1306  // Next comes the active tab.
1307  if (active_tab && !is_dragging)
1308    active_tab->Paint(canvas, cull_set);
1309
1310  // Paint the New Tab button.
1311  if (inactive_tab_alpha < 255)
1312    canvas->SaveLayerAlpha(inactive_tab_alpha);
1313  newtab_button_->Paint(canvas, cull_set);
1314  if (inactive_tab_alpha < 255)
1315    canvas->Restore();
1316
1317  // And the dragged tabs.
1318  for (size_t i = 0; i < tabs_dragging.size(); ++i)
1319    tabs_dragging[i]->Paint(canvas, cull_set);
1320
1321  // If the active tab is being dragged, it goes last.
1322  if (active_tab && is_dragging)
1323    active_tab->Paint(canvas, cull_set);
1324}
1325
1326const char* TabStrip::GetClassName() const {
1327  return kViewClassName;
1328}
1329
1330gfx::Size TabStrip::GetPreferredSize() const {
1331  int needed_tab_width;
1332  if (touch_layout_ || adjust_layout_) {
1333    // For stacked tabs the minimum size is calculated as the size needed to
1334    // handle showing any number of tabs.
1335    needed_tab_width =
1336        Tab::GetTouchWidth() + (2 * kStackedPadding * kMaxStackedCount);
1337  } else {
1338    // Otherwise the minimum width is based on the actual number of tabs.
1339    const int mini_tab_count = GetMiniTabCount();
1340    needed_tab_width = mini_tab_count * Tab::GetMiniWidth();
1341    const int remaining_tab_count = tab_count() - mini_tab_count;
1342    const int min_selected_width = Tab::GetMinimumSelectedSize().width();
1343    const int min_unselected_width = Tab::GetMinimumUnselectedSize().width();
1344    if (remaining_tab_count > 0) {
1345      needed_tab_width += kMiniToNonMiniGap + min_selected_width +
1346          ((remaining_tab_count - 1) * min_unselected_width);
1347    }
1348    if (tab_count() > 1)
1349      needed_tab_width += (tab_count() - 1) * kTabHorizontalOffset;
1350
1351    // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1352    // and don't force it to be larger than is necessary to show 20 tabs.
1353    const int largest_min_tab_width =
1354        min_selected_width + 19 * (min_unselected_width + kTabHorizontalOffset);
1355    needed_tab_width = std::min(
1356        std::max(needed_tab_width, min_selected_width), largest_min_tab_width);
1357  }
1358  return gfx::Size(
1359      needed_tab_width + new_tab_button_width(),
1360      immersive_style_ ?
1361          Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1362}
1363
1364void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1365  // Force animations to stop, otherwise it makes the index calculation tricky.
1366  StopAnimating(true);
1367
1368  UpdateDropIndex(event);
1369
1370  GURL url;
1371  base::string16 title;
1372
1373  // Check whether the event data includes supported drop data.
1374  if (event.data().GetURLAndTitle(
1375          ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1376      url.is_valid()) {
1377    drop_info_->url = url;
1378
1379    // For file:// URLs, kick off a MIME type request in case they're dropped.
1380    if (url.SchemeIsFile())
1381      controller()->CheckFileSupported(url);
1382  }
1383}
1384
1385int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1386  // Update the drop index even if the file is unsupported, to allow
1387  // dragging a file to the contents of another tab.
1388  UpdateDropIndex(event);
1389
1390  if (!drop_info_->file_supported)
1391    return ui::DragDropTypes::DRAG_NONE;
1392
1393  return GetDropEffect(event);
1394}
1395
1396void TabStrip::OnDragExited() {
1397  SetDropIndex(-1, false);
1398}
1399
1400int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1401  if (!drop_info_.get())
1402    return ui::DragDropTypes::DRAG_NONE;
1403
1404  const int drop_index = drop_info_->drop_index;
1405  const bool drop_before = drop_info_->drop_before;
1406  const bool file_supported = drop_info_->file_supported;
1407
1408  // Hide the drop indicator.
1409  SetDropIndex(-1, false);
1410
1411  // Do nothing if the file was unsupported or the URL is invalid. The URL may
1412  // have been changed after |drop_info_| was created.
1413  GURL url;
1414  base::string16 title;
1415  if (!file_supported ||
1416      !event.data().GetURLAndTitle(
1417           ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1418      !url.is_valid())
1419    return ui::DragDropTypes::DRAG_NONE;
1420
1421  controller()->PerformDrop(drop_before, drop_index, url);
1422
1423  return GetDropEffect(event);
1424}
1425
1426void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1427  state->role = ui::AX_ROLE_TAB_LIST;
1428}
1429
1430views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1431  if (!HitTestPoint(point))
1432    return NULL;
1433
1434  if (!touch_layout_) {
1435    // Return any view that isn't a Tab or this TabStrip immediately. We don't
1436    // want to interfere.
1437    views::View* v = View::GetTooltipHandlerForPoint(point);
1438    if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1439      return v;
1440
1441    views::View* tab = FindTabHitByPoint(point);
1442    if (tab)
1443      return tab;
1444  } else {
1445    if (newtab_button_->visible()) {
1446      views::View* view =
1447          ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1448      if (view)
1449        return view;
1450    }
1451    Tab* tab = FindTabForEvent(point);
1452    if (tab)
1453      return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1454  }
1455  return this;
1456}
1457
1458// static
1459int TabStrip::GetImmersiveHeight() {
1460  return Tab::GetImmersiveHeight();
1461}
1462
1463///////////////////////////////////////////////////////////////////////////////
1464// TabStrip, private:
1465
1466void TabStrip::Init() {
1467  set_id(VIEW_ID_TAB_STRIP);
1468  // So we get enter/exit on children to switch stacked layout on and off.
1469  set_notify_enter_exit_on_child(true);
1470  newtab_button_bounds_.SetRect(0,
1471                                0,
1472                                kNewTabButtonAssetWidth,
1473                                kNewTabButtonAssetHeight +
1474                                    kNewTabButtonVerticalOffset);
1475  newtab_button_ = new NewTabButton(this, this);
1476  newtab_button_->SetTooltipText(
1477      l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1478  newtab_button_->SetAccessibleName(
1479      l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1480  newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1481                                    views::ImageButton::ALIGN_BOTTOM);
1482  newtab_button_->SetEventTargeter(
1483      scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(newtab_button_)));
1484  AddChildView(newtab_button_);
1485
1486  if (drop_indicator_width == 0) {
1487    // Direction doesn't matter, both images are the same size.
1488    gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1489    drop_indicator_width = drop_image->width();
1490    drop_indicator_height = drop_image->height();
1491  }
1492}
1493
1494Tab* TabStrip::CreateTab() {
1495  Tab* tab = new Tab(this);
1496  tab->set_animation_container(animation_container_.get());
1497  return tab;
1498}
1499
1500void TabStrip::StartInsertTabAnimation(int model_index) {
1501  PrepareForAnimation();
1502
1503  // The TabStrip can now use its entire width to lay out Tabs.
1504  in_tab_close_ = false;
1505  available_width_for_tabs_ = -1;
1506
1507  GenerateIdealBounds();
1508
1509  Tab* tab = tab_at(model_index);
1510  if (model_index == 0) {
1511    tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1512                   ideal_bounds(model_index).height());
1513  } else {
1514    Tab* last_tab = tab_at(model_index - 1);
1515    tab->SetBounds(last_tab->bounds().right() + kTabHorizontalOffset,
1516                   ideal_bounds(model_index).y(), 0,
1517                   ideal_bounds(model_index).height());
1518  }
1519
1520  AnimateToIdealBounds();
1521}
1522
1523void TabStrip::StartMoveTabAnimation() {
1524  PrepareForAnimation();
1525  GenerateIdealBounds();
1526  AnimateToIdealBounds();
1527}
1528
1529void TabStrip::StartRemoveTabAnimation(int model_index) {
1530  PrepareForAnimation();
1531
1532  // Mark the tab as closing.
1533  Tab* tab = tab_at(model_index);
1534  tab->set_closing(true);
1535
1536  RemoveTabFromViewModel(model_index);
1537
1538  ScheduleRemoveTabAnimation(tab);
1539}
1540
1541void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1542  // Start an animation for the tabs.
1543  GenerateIdealBounds();
1544  AnimateToIdealBounds();
1545
1546  // Animate the tab being closed to zero width.
1547  gfx::Rect tab_bounds = tab->bounds();
1548  tab_bounds.set_width(0);
1549  bounds_animator_.AnimateViewTo(tab, tab_bounds);
1550  bounds_animator_.SetAnimationDelegate(
1551      tab,
1552      scoped_ptr<gfx::AnimationDelegate>(new RemoveTabDelegate(this, tab)));
1553
1554  // Don't animate the new tab button when dragging tabs. Otherwise it looks
1555  // like the new tab button magically appears from beyond the end of the tab
1556  // strip.
1557  if (TabDragController::IsAttachedTo(this)) {
1558    bounds_animator_.StopAnimatingView(newtab_button_);
1559    newtab_button_->SetBoundsRect(newtab_button_bounds_);
1560  }
1561}
1562
1563void TabStrip::AnimateToIdealBounds() {
1564  for (int i = 0; i < tab_count(); ++i) {
1565    Tab* tab = tab_at(i);
1566    if (!tab->dragging()) {
1567      bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1568      bounds_animator_.SetAnimationDelegate(
1569          tab,
1570          scoped_ptr<gfx::AnimationDelegate>(
1571              new TabAnimationDelegate(this, tab)));
1572    }
1573  }
1574
1575  bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1576}
1577
1578bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1579  return in_tab_close_;
1580}
1581
1582void TabStrip::DoLayout() {
1583  last_layout_size_ = size();
1584
1585  StopAnimating(false);
1586
1587  SwapLayoutIfNecessary();
1588
1589  if (touch_layout_)
1590    touch_layout_->SetWidth(tab_area_width());
1591
1592  GenerateIdealBounds();
1593
1594  views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1595  SetTabVisibility();
1596
1597  SchedulePaint();
1598
1599  bounds_animator_.StopAnimatingView(newtab_button_);
1600  newtab_button_->SetBoundsRect(newtab_button_bounds_);
1601}
1602
1603void TabStrip::SetTabVisibility() {
1604  // We could probably be more efficient here by making use of the fact that the
1605  // tabstrip will always have any visible tabs, and then any invisible tabs, so
1606  // we could e.g. binary-search for the changeover point.  But since we have to
1607  // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1608  // worth it.
1609  for (int i = 0; i < tab_count(); ++i) {
1610    Tab* tab = tab_at(i);
1611    tab->SetVisible(ShouldTabBeVisible(tab));
1612  }
1613  for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1614       i != tabs_closing_map_.end(); ++i) {
1615    for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) {
1616      Tab* tab = *j;
1617      tab->SetVisible(ShouldTabBeVisible(tab));
1618    }
1619  }
1620}
1621
1622void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1623                             int delta) {
1624  DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1625  if (!touch_layout_) {
1626    StackDraggedTabs(delta);
1627    return;
1628  }
1629  SetIdealBoundsFromPositions(initial_positions);
1630  touch_layout_->DragActiveTab(delta);
1631  DoLayout();
1632}
1633
1634void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1635  if (static_cast<size_t>(tab_count()) != positions.size())
1636    return;
1637
1638  for (int i = 0; i < tab_count(); ++i) {
1639    gfx::Rect bounds(ideal_bounds(i));
1640    bounds.set_x(positions[i]);
1641    tabs_.set_ideal_bounds(i, bounds);
1642  }
1643}
1644
1645void TabStrip::StackDraggedTabs(int delta) {
1646  DCHECK(!touch_layout_);
1647  GenerateIdealBounds();
1648  const int active_index = controller_->GetActiveIndex();
1649  DCHECK_NE(-1, active_index);
1650  if (delta < 0) {
1651    // Drag the tabs to the left, stacking tabs before the active tab.
1652    const int adjusted_delta =
1653        std::min(ideal_bounds(active_index).x() -
1654                     kStackedPadding * std::min(active_index, kMaxStackedCount),
1655                 -delta);
1656    for (int i = 0; i <= active_index; ++i) {
1657      const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1658      gfx::Rect new_bounds(ideal_bounds(i));
1659      new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1660      tabs_.set_ideal_bounds(i, new_bounds);
1661    }
1662    const bool is_active_mini = tab_at(active_index)->data().mini;
1663    const int active_width = ideal_bounds(active_index).width();
1664    for (int i = active_index + 1; i < tab_count(); ++i) {
1665      const int max_x = ideal_bounds(active_index).x() +
1666          (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1667      gfx::Rect new_bounds(ideal_bounds(i));
1668      int new_x = std::max(new_bounds.x() + delta, max_x);
1669      if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini &&
1670          new_bounds.width() != active_width)
1671        new_x += (active_width - new_bounds.width());
1672      new_bounds.set_x(new_x);
1673      tabs_.set_ideal_bounds(i, new_bounds);
1674    }
1675  } else {
1676    // Drag the tabs to the right, stacking tabs after the active tab.
1677    const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1678    const int last_tab_x = tab_area_width() - last_tab_width;
1679    if (active_index == tab_count() - 1 &&
1680        ideal_bounds(tab_count() - 1).x() == last_tab_x)
1681      return;
1682    const int adjusted_delta =
1683        std::min(last_tab_x -
1684                     kStackedPadding * std::min(tab_count() - active_index - 1,
1685                                                kMaxStackedCount) -
1686                     ideal_bounds(active_index).x(),
1687                 delta);
1688    for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1689         --i) {
1690      const int max_x = last_tab_x -
1691          std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1692      gfx::Rect new_bounds(ideal_bounds(i));
1693      int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1694      // Because of rounding not all tabs are the same width. Adjust the
1695      // position to accommodate this, otherwise the stacking is off.
1696      if (new_x == max_x && !tab_at(i)->data().mini &&
1697          new_bounds.width() != last_tab_width)
1698        new_x += (last_tab_width - new_bounds.width());
1699      new_bounds.set_x(new_x);
1700      tabs_.set_ideal_bounds(i, new_bounds);
1701    }
1702    for (int i = active_index - 1; i >= 0; --i) {
1703      const int min_x = ideal_bounds(active_index).x() -
1704          std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1705      gfx::Rect new_bounds(ideal_bounds(i));
1706      new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1707      tabs_.set_ideal_bounds(i, new_bounds);
1708    }
1709    if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1710      newtab_button_->SetVisible(false);
1711  }
1712  views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1713  SchedulePaint();
1714}
1715
1716bool TabStrip::IsStackingDraggedTabs() const {
1717  return drag_controller_.get() && drag_controller_->started_drag() &&
1718      (drag_controller_->move_behavior() ==
1719       TabDragController::MOVE_VISIBILE_TABS);
1720}
1721
1722void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs,
1723                                   Tab* active_tab,
1724                                   const gfx::Point& location,
1725                                   bool initial_drag) {
1726  // Immediately hide the new tab button if the last tab is being dragged.
1727  const Tab* last_visible_tab = GetLastVisibleTab();
1728  if (last_visible_tab && last_visible_tab->dragging())
1729    newtab_button_->SetVisible(false);
1730  std::vector<gfx::Rect> bounds;
1731  CalculateBoundsForDraggedTabs(tabs, &bounds);
1732  DCHECK_EQ(tabs.size(), bounds.size());
1733  int active_tab_model_index = GetModelIndexOfTab(active_tab);
1734  int active_tab_index = static_cast<int>(
1735      std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1736  for (size_t i = 0; i < tabs.size(); ++i) {
1737    Tab* tab = tabs[i];
1738    gfx::Rect new_bounds = bounds[i];
1739    new_bounds.Offset(location.x(), location.y());
1740    int consecutive_index =
1741        active_tab_model_index - (active_tab_index - static_cast<int>(i));
1742    // If this is the initial layout during a drag and the tabs aren't
1743    // consecutive animate the view into position. Do the same if the tab is
1744    // already animating (which means we previously caused it to animate).
1745    if ((initial_drag &&
1746         GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1747        bounds_animator_.IsAnimating(tabs[i])) {
1748      bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1749    } else {
1750      tab->SetBoundsRect(new_bounds);
1751    }
1752  }
1753  SetTabVisibility();
1754}
1755
1756void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs,
1757                                             std::vector<gfx::Rect>* bounds) {
1758  int x = 0;
1759  for (size_t i = 0; i < tabs.size(); ++i) {
1760    Tab* tab = tabs[i];
1761    if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1762      x += kMiniToNonMiniGap;
1763    gfx::Rect new_bounds = tab->bounds();
1764    new_bounds.set_origin(gfx::Point(x, 0));
1765    bounds->push_back(new_bounds);
1766    x += tab->width() + kTabHorizontalOffset;
1767  }
1768}
1769
1770int TabStrip::GetSizeNeededForTabs(const Tabs& tabs) {
1771  int width = 0;
1772  for (size_t i = 0; i < tabs.size(); ++i) {
1773    Tab* tab = tabs[i];
1774    width += tab->width();
1775    if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1776      width += kMiniToNonMiniGap;
1777  }
1778  if (tabs.size() > 0)
1779    width += kTabHorizontalOffset * static_cast<int>(tabs.size() - 1);
1780  return width;
1781}
1782
1783int TabStrip::GetMiniTabCount() const {
1784  int mini_count = 0;
1785  while (mini_count < tab_count() && tab_at(mini_count)->data().mini)
1786    mini_count++;
1787  return mini_count;
1788}
1789
1790const Tab* TabStrip::GetLastVisibleTab() const {
1791  for (int i = tab_count() - 1; i >= 0; --i) {
1792    const Tab* tab = tab_at(i);
1793    if (tab->visible())
1794      return tab;
1795  }
1796  // While in normal use the tabstrip should always be wide enough to have at
1797  // least one visible tab, it can be zero-width in tests, meaning we get here.
1798  return NULL;
1799}
1800
1801void TabStrip::RemoveTabFromViewModel(int index) {
1802  // We still need to paint the tab until we actually remove it. Put it
1803  // in tabs_closing_map_ so we can find it.
1804  tabs_closing_map_[index].push_back(tab_at(index));
1805  UpdateTabsClosingMap(index + 1, -1);
1806  tabs_.Remove(index);
1807}
1808
1809void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1810  scoped_ptr<Tab> deleter(tab);
1811  FindClosingTabResult res(FindClosingTab(tab));
1812  res.first->second.erase(res.second);
1813  if (res.first->second.empty())
1814    tabs_closing_map_.erase(res.first);
1815}
1816
1817void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1818  if (tabs_closing_map_.empty())
1819    return;
1820
1821  if (delta == -1 &&
1822      tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1823      tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1824    const Tabs& tabs(tabs_closing_map_[index]);
1825    tabs_closing_map_[index - 1].insert(
1826        tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1827  }
1828  TabsClosingMap updated_map;
1829  for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1830       i != tabs_closing_map_.end(); ++i) {
1831    if (i->first > index)
1832      updated_map[i->first + delta] = i->second;
1833    else if (i->first < index)
1834      updated_map[i->first] = i->second;
1835  }
1836  if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1837    updated_map[index + delta] = tabs_closing_map_[index];
1838  tabs_closing_map_.swap(updated_map);
1839}
1840
1841void TabStrip::StartedDraggingTabs(const Tabs& tabs) {
1842  // Let the controller know that the user started dragging tabs.
1843  controller()->OnStartedDraggingTabs();
1844
1845  // Hide the new tab button immediately if we didn't originate the drag.
1846  if (!drag_controller_.get())
1847    newtab_button_->SetVisible(false);
1848
1849  PrepareForAnimation();
1850
1851  // Reset dragging state of existing tabs.
1852  for (int i = 0; i < tab_count(); ++i)
1853    tab_at(i)->set_dragging(false);
1854
1855  for (size_t i = 0; i < tabs.size(); ++i) {
1856    tabs[i]->set_dragging(true);
1857    bounds_animator_.StopAnimatingView(tabs[i]);
1858  }
1859
1860  // Move the dragged tabs to their ideal bounds.
1861  GenerateIdealBounds();
1862
1863  // Sets the bounds of the dragged tabs.
1864  for (size_t i = 0; i < tabs.size(); ++i) {
1865    int tab_data_index = GetModelIndexOfTab(tabs[i]);
1866    DCHECK_NE(-1, tab_data_index);
1867    tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1868  }
1869  SetTabVisibility();
1870  SchedulePaint();
1871}
1872
1873void TabStrip::DraggedTabsDetached() {
1874  // Let the controller know that the user is not dragging this tabstrip's tabs
1875  // anymore.
1876  controller()->OnStoppedDraggingTabs();
1877  newtab_button_->SetVisible(true);
1878}
1879
1880void TabStrip::StoppedDraggingTabs(const Tabs& tabs,
1881                                   const std::vector<int>& initial_positions,
1882                                   bool move_only,
1883                                   bool completed) {
1884  // Let the controller know that the user stopped dragging tabs.
1885  controller()->OnStoppedDraggingTabs();
1886
1887  newtab_button_->SetVisible(true);
1888  if (move_only && touch_layout_) {
1889    if (completed)
1890      touch_layout_->SizeToFit();
1891    else
1892      SetIdealBoundsFromPositions(initial_positions);
1893  }
1894  bool is_first_tab = true;
1895  for (size_t i = 0; i < tabs.size(); ++i)
1896    StoppedDraggingTab(tabs[i], &is_first_tab);
1897}
1898
1899void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1900  int tab_data_index = GetModelIndexOfTab(tab);
1901  if (tab_data_index == -1) {
1902    // The tab was removed before the drag completed. Don't do anything.
1903    return;
1904  }
1905
1906  if (*is_first_tab) {
1907    *is_first_tab = false;
1908    PrepareForAnimation();
1909
1910    // Animate the view back to its correct position.
1911    GenerateIdealBounds();
1912    AnimateToIdealBounds();
1913  }
1914  bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1915  // Install a delegate to reset the dragging state when done. We have to leave
1916  // dragging true for the tab otherwise it'll draw beneath the new tab button.
1917  bounds_animator_.SetAnimationDelegate(
1918      tab,
1919      scoped_ptr<gfx::AnimationDelegate>(
1920          new ResetDraggingStateDelegate(this, tab)));
1921}
1922
1923void TabStrip::OwnDragController(TabDragController* controller) {
1924  // Typically, ReleaseDragController() and OwnDragController() calls are paired
1925  // via corresponding calls to TabDragController::Detach() and
1926  // TabDragController::Attach(). There is one exception to that rule: when a
1927  // drag might start, we create a TabDragController that is owned by the
1928  // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1929  // we then call Attach() on the source tabstrip, but since the source tabstrip
1930  // already owns the TabDragController, so we don't need to do anything.
1931  if (controller != drag_controller_.get())
1932    drag_controller_.reset(controller);
1933}
1934
1935void TabStrip::DestroyDragController() {
1936  newtab_button_->SetVisible(true);
1937  drag_controller_.reset();
1938}
1939
1940TabDragController* TabStrip::ReleaseDragController() {
1941  return drag_controller_.release();
1942}
1943
1944TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
1945  DCHECK(tab->closing());
1946  for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1947       i != tabs_closing_map_.end(); ++i) {
1948    Tabs::iterator j = std::find(i->second.begin(), i->second.end(), tab);
1949    if (j != i->second.end())
1950      return FindClosingTabResult(i, j);
1951  }
1952  NOTREACHED();
1953  return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
1954}
1955
1956void TabStrip::PaintClosingTabs(gfx::Canvas* canvas,
1957                                int index,
1958                                const views::CullSet& cull_set) {
1959  if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
1960    return;
1961
1962  const Tabs& tabs = tabs_closing_map_[index];
1963  for (Tabs::const_reverse_iterator i(tabs.rbegin()); i != tabs.rend(); ++i)
1964    (*i)->Paint(canvas, cull_set);
1965}
1966
1967void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
1968                                                 const ui::MouseEvent& event) {
1969  if (!adjust_layout_)
1970    return;
1971
1972  // The following code attempts to switch to shrink (not stacked) layout when
1973  // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
1974  // to stacked layout when a touch device is used. This is made problematic by
1975  // windows generating mouse move events that do not clearly indicate the move
1976  // is the result of a touch device. This assumes a real mouse is used if
1977  // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
1978  // the time window |kMouseMoveTimeMS|.  At the time we get a mouse press we
1979  // know whether its from a touch device or not, but we don't layout then else
1980  // everything shifts. Instead we wait for the release.
1981  //
1982  // TODO(sky): revisit this when touch events are really plumbed through.
1983
1984  switch (event.type()) {
1985    case ui::ET_MOUSE_PRESSED:
1986      mouse_move_count_ = 0;
1987      last_mouse_move_time_ = base::TimeTicks();
1988      SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
1989      if (reset_to_shrink_on_exit_ && touch_layout_) {
1990        gfx::Point tab_strip_point(event.location());
1991        views::View::ConvertPointToTarget(source, this, &tab_strip_point);
1992        Tab* tab = FindTabForEvent(tab_strip_point);
1993        if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
1994          SetStackedLayout(false);
1995          controller_->StackedLayoutMaybeChanged();
1996        }
1997      }
1998      break;
1999
2000    case ui::ET_MOUSE_MOVED: {
2001#if defined(USE_ASH)
2002      // Ash does not synthesize mouse events from touch events.
2003      SetResetToShrinkOnExit(true);
2004#else
2005      gfx::Point location(event.location());
2006      ConvertPointToTarget(source, this, &location);
2007      if (location == last_mouse_move_location_)
2008        return;  // Ignore spurious moves.
2009      last_mouse_move_location_ = location;
2010      if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2011          (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2012        if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2013            kMouseMoveTimeMS) {
2014          if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2015            SetResetToShrinkOnExit(true);
2016        } else {
2017          mouse_move_count_ = 1;
2018          last_mouse_move_time_ = base::TimeTicks::Now();
2019        }
2020      } else {
2021        last_mouse_move_time_ = base::TimeTicks();
2022      }
2023#endif
2024      break;
2025    }
2026
2027    case ui::ET_MOUSE_RELEASED: {
2028      gfx::Point location(event.location());
2029      ConvertPointToTarget(source, this, &location);
2030      last_mouse_move_location_ = location;
2031      mouse_move_count_ = 0;
2032      last_mouse_move_time_ = base::TimeTicks();
2033      if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2034        SetStackedLayout(true);
2035        controller_->StackedLayoutMaybeChanged();
2036      }
2037      break;
2038    }
2039
2040    default:
2041      break;
2042  }
2043}
2044
2045void TabStrip::GetCurrentTabWidths(double* unselected_width,
2046                                   double* selected_width) const {
2047  *unselected_width = current_unselected_width_;
2048  *selected_width = current_selected_width_;
2049}
2050
2051void TabStrip::GetDesiredTabWidths(int tab_count,
2052                                   int mini_tab_count,
2053                                   double* unselected_width,
2054                                   double* selected_width) const {
2055  DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
2056  const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2057  const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2058
2059  *unselected_width = min_unselected_width;
2060  *selected_width = min_selected_width;
2061
2062  if (tab_count == 0) {
2063    // Return immediately to avoid divide-by-zero below.
2064    return;
2065  }
2066
2067  // Determine how much space we can actually allocate to tabs.
2068  int available_width = (available_width_for_tabs_ < 0) ?
2069      tab_area_width() : available_width_for_tabs_;
2070  if (mini_tab_count > 0) {
2071    available_width -=
2072        mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset);
2073    tab_count -= mini_tab_count;
2074    if (tab_count == 0) {
2075      *selected_width = *unselected_width = Tab::GetStandardSize().width();
2076      return;
2077    }
2078    // Account for gap between the last mini-tab and first non-mini-tab.
2079    available_width -= kMiniToNonMiniGap;
2080  }
2081
2082  // Calculate the desired tab widths by dividing the available space into equal
2083  // portions.  Don't let tabs get larger than the "standard width" or smaller
2084  // than the minimum width for each type, respectively.
2085  const int total_offset = kTabHorizontalOffset * (tab_count - 1);
2086  const double desired_tab_width = std::min((static_cast<double>(
2087      available_width - total_offset) / static_cast<double>(tab_count)),
2088      static_cast<double>(Tab::GetStandardSize().width()));
2089  *unselected_width = std::max(desired_tab_width, min_unselected_width);
2090  *selected_width = std::max(desired_tab_width, min_selected_width);
2091
2092  // When there are multiple tabs, we'll have one selected and some unselected
2093  // tabs.  If the desired width was between the minimum sizes of these types,
2094  // try to shrink the tabs with the smaller minimum.  For example, if we have
2095  // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5.  If
2096  // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2097  // width of 1, the above code would set *unselected_width = 2.5,
2098  // *selected_width = 4, which results in a total width of 11.5.  Instead, we
2099  // want to set *unselected_width = 2, *selected_width = 4, for a total width
2100  // of 10.
2101  if (tab_count > 1) {
2102    if (desired_tab_width < min_selected_width) {
2103      // Unselected width = (total width - selected width) / (num_tabs - 1)
2104      *unselected_width = std::max(static_cast<double>(
2105          available_width - total_offset - min_selected_width) /
2106          static_cast<double>(tab_count - 1), min_unselected_width);
2107    }
2108  }
2109}
2110
2111void TabStrip::ResizeLayoutTabs() {
2112  // We've been called back after the TabStrip has been emptied out (probably
2113  // just prior to the window being destroyed). We need to do nothing here or
2114  // else GetTabAt below will crash.
2115  if (tab_count() == 0)
2116    return;
2117
2118  // It is critically important that this is unhooked here, otherwise we will
2119  // keep spying on messages forever.
2120  RemoveMessageLoopObserver();
2121
2122  in_tab_close_ = false;
2123  available_width_for_tabs_ = -1;
2124  int mini_tab_count = GetMiniTabCount();
2125  if (mini_tab_count == tab_count()) {
2126    // Only mini-tabs, we know the tab widths won't have changed (all
2127    // mini-tabs have the same width), so there is nothing to do.
2128    return;
2129  }
2130  // Don't try and avoid layout based on tab sizes. If tabs are small enough
2131  // then the width of the active tab may not change, but other widths may
2132  // have. This is particularly important if we've overflowed (all tabs are at
2133  // the min).
2134  StartResizeLayoutAnimation();
2135}
2136
2137void TabStrip::ResizeLayoutTabsFromTouch() {
2138  // Don't resize if the user is interacting with the tabstrip.
2139  if (!drag_controller_.get())
2140    ResizeLayoutTabs();
2141  else
2142    StartResizeLayoutTabsFromTouchTimer();
2143}
2144
2145void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2146  resize_layout_timer_.Stop();
2147  resize_layout_timer_.Start(
2148      FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2149      this, &TabStrip::ResizeLayoutTabsFromTouch);
2150}
2151
2152void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2153  StopAnimating(false);
2154  DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2155  for (int i = 0; i < tab_count(); ++i)
2156    tab_at(i)->SetBoundsRect(tab_bounds[i]);
2157  // Reset the layout size as we've effectively layed out a different size.
2158  // This ensures a layout happens after the drag is done.
2159  last_layout_size_ = gfx::Size();
2160}
2161
2162void TabStrip::AddMessageLoopObserver() {
2163  if (!mouse_watcher_.get()) {
2164    mouse_watcher_.reset(
2165        new views::MouseWatcher(
2166            new views::MouseWatcherViewHost(
2167                this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2168            this));
2169  }
2170  mouse_watcher_->Start();
2171}
2172
2173void TabStrip::RemoveMessageLoopObserver() {
2174  mouse_watcher_.reset(NULL);
2175}
2176
2177gfx::Rect TabStrip::GetDropBounds(int drop_index,
2178                                  bool drop_before,
2179                                  bool* is_beneath) {
2180  DCHECK_NE(drop_index, -1);
2181  int center_x;
2182  if (drop_index < tab_count()) {
2183    Tab* tab = tab_at(drop_index);
2184    if (drop_before)
2185      center_x = tab->x() - (kTabHorizontalOffset / 2);
2186    else
2187      center_x = tab->x() + (tab->width() / 2);
2188  } else {
2189    Tab* last_tab = tab_at(drop_index - 1);
2190    center_x = last_tab->x() + last_tab->width() + (kTabHorizontalOffset / 2);
2191  }
2192
2193  // Mirror the center point if necessary.
2194  center_x = GetMirroredXInView(center_x);
2195
2196  // Determine the screen bounds.
2197  gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2198                      -drop_indicator_height);
2199  ConvertPointToScreen(this, &drop_loc);
2200  gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2201                        drop_indicator_height);
2202
2203  // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2204  gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2205  gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2206  *is_beneath = !display.bounds().Contains(drop_bounds);
2207  if (*is_beneath)
2208    drop_bounds.Offset(0, drop_bounds.height() + height());
2209
2210  return drop_bounds;
2211}
2212
2213void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2214  // If the UI layout is right-to-left, we need to mirror the mouse
2215  // coordinates since we calculate the drop index based on the
2216  // original (and therefore non-mirrored) positions of the tabs.
2217  const int x = GetMirroredXInView(event.x());
2218  // We don't allow replacing the urls of mini-tabs.
2219  for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
2220    Tab* tab = tab_at(i);
2221    const int tab_max_x = tab->x() + tab->width();
2222    const int hot_width = tab->width() / kTabEdgeRatioInverse;
2223    if (x < tab_max_x) {
2224      if (x < tab->x() + hot_width)
2225        SetDropIndex(i, true);
2226      else if (x >= tab_max_x - hot_width)
2227        SetDropIndex(i + 1, true);
2228      else
2229        SetDropIndex(i, false);
2230      return;
2231    }
2232  }
2233
2234  // The drop isn't over a tab, add it to the end.
2235  SetDropIndex(tab_count(), true);
2236}
2237
2238void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2239  // Let the controller know of the index update.
2240  controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2241
2242  if (tab_data_index == -1) {
2243    if (drop_info_.get())
2244      drop_info_.reset(NULL);
2245    return;
2246  }
2247
2248  if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2249      drop_info_->drop_before == drop_before) {
2250    return;
2251  }
2252
2253  bool is_beneath;
2254  gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2255                                        &is_beneath);
2256
2257  if (!drop_info_.get()) {
2258    drop_info_.reset(
2259        new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2260  } else {
2261    drop_info_->drop_index = tab_data_index;
2262    drop_info_->drop_before = drop_before;
2263    if (is_beneath == drop_info_->point_down) {
2264      drop_info_->point_down = !is_beneath;
2265      drop_info_->arrow_view->SetImage(
2266          GetDropArrowImage(drop_info_->point_down));
2267    }
2268  }
2269
2270  // Reposition the window. Need to show it too as the window is initially
2271  // hidden.
2272  drop_info_->arrow_window->SetBounds(drop_bounds);
2273  drop_info_->arrow_window->Show();
2274}
2275
2276int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2277  const int source_ops = event.source_operations();
2278  if (source_ops & ui::DragDropTypes::DRAG_COPY)
2279    return ui::DragDropTypes::DRAG_COPY;
2280  if (source_ops & ui::DragDropTypes::DRAG_LINK)
2281    return ui::DragDropTypes::DRAG_LINK;
2282  return ui::DragDropTypes::DRAG_MOVE;
2283}
2284
2285// static
2286gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2287  return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2288      is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2289}
2290
2291// TabStrip::DropInfo ----------------------------------------------------------
2292
2293TabStrip::DropInfo::DropInfo(int drop_index,
2294                             bool drop_before,
2295                             bool point_down,
2296                             views::Widget* context)
2297    : drop_index(drop_index),
2298      drop_before(drop_before),
2299      point_down(point_down),
2300      file_supported(true) {
2301  arrow_view = new views::ImageView;
2302  arrow_view->SetImage(GetDropArrowImage(point_down));
2303
2304  arrow_window = new views::Widget;
2305  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2306  params.keep_on_top = true;
2307  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2308  params.accept_events = false;
2309  params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2310  params.context = context->GetNativeWindow();
2311  arrow_window->Init(params);
2312  arrow_window->SetContentsView(arrow_view);
2313}
2314
2315TabStrip::DropInfo::~DropInfo() {
2316  // Close eventually deletes the window, which deletes arrow_view too.
2317  arrow_window->Close();
2318}
2319
2320///////////////////////////////////////////////////////////////////////////////
2321
2322void TabStrip::PrepareForAnimation() {
2323  if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2324    for (int i = 0; i < tab_count(); ++i)
2325      tab_at(i)->set_dragging(false);
2326  }
2327}
2328
2329void TabStrip::GenerateIdealBounds() {
2330  int new_tab_y = 0;
2331
2332  if (touch_layout_) {
2333    if (tabs_.view_size() == 0)
2334      return;
2335
2336    int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2337        kNewTabButtonHorizontalOffset;
2338    newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2339    return;
2340  }
2341
2342  GetDesiredTabWidths(tab_count(), GetMiniTabCount(),
2343                      &current_unselected_width_, &current_selected_width_);
2344
2345  // NOTE: This currently assumes a tab's height doesn't differ based on
2346  // selected state or the number of tabs in the strip!
2347  int tab_height = Tab::GetStandardSize().height();
2348  int first_non_mini_index = 0;
2349  double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index);
2350  for (int i = first_non_mini_index; i < tab_count(); ++i) {
2351    Tab* tab = tab_at(i);
2352    DCHECK(!tab->data().mini);
2353    double tab_width =
2354        tab->IsActive() ? current_selected_width_ : current_unselected_width_;
2355    double end_of_tab = tab_x + tab_width;
2356    int rounded_tab_x = Round(tab_x);
2357    tabs_.set_ideal_bounds(
2358        i,
2359        gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2360                  tab_height));
2361    tab_x = end_of_tab + kTabHorizontalOffset;
2362  }
2363
2364  // Update bounds of new tab button.
2365  int new_tab_x;
2366  if ((Tab::GetStandardSize().width() - Round(current_unselected_width_)) > 1 &&
2367      !in_tab_close_) {
2368    // We're shrinking tabs, so we need to anchor the New Tab button to the
2369    // right edge of the TabStrip's bounds, rather than the right edge of the
2370    // right-most Tab, otherwise it'll bounce when animating.
2371    new_tab_x = width() - newtab_button_bounds_.width();
2372  } else {
2373    new_tab_x = Round(tab_x - kTabHorizontalOffset) +
2374        kNewTabButtonHorizontalOffset;
2375  }
2376  newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2377}
2378
2379int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) {
2380  int next_x = 0;
2381  int mini_width = Tab::GetMiniWidth();
2382  int tab_height = Tab::GetStandardSize().height();
2383  int index = 0;
2384  for (; index < tab_count() && tab_at(index)->data().mini; ++index) {
2385    tabs_.set_ideal_bounds(index, gfx::Rect(next_x, 0, mini_width, tab_height));
2386    next_x += mini_width + kTabHorizontalOffset;
2387  }
2388  if (index > 0 && index < tab_count())
2389    next_x += kMiniToNonMiniGap;
2390  if (first_non_mini_index)
2391    *first_non_mini_index = index;
2392  return next_x;
2393}
2394
2395void TabStrip::StartResizeLayoutAnimation() {
2396  PrepareForAnimation();
2397  GenerateIdealBounds();
2398  AnimateToIdealBounds();
2399}
2400
2401void TabStrip::StartMiniTabAnimation() {
2402  in_tab_close_ = false;
2403  available_width_for_tabs_ = -1;
2404
2405  PrepareForAnimation();
2406
2407  GenerateIdealBounds();
2408  AnimateToIdealBounds();
2409}
2410
2411void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2412  // The user initiated the close. We want to persist the bounds of all the
2413  // existing tabs, so we manually shift ideal_bounds then animate.
2414  Tab* tab_closing = tab_at(model_index);
2415  int delta = tab_closing->width() + kTabHorizontalOffset;
2416  // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2417  // add the extra padding.
2418  DCHECK_LT(model_index, tab_count() - 1);
2419  if (tab_closing->data().mini && !tab_at(model_index + 1)->data().mini)
2420    delta += kMiniToNonMiniGap;
2421
2422  for (int i = model_index + 1; i < tab_count(); ++i) {
2423    gfx::Rect bounds = ideal_bounds(i);
2424    bounds.set_x(bounds.x() - delta);
2425    tabs_.set_ideal_bounds(i, bounds);
2426  }
2427
2428  // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2429  // overflow tabs that will be able to animate into the strip, in which case
2430  // the new tab button should stay where it is.
2431  newtab_button_bounds_.set_x(std::min(
2432      width() - newtab_button_bounds_.width(),
2433      ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset));
2434
2435  PrepareForAnimation();
2436
2437  tab_closing->set_closing(true);
2438
2439  // We still need to paint the tab until we actually remove it. Put it in
2440  // tabs_closing_map_ so we can find it.
2441  RemoveTabFromViewModel(model_index);
2442
2443  AnimateToIdealBounds();
2444
2445  gfx::Rect tab_bounds = tab_closing->bounds();
2446  tab_bounds.set_width(0);
2447  bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2448
2449  // Register delegate to do cleanup when done, BoundsAnimator takes
2450  // ownership of RemoveTabDelegate.
2451  bounds_animator_.SetAnimationDelegate(
2452      tab_closing,
2453      scoped_ptr<gfx::AnimationDelegate>(
2454          new RemoveTabDelegate(this, tab_closing)));
2455}
2456
2457bool TabStrip::IsPointInTab(Tab* tab,
2458                            const gfx::Point& point_in_tabstrip_coords) {
2459  gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2460  View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2461  return tab->HitTestPoint(point_in_tab_coords);
2462}
2463
2464int TabStrip::GetStartXForNormalTabs() const {
2465  int mini_tab_count = GetMiniTabCount();
2466  if (mini_tab_count == 0)
2467    return 0;
2468  return mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset) +
2469      kMiniToNonMiniGap;
2470}
2471
2472Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2473  if (touch_layout_) {
2474    int active_tab_index = touch_layout_->active_index();
2475    if (active_tab_index != -1) {
2476      Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2477      if (!tab)
2478        tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2479      return tab;
2480    }
2481    if (tab_count())
2482      return FindTabForEventFrom(point, 0, 1);
2483  } else {
2484    for (int i = 0; i < tab_count(); ++i) {
2485      if (IsPointInTab(tab_at(i), point))
2486        return tab_at(i);
2487    }
2488  }
2489  return NULL;
2490}
2491
2492Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2493                                   int start,
2494                                   int delta) {
2495  // |start| equals tab_count() when there are only pinned tabs.
2496  if (start == tab_count())
2497    start += delta;
2498  for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2499    if (IsPointInTab(tab_at(i), point))
2500      return tab_at(i);
2501  }
2502  return NULL;
2503}
2504
2505views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2506  // The display order doesn't necessarily match the child list order, so we
2507  // walk the display list hit-testing Tabs. Since the active tab always
2508  // renders on top of adjacent tabs, it needs to be hit-tested before any
2509  // left-adjacent Tab, so we look ahead for it as we walk.
2510  for (int i = 0; i < tab_count(); ++i) {
2511    Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2512    if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2513      return next_tab;
2514    if (IsPointInTab(tab_at(i), point))
2515      return tab_at(i);
2516  }
2517
2518  return NULL;
2519}
2520
2521std::vector<int> TabStrip::GetTabXCoordinates() {
2522  std::vector<int> results;
2523  for (int i = 0; i < tab_count(); ++i)
2524    results.push_back(ideal_bounds(i).x());
2525  return results;
2526}
2527
2528void TabStrip::SwapLayoutIfNecessary() {
2529  bool needs_touch = NeedsTouchLayout();
2530  bool using_touch = touch_layout_ != NULL;
2531  if (needs_touch == using_touch)
2532    return;
2533
2534  if (needs_touch) {
2535    gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2536    tab_size.set_width(Tab::GetTouchWidth());
2537    touch_layout_.reset(new StackedTabStripLayout(
2538                            tab_size,
2539                            kTabHorizontalOffset,
2540                            kStackedPadding,
2541                            kMaxStackedCount,
2542                            &tabs_));
2543    touch_layout_->SetWidth(tab_area_width());
2544    // This has to be after SetWidth() as SetWidth() is going to reset the
2545    // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2546    // many mini-tabs there are).
2547    GenerateIdealBoundsForMiniTabs(NULL);
2548    touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(),
2549                                    GetMiniTabCount());
2550    touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2551  } else {
2552    touch_layout_.reset();
2553  }
2554  PrepareForAnimation();
2555  GenerateIdealBounds();
2556  SetTabVisibility();
2557  AnimateToIdealBounds();
2558}
2559
2560bool TabStrip::NeedsTouchLayout() const {
2561  if (!stacked_layout_)
2562    return false;
2563
2564  int mini_tab_count = GetMiniTabCount();
2565  int normal_count = tab_count() - mini_tab_count;
2566  if (normal_count <= 1 || normal_count == mini_tab_count)
2567    return false;
2568  int x = GetStartXForNormalTabs();
2569  int available_width = tab_area_width() - x;
2570  return (Tab::GetTouchWidth() * normal_count +
2571          kTabHorizontalOffset * (normal_count - 1)) > available_width;
2572}
2573
2574void TabStrip::SetResetToShrinkOnExit(bool value) {
2575  if (!adjust_layout_)
2576    return;
2577
2578  if (value && !stacked_layout_)
2579    value = false;  // We're already using shrink (not stacked) layout.
2580
2581  if (value == reset_to_shrink_on_exit_)
2582    return;
2583
2584  reset_to_shrink_on_exit_ = value;
2585  // Add an observer so we know when the mouse moves out of the tabstrip.
2586  if (reset_to_shrink_on_exit_)
2587    AddMessageLoopObserver();
2588  else
2589    RemoveMessageLoopObserver();
2590}
2591
2592void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
2593  if (sender == newtab_button_) {
2594    content::RecordAction(UserMetricsAction("NewTab_Button"));
2595    UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
2596                              TabStripModel::NEW_TAB_ENUM_COUNT);
2597    if (event.IsMouseEvent()) {
2598      const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
2599      if (mouse.IsOnlyMiddleMouseButton()) {
2600        base::string16 clipboard_text = GetClipboardText();
2601        if (!clipboard_text.empty())
2602          controller()->CreateNewTabWithLocation(clipboard_text);
2603        return;
2604      }
2605    }
2606
2607    controller()->CreateNewTab();
2608    if (event.type() == ui::ET_GESTURE_TAP)
2609      TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
2610  }
2611}
2612
2613// Overridden to support automation. See automation_proxy_uitest.cc.
2614const views::View* TabStrip::GetViewByID(int view_id) const {
2615  if (tab_count() > 0) {
2616    if (view_id == VIEW_ID_TAB_LAST)
2617      return tab_at(tab_count() - 1);
2618    if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
2619      int index = view_id - VIEW_ID_TAB_0;
2620      return (index >= 0 && index < tab_count()) ? tab_at(index) : NULL;
2621    }
2622  }
2623
2624  return View::GetViewByID(view_id);
2625}
2626
2627bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
2628  UpdateStackedLayoutFromMouseEvent(this, event);
2629  // We can't return true here, else clicking in an empty area won't drag the
2630  // window.
2631  return false;
2632}
2633
2634bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
2635  ContinueDrag(this, event);
2636  return true;
2637}
2638
2639void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
2640  EndDrag(END_DRAG_COMPLETE);
2641  UpdateStackedLayoutFromMouseEvent(this, event);
2642}
2643
2644void TabStrip::OnMouseCaptureLost() {
2645  EndDrag(END_DRAG_CAPTURE_LOST);
2646}
2647
2648void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
2649  UpdateStackedLayoutFromMouseEvent(this, event);
2650}
2651
2652void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
2653  SetResetToShrinkOnExit(true);
2654}
2655
2656void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
2657  SetResetToShrinkOnExit(false);
2658  switch (event->type()) {
2659    case ui::ET_GESTURE_SCROLL_END:
2660    case ui::ET_SCROLL_FLING_START:
2661    case ui::ET_GESTURE_END:
2662      EndDrag(END_DRAG_COMPLETE);
2663      if (adjust_layout_) {
2664        SetStackedLayout(true);
2665        controller_->StackedLayoutMaybeChanged();
2666      }
2667      break;
2668
2669    case ui::ET_GESTURE_LONG_PRESS:
2670      if (drag_controller_.get())
2671        drag_controller_->SetMoveBehavior(TabDragController::REORDER);
2672      break;
2673
2674    case ui::ET_GESTURE_LONG_TAP: {
2675      EndDrag(END_DRAG_CANCEL);
2676      gfx::Point local_point = event->location();
2677      Tab* tab = FindTabForEvent(local_point);
2678      if (tab) {
2679        ConvertPointToScreen(this, &local_point);
2680        ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
2681      }
2682      break;
2683    }
2684
2685    case ui::ET_GESTURE_SCROLL_UPDATE:
2686      ContinueDrag(this, *event);
2687      break;
2688
2689    case ui::ET_GESTURE_TAP_DOWN:
2690      EndDrag(END_DRAG_CANCEL);
2691      break;
2692
2693    case ui::ET_GESTURE_TAP: {
2694      const int active_index = controller_->GetActiveIndex();
2695      DCHECK_NE(-1, active_index);
2696      Tab* active_tab = tab_at(active_index);
2697      TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
2698      if (active_tab->tab_activated_with_last_tap_down())
2699        action = TouchUMA::GESTURE_TABSWITCH_TAP;
2700      TouchUMA::RecordGestureAction(action);
2701      break;
2702    }
2703
2704    default:
2705      break;
2706  }
2707  event->SetHandled();
2708}
2709
2710views::View* TabStrip::TargetForRect(views::View* root, const gfx::Rect& rect) {
2711  CHECK_EQ(root, this);
2712
2713  if (!views::UsePointBasedTargeting(rect))
2714    return views::ViewTargeterDelegate::TargetForRect(root, rect);
2715  const gfx::Point point(rect.CenterPoint());
2716
2717  if (!touch_layout_) {
2718    // Return any view that isn't a Tab or this TabStrip immediately. We don't
2719    // want to interfere.
2720    views::View* v = views::ViewTargeterDelegate::TargetForRect(root, rect);
2721    if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
2722      return v;
2723
2724    views::View* tab = FindTabHitByPoint(point);
2725    if (tab)
2726      return tab;
2727  } else {
2728    if (newtab_button_->visible()) {
2729      views::View* view =
2730          ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
2731      if (view)
2732        return view;
2733    }
2734    Tab* tab = FindTabForEvent(point);
2735    if (tab)
2736      return ConvertPointToViewAndGetEventHandler(this, tab, point);
2737  }
2738  return this;
2739}
2740