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