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