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