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