tab_strip.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 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#include <algorithm>
8
9#include "base/compiler_specific.h"
10#include "base/stl_util-inl.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/defaults.h"
13#include "chrome/browser/themes/browser_theme_provider.h"
14#include "chrome/browser/ui/view_ids.h"
15#include "chrome/browser/ui/views/tabs/tab.h"
16#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
17#include "grit/generated_resources.h"
18#include "grit/theme_resources.h"
19#include "ui/base/animation/animation_container.h"
20#include "ui/base/dragdrop/drag_drop_types.h"
21#include "ui/base/l10n/l10n_util.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "ui/gfx/canvas_skia.h"
24#include "ui/gfx/path.h"
25#include "ui/gfx/size.h"
26#include "views/controls/image_view.h"
27#include "views/widget/default_theme_provider.h"
28#include "views/window/non_client_view.h"
29#include "views/window/window.h"
30
31#if defined(OS_WIN)
32#include "views/widget/monitor_win.h"
33#include "views/widget/widget_win.h"
34#elif defined(OS_LINUX)
35#include "views/widget/widget_gtk.h"
36#endif
37
38#undef min
39#undef max
40
41#if defined(COMPILER_GCC)
42// Squash false positive signed overflow warning in GenerateStartAndEndWidths
43// when doing 'start_tab_count < end_tab_count'.
44#pragma GCC diagnostic ignored "-Wstrict-overflow"
45#endif
46
47using views::DropTargetEvent;
48
49static const int kNewTabButtonHOffset = -5;
50static const int kNewTabButtonVOffset = 5;
51static const int kSuspendAnimationsTimeMs = 200;
52static const int kTabHOffset = -16;
53static const int kTabStripAnimationVSlop = 40;
54
55// Size of the drop indicator.
56static int drop_indicator_width;
57static int drop_indicator_height;
58
59static inline int Round(double x) {
60  // Why oh why is this not in a standard header?
61  return static_cast<int>(floor(x + 0.5));
62}
63
64namespace {
65
66///////////////////////////////////////////////////////////////////////////////
67// NewTabButton
68//
69//  A subclass of button that hit-tests to the shape of the new tab button.
70
71class NewTabButton : public views::ImageButton {
72 public:
73  explicit NewTabButton(views::ButtonListener* listener)
74      : views::ImageButton(listener) {
75  }
76  virtual ~NewTabButton() {}
77
78 protected:
79  // Overridden from views::View:
80  virtual bool HasHitTestMask() const {
81    // When the button is sized to the top of the tab strip we want the user to
82    // be able to click on complete bounds, and so don't return a custom hit
83    // mask.
84    return !browser_defaults::kSizeTabButtonToTopOfTabStrip;
85  }
86  virtual void GetHitTestMask(gfx::Path* path) const {
87    DCHECK(path);
88
89    SkScalar w = SkIntToScalar(width());
90
91    // These values are defined by the shape of the new tab bitmap. Should that
92    // bitmap ever change, these values will need to be updated. They're so
93    // custom it's not really worth defining constants for.
94    path->moveTo(0, 1);
95    path->lineTo(w - 7, 1);
96    path->lineTo(w - 4, 4);
97    path->lineTo(w, 16);
98    path->lineTo(w - 1, 17);
99    path->lineTo(7, 17);
100    path->lineTo(4, 13);
101    path->lineTo(0, 1);
102    path->close();
103  }
104
105 private:
106  DISALLOW_COPY_AND_ASSIGN(NewTabButton);
107};
108
109}  // namespace
110
111///////////////////////////////////////////////////////////////////////////////
112// TabStrip, public:
113
114// static
115const int TabStrip::mini_to_non_mini_gap_ = 3;
116
117TabStrip::TabStrip(TabStripController* controller)
118    : BaseTabStrip(controller, BaseTabStrip::HORIZONTAL_TAB_STRIP),
119      current_unselected_width_(Tab::GetStandardSize().width()),
120      current_selected_width_(Tab::GetStandardSize().width()),
121      available_width_for_tabs_(-1),
122      in_tab_close_(false),
123      animation_container_(new ui::AnimationContainer()) {
124  Init();
125}
126
127TabStrip::~TabStrip() {
128  // The animations may reference the tabs. Shut down the animation before we
129  // delete the tabs.
130  StopAnimating(false);
131
132  DestroyDragController();
133
134  // Make sure we unhook ourselves as a message loop observer so that we don't
135  // crash in the case where the user closes the window after closing a tab
136  // but before moving the mouse.
137  RemoveMessageLoopObserver();
138
139  // The children (tabs) may callback to us from their destructor. Delete them
140  // so that if they call back we aren't in a weird state.
141  RemoveAllChildViews(true);
142}
143
144void TabStrip::InitTabStripButtons() {
145  newtab_button_ = new NewTabButton(this);
146  if (browser_defaults::kSizeTabButtonToTopOfTabStrip) {
147    newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
148                                      views::ImageButton::ALIGN_BOTTOM);
149  }
150  LoadNewTabButtonImage();
151  newtab_button_->SetAccessibleName(
152      l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
153  AddChildView(newtab_button_);
154}
155
156gfx::Rect TabStrip::GetNewTabButtonBounds() {
157  return newtab_button_->bounds();
158}
159
160void TabStrip::MouseMovedOutOfView() {
161  ResizeLayoutTabs();
162}
163
164////////////////////////////////////////////////////////////////////////////////
165// TabStrip, BaseTabStrip implementation:
166
167void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
168  for (int i = 0; i < tab_count(); ++i)
169    GetTabAtTabDataIndex(i)->set_background_offset(offset);
170}
171
172bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
173  views::View* v = GetEventHandlerForPoint(point);
174
175  // If there is no control at this location, claim the hit was in the title
176  // bar to get a move action.
177  if (v == this)
178    return true;
179
180  // Check to see if the point is within the non-button parts of the new tab
181  // button. The button has a non-rectangular shape, so if it's not in the
182  // visual portions of the button we treat it as a click to the caption.
183  gfx::Point point_in_newtab_coords(point);
184  View::ConvertPointToView(this, newtab_button_, &point_in_newtab_coords);
185  if (newtab_button_->bounds().Contains(point) &&
186      !newtab_button_->HitTest(point_in_newtab_coords)) {
187    return true;
188  }
189
190  // All other regions, including the new Tab button, should be considered part
191  // of the containing Window's client area so that regular events can be
192  // processed for them.
193  return false;
194}
195
196void TabStrip::PrepareForCloseAt(int model_index) {
197  if (!in_tab_close_ && IsAnimating()) {
198    // Cancel any current animations. We do this as remove uses the current
199    // ideal bounds and we need to know ideal bounds is in a good state.
200    StopAnimating(true);
201  }
202
203  int model_count = GetModelCount();
204  if (model_index + 1 != model_count && model_count > 1) {
205    // The user is about to close a tab other than the last tab. Set
206    // available_width_for_tabs_ so that if we do a layout we don't position a
207    // tab past the end of the second to last tab. We do this so that as the
208    // user closes tabs with the mouse a tab continues to fall under the mouse.
209    available_width_for_tabs_ = GetAvailableWidthForTabs(
210        GetTabAtModelIndex(model_count - 2));
211  }
212
213  in_tab_close_ = true;
214  AddMessageLoopObserver();
215}
216
217void TabStrip::RemoveTabAt(int model_index) {
218  if (in_tab_close_ && model_index != GetModelCount())
219    StartMouseInitiatedRemoveTabAnimation(model_index);
220  else
221    StartRemoveTabAnimation(model_index);
222}
223
224void TabStrip::SelectTabAt(int old_model_index, int new_model_index) {
225  // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
226  // a different size to the selected ones.
227  bool tiny_tabs = current_unselected_width_ != current_selected_width_;
228  if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
229    DoLayout();
230  } else {
231    SchedulePaint();
232  }
233
234  if (old_model_index >= 0) {
235    GetTabAtTabDataIndex(ModelIndexToTabIndex(old_model_index))->
236        StopMiniTabTitleAnimation();
237  }
238}
239
240void TabStrip::TabTitleChangedNotLoading(int model_index) {
241  Tab* tab = GetTabAtModelIndex(model_index);
242  if (tab->data().mini && !tab->IsSelected())
243    tab->StartMiniTabTitleAnimation();
244}
245
246void TabStrip::StartHighlight(int model_index) {
247  GetTabAtModelIndex(model_index)->StartPulse();
248}
249
250void TabStrip::StopAllHighlighting() {
251  for (int i = 0; i < tab_count(); ++i)
252    GetTabAtTabDataIndex(i)->StopPulse();
253}
254
255BaseTab* TabStrip::CreateTabForDragging() {
256  Tab* tab = new Tab(NULL);
257  // Make sure the dragged tab shares our theme provider. We need to explicitly
258  // do this as during dragging there isn't a theme provider.
259  tab->set_theme_provider(GetThemeProvider());
260  return tab;
261}
262
263///////////////////////////////////////////////////////////////////////////////
264// TabStrip, views::View overrides:
265
266void TabStrip::PaintChildren(gfx::Canvas* canvas) {
267  // Tabs are painted in reverse order, so they stack to the left.
268  Tab* selected_tab = NULL;
269  Tab* dragging_tab = NULL;
270
271  for (int i = tab_count() - 1; i >= 0; --i) {
272    Tab* tab = GetTabAtTabDataIndex(i);
273    // We must ask the _Tab's_ model, not ourselves, because in some situations
274    // the model will be different to this object, e.g. when a Tab is being
275    // removed after its TabContents has been destroyed.
276    if (tab->dragging()) {
277      dragging_tab = tab;
278    } else if (!tab->IsSelected()) {
279      tab->Paint(canvas);
280    } else {
281      selected_tab = tab;
282    }
283  }
284
285  if (GetWindow()->non_client_view()->UseNativeFrame()) {
286    // Make sure unselected tabs are somewhat transparent.
287    SkPaint paint;
288    paint.setColor(SkColorSetARGB(200, 255, 255, 255));
289    paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
290    paint.setStyle(SkPaint::kFill_Style);
291    canvas->DrawRectInt(0, 0, width(),
292        height() - 2,  // Visible region that overlaps the toolbar.
293        paint);
294  }
295
296  // Paint the selected tab last, so it overlaps all the others.
297  if (selected_tab)
298    selected_tab->Paint(canvas);
299
300  // Paint the New Tab button.
301  newtab_button_->Paint(canvas);
302
303  // And the dragged tab.
304  if (dragging_tab)
305    dragging_tab->Paint(canvas);
306}
307
308// Overridden to support automation. See automation_proxy_uitest.cc.
309const views::View* TabStrip::GetViewByID(int view_id) const {
310  if (tab_count() > 0) {
311    if (view_id == VIEW_ID_TAB_LAST) {
312      return GetTabAtTabDataIndex(tab_count() - 1);
313    } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
314      int index = view_id - VIEW_ID_TAB_0;
315      if (index >= 0 && index < tab_count()) {
316        return GetTabAtTabDataIndex(index);
317      } else {
318        return NULL;
319      }
320    }
321  }
322
323  return View::GetViewByID(view_id);
324}
325
326gfx::Size TabStrip::GetPreferredSize() {
327  return gfx::Size(0, Tab::GetMinimumUnselectedSize().height());
328}
329
330void TabStrip::OnDragEntered(const DropTargetEvent& event) {
331  // Force animations to stop, otherwise it makes the index calculation tricky.
332  StopAnimating(true);
333
334  UpdateDropIndex(event);
335}
336
337int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
338  UpdateDropIndex(event);
339  return GetDropEffect(event);
340}
341
342void TabStrip::OnDragExited() {
343  SetDropIndex(-1, false);
344}
345
346int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
347  if (!drop_info_.get())
348    return ui::DragDropTypes::DRAG_NONE;
349
350  const int drop_index = drop_info_->drop_index;
351  const bool drop_before = drop_info_->drop_before;
352
353  // Hide the drop indicator.
354  SetDropIndex(-1, false);
355
356  GURL url;
357  string16 title;
358  if (!event.data().GetURLAndTitle(&url, &title) || !url.is_valid())
359    return ui::DragDropTypes::DRAG_NONE;
360
361  controller()->PerformDrop(drop_before, drop_index, url);
362
363  return GetDropEffect(event);
364}
365
366AccessibilityTypes::Role TabStrip::GetAccessibleRole() {
367  return AccessibilityTypes::ROLE_PAGETABLIST;
368}
369
370views::View* TabStrip::GetEventHandlerForPoint(const gfx::Point& point) {
371  // Return any view that isn't a Tab or this TabStrip immediately. We don't
372  // want to interfere.
373  views::View* v = View::GetEventHandlerForPoint(point);
374  if (v && v != this && v->GetClassName() != Tab::kViewClassName)
375    return v;
376
377  // The display order doesn't necessarily match the child list order, so we
378  // walk the display list hit-testing Tabs. Since the selected tab always
379  // renders on top of adjacent tabs, it needs to be hit-tested before any
380  // left-adjacent Tab, so we look ahead for it as we walk.
381  for (int i = 0; i < tab_count(); ++i) {
382    Tab* next_tab = i < (tab_count() - 1) ? GetTabAtTabDataIndex(i + 1) : NULL;
383    if (next_tab && next_tab->IsSelected() && IsPointInTab(next_tab, point))
384      return next_tab;
385    Tab* tab = GetTabAtTabDataIndex(i);
386    if (IsPointInTab(tab, point))
387      return tab;
388  }
389
390  // No need to do any floating view stuff, we don't use them in the TabStrip.
391  return this;
392}
393
394void TabStrip::OnThemeChanged() {
395  LoadNewTabButtonImage();
396}
397
398BaseTab* TabStrip::CreateTab() {
399  Tab* tab = new Tab(this);
400  tab->set_animation_container(animation_container_.get());
401  return tab;
402}
403
404void TabStrip::StartInsertTabAnimation(int model_index, bool foreground) {
405  PrepareForAnimation();
406
407  // The TabStrip can now use its entire width to lay out Tabs.
408  in_tab_close_ = false;
409  available_width_for_tabs_ = -1;
410
411  GenerateIdealBounds();
412
413  int tab_data_index = ModelIndexToTabIndex(model_index);
414  BaseTab* tab = base_tab_at_tab_index(tab_data_index);
415  if (model_index == 0) {
416    tab->SetBounds(0, ideal_bounds(tab_data_index).y(), 0,
417                   ideal_bounds(tab_data_index).height());
418  } else {
419    BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1);
420    tab->SetBounds(last_tab->bounds().right() + kTabHOffset,
421                   ideal_bounds(tab_data_index).y(), 0,
422                   ideal_bounds(tab_data_index).height());
423  }
424
425  AnimateToIdealBounds();
426}
427
428void TabStrip::AnimateToIdealBounds() {
429  for (int i = 0; i < tab_count(); ++i) {
430    Tab* tab = GetTabAtTabDataIndex(i);
431    if (!tab->closing() && !tab->dragging())
432      bounds_animator().AnimateViewTo(tab, ideal_bounds(i));
433  }
434
435  bounds_animator().AnimateViewTo(newtab_button_, newtab_button_bounds_);
436}
437
438bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
439  return in_tab_close_;
440}
441
442void TabStrip::DoLayout() {
443  BaseTabStrip::DoLayout();
444
445  newtab_button_->SetBoundsRect(newtab_button_bounds_);
446}
447
448void TabStrip::ViewHierarchyChanged(bool is_add,
449                                    views::View* parent,
450                                    views::View* child) {
451  if (is_add && child == this)
452    InitTabStripButtons();
453}
454
455///////////////////////////////////////////////////////////////////////////////
456// TabStrip, Tab::Delegate implementation:
457
458bool TabStrip::IsTabSelected(const BaseTab* btr) const {
459  const Tab* tab = static_cast<const Tab*>(btr);
460  return !tab->closing() && BaseTabStrip::IsTabSelected(btr);
461}
462
463///////////////////////////////////////////////////////////////////////////////
464// TabStrip, views::BaseButton::ButtonListener implementation:
465
466void TabStrip::ButtonPressed(views::Button* sender, const views::Event& event) {
467  if (sender == newtab_button_)
468    controller()->CreateNewTab();
469}
470
471///////////////////////////////////////////////////////////////////////////////
472// TabStrip, private:
473
474void TabStrip::Init() {
475  SetID(VIEW_ID_TAB_STRIP);
476  newtab_button_bounds_.SetRect(0, 0, kNewTabButtonWidth, kNewTabButtonHeight);
477  if (browser_defaults::kSizeTabButtonToTopOfTabStrip) {
478    newtab_button_bounds_.set_height(
479        kNewTabButtonHeight + kNewTabButtonVOffset);
480  }
481  if (drop_indicator_width == 0) {
482    // Direction doesn't matter, both images are the same size.
483    SkBitmap* drop_image = GetDropArrowImage(true);
484    drop_indicator_width = drop_image->width();
485    drop_indicator_height = drop_image->height();
486  }
487}
488
489void TabStrip::LoadNewTabButtonImage() {
490  ui::ThemeProvider* tp = GetThemeProvider();
491
492  // If we don't have a theme provider yet, it means we do not have a
493  // root view, and are therefore in a test.
494  bool in_test = false;
495  if (tp == NULL) {
496    tp = new views::DefaultThemeProvider();
497    in_test = true;
498  }
499
500  SkBitmap* bitmap = tp->GetBitmapNamed(IDR_NEWTAB_BUTTON);
501  SkColor color = tp->GetColor(BrowserThemeProvider::COLOR_BUTTON_BACKGROUND);
502  SkBitmap* background = tp->GetBitmapNamed(
503      IDR_THEME_WINDOW_CONTROL_BACKGROUND);
504
505  newtab_button_->SetImage(views::CustomButton::BS_NORMAL, bitmap);
506  newtab_button_->SetImage(views::CustomButton::BS_PUSHED,
507                           tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_P));
508  newtab_button_->SetImage(views::CustomButton::BS_HOT,
509                           tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_H));
510  newtab_button_->SetBackground(color, background,
511                                tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_MASK));
512  if (in_test)
513    delete tp;
514}
515
516Tab* TabStrip::GetTabAtTabDataIndex(int tab_data_index) const {
517  return static_cast<Tab*>(base_tab_at_tab_index(tab_data_index));
518}
519
520Tab* TabStrip::GetTabAtModelIndex(int model_index) const {
521  return GetTabAtTabDataIndex(ModelIndexToTabIndex(model_index));
522}
523
524void TabStrip::GetCurrentTabWidths(double* unselected_width,
525                                   double* selected_width) const {
526  *unselected_width = current_unselected_width_;
527  *selected_width = current_selected_width_;
528}
529
530void TabStrip::GetDesiredTabWidths(int tab_count,
531                                   int mini_tab_count,
532                                   double* unselected_width,
533                                   double* selected_width) const {
534  DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
535  const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
536  const double min_selected_width = Tab::GetMinimumSelectedSize().width();
537
538  *unselected_width = min_unselected_width;
539  *selected_width = min_selected_width;
540
541  if (tab_count == 0) {
542    // Return immediately to avoid divide-by-zero below.
543    return;
544  }
545
546  // Determine how much space we can actually allocate to tabs.
547  int available_width;
548  if (available_width_for_tabs_ < 0) {
549    available_width = width();
550    available_width -= (kNewTabButtonHOffset + newtab_button_bounds_.width());
551  } else {
552    // Interesting corner case: if |available_width_for_tabs_| > the result
553    // of the calculation in the conditional arm above, the strip is in
554    // overflow.  We can either use the specified width or the true available
555    // width here; the first preserves the consistent "leave the last tab under
556    // the user's mouse so they can close many tabs" behavior at the cost of
557    // prolonging the glitchy appearance of the overflow state, while the second
558    // gets us out of overflow as soon as possible but forces the user to move
559    // their mouse for a few tabs' worth of closing.  We choose visual
560    // imperfection over behavioral imperfection and select the first option.
561    available_width = available_width_for_tabs_;
562  }
563
564  if (mini_tab_count > 0) {
565    available_width -= mini_tab_count * (Tab::GetMiniWidth() + kTabHOffset);
566    tab_count -= mini_tab_count;
567    if (tab_count == 0) {
568      *selected_width = *unselected_width = Tab::GetStandardSize().width();
569      return;
570    }
571    // Account for gap between the last mini-tab and first non-mini-tab.
572    available_width -= mini_to_non_mini_gap_;
573  }
574
575  // Calculate the desired tab widths by dividing the available space into equal
576  // portions.  Don't let tabs get larger than the "standard width" or smaller
577  // than the minimum width for each type, respectively.
578  const int total_offset = kTabHOffset * (tab_count - 1);
579  const double desired_tab_width = std::min((static_cast<double>(
580      available_width - total_offset) / static_cast<double>(tab_count)),
581      static_cast<double>(Tab::GetStandardSize().width()));
582  *unselected_width = std::max(desired_tab_width, min_unselected_width);
583  *selected_width = std::max(desired_tab_width, min_selected_width);
584
585  // When there are multiple tabs, we'll have one selected and some unselected
586  // tabs.  If the desired width was between the minimum sizes of these types,
587  // try to shrink the tabs with the smaller minimum.  For example, if we have
588  // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5.  If
589  // selected tabs have a minimum width of 4 and unselected tabs have a minimum
590  // width of 1, the above code would set *unselected_width = 2.5,
591  // *selected_width = 4, which results in a total width of 11.5.  Instead, we
592  // want to set *unselected_width = 2, *selected_width = 4, for a total width
593  // of 10.
594  if (tab_count > 1) {
595    if ((min_unselected_width < min_selected_width) &&
596        (desired_tab_width < min_selected_width)) {
597      // Unselected width = (total width - selected width) / (num_tabs - 1)
598      *unselected_width = std::max(static_cast<double>(
599          available_width - total_offset - min_selected_width) /
600          static_cast<double>(tab_count - 1), min_unselected_width);
601    } else if ((min_unselected_width > min_selected_width) &&
602               (desired_tab_width < min_unselected_width)) {
603      // Selected width = (total width - (unselected width * (num_tabs - 1)))
604      *selected_width = std::max(available_width - total_offset -
605          (min_unselected_width * (tab_count - 1)), min_selected_width);
606    }
607  }
608}
609
610void TabStrip::ResizeLayoutTabs() {
611  // We've been called back after the TabStrip has been emptied out (probably
612  // just prior to the window being destroyed). We need to do nothing here or
613  // else GetTabAt below will crash.
614  if (tab_count() == 0)
615    return;
616
617  // It is critically important that this is unhooked here, otherwise we will
618  // keep spying on messages forever.
619  RemoveMessageLoopObserver();
620
621  in_tab_close_ = false;
622  available_width_for_tabs_ = -1;
623  int mini_tab_count = GetMiniTabCount();
624  if (mini_tab_count == tab_count()) {
625    // Only mini-tabs, we know the tab widths won't have changed (all
626    // mini-tabs have the same width), so there is nothing to do.
627    return;
628  }
629  Tab* first_tab  = GetTabAtTabDataIndex(mini_tab_count);
630  double unselected, selected;
631  GetDesiredTabWidths(tab_count(), mini_tab_count, &unselected, &selected);
632  int w = Round(first_tab->IsSelected() ? selected : selected);
633
634  // We only want to run the animation if we're not already at the desired
635  // size.
636  if (abs(first_tab->width() - w) > 1)
637    StartResizeLayoutAnimation();
638}
639
640void TabStrip::AddMessageLoopObserver() {
641  if (!mouse_watcher_.get()) {
642    mouse_watcher_.reset(
643        new views::MouseWatcher(this, this,
644                                gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)));
645  }
646  mouse_watcher_->Start();
647}
648
649void TabStrip::RemoveMessageLoopObserver() {
650  mouse_watcher_.reset(NULL);
651}
652
653gfx::Rect TabStrip::GetDropBounds(int drop_index,
654                                  bool drop_before,
655                                  bool* is_beneath) {
656  DCHECK(drop_index != -1);
657  int center_x;
658  if (drop_index < tab_count()) {
659    Tab* tab = GetTabAtTabDataIndex(drop_index);
660    if (drop_before)
661      center_x = tab->x() - (kTabHOffset / 2);
662    else
663      center_x = tab->x() + (tab->width() / 2);
664  } else {
665    Tab* last_tab = GetTabAtTabDataIndex(drop_index - 1);
666    center_x = last_tab->x() + last_tab->width() + (kTabHOffset / 2);
667  }
668
669  // Mirror the center point if necessary.
670  center_x = GetMirroredXInView(center_x);
671
672  // Determine the screen bounds.
673  gfx::Point drop_loc(center_x - drop_indicator_width / 2,
674                      -drop_indicator_height);
675  ConvertPointToScreen(this, &drop_loc);
676  gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
677                        drop_indicator_height);
678
679  // If the rect doesn't fit on the monitor, push the arrow to the bottom.
680#if defined(OS_WIN)
681  gfx::Rect monitor_bounds = views::GetMonitorBoundsForRect(drop_bounds);
682  *is_beneath = (monitor_bounds.IsEmpty() ||
683                 !monitor_bounds.Contains(drop_bounds));
684#else
685  *is_beneath = false;
686  NOTIMPLEMENTED();
687#endif
688  if (*is_beneath)
689    drop_bounds.Offset(0, drop_bounds.height() + height());
690
691  return drop_bounds;
692}
693
694void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
695  // If the UI layout is right-to-left, we need to mirror the mouse
696  // coordinates since we calculate the drop index based on the
697  // original (and therefore non-mirrored) positions of the tabs.
698  const int x = GetMirroredXInView(event.x());
699  // We don't allow replacing the urls of mini-tabs.
700  for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
701    Tab* tab = GetTabAtTabDataIndex(i);
702    const int tab_max_x = tab->x() + tab->width();
703    const int hot_width = tab->width() / 3;
704    if (x < tab_max_x) {
705      if (x < tab->x() + hot_width)
706        SetDropIndex(i, true);
707      else if (x >= tab_max_x - hot_width)
708        SetDropIndex(i + 1, true);
709      else
710        SetDropIndex(i, false);
711      return;
712    }
713  }
714
715  // The drop isn't over a tab, add it to the end.
716  SetDropIndex(tab_count(), true);
717}
718
719void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
720  if (tab_data_index == -1) {
721    if (drop_info_.get())
722      drop_info_.reset(NULL);
723    return;
724  }
725
726  if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
727      drop_info_->drop_before == drop_before) {
728    return;
729  }
730
731  bool is_beneath;
732  gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
733                                        &is_beneath);
734
735  if (!drop_info_.get()) {
736    drop_info_.reset(new DropInfo(tab_data_index, drop_before, !is_beneath));
737  } else {
738    drop_info_->drop_index = tab_data_index;
739    drop_info_->drop_before = drop_before;
740    if (is_beneath == drop_info_->point_down) {
741      drop_info_->point_down = !is_beneath;
742      drop_info_->arrow_view->SetImage(
743          GetDropArrowImage(drop_info_->point_down));
744    }
745  }
746
747  // Reposition the window. Need to show it too as the window is initially
748  // hidden.
749
750#if defined(OS_WIN)
751  drop_info_->arrow_window->SetWindowPos(
752      HWND_TOPMOST, drop_bounds.x(), drop_bounds.y(), drop_bounds.width(),
753      drop_bounds.height(), SWP_NOACTIVATE | SWP_SHOWWINDOW);
754#else
755  drop_info_->arrow_window->SetBounds(drop_bounds);
756  drop_info_->arrow_window->Show();
757#endif
758}
759
760int TabStrip::GetDropEffect(const views::DropTargetEvent& event) {
761  const int source_ops = event.source_operations();
762  if (source_ops & ui::DragDropTypes::DRAG_COPY)
763    return ui::DragDropTypes::DRAG_COPY;
764  if (source_ops & ui::DragDropTypes::DRAG_LINK)
765    return ui::DragDropTypes::DRAG_LINK;
766  return ui::DragDropTypes::DRAG_MOVE;
767}
768
769// static
770SkBitmap* TabStrip::GetDropArrowImage(bool is_down) {
771  return ResourceBundle::GetSharedInstance().GetBitmapNamed(
772      is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
773}
774
775// TabStrip::DropInfo ----------------------------------------------------------
776
777TabStrip::DropInfo::DropInfo(int drop_index, bool drop_before, bool point_down)
778    : drop_index(drop_index),
779      drop_before(drop_before),
780      point_down(point_down) {
781  arrow_view = new views::ImageView;
782  arrow_view->SetImage(GetDropArrowImage(point_down));
783
784#if defined(OS_WIN)
785  arrow_window = new views::WidgetWin;
786  arrow_window->set_window_style(WS_POPUP);
787  arrow_window->set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE |
788                                    WS_EX_LAYERED | WS_EX_TRANSPARENT);
789#else
790  arrow_window = new views::WidgetGtk(views::WidgetGtk::TYPE_POPUP);
791  arrow_window->MakeTransparent();
792#endif
793  arrow_window->Init(
794      NULL,
795      gfx::Rect(0, 0, drop_indicator_width, drop_indicator_height));
796  arrow_window->SetContentsView(arrow_view);
797}
798
799TabStrip::DropInfo::~DropInfo() {
800  // Close eventually deletes the window, which deletes arrow_view too.
801  arrow_window->Close();
802}
803
804///////////////////////////////////////////////////////////////////////////////
805
806// Called from:
807// - BasicLayout
808// - Tab insertion/removal
809// - Tab reorder
810void TabStrip::GenerateIdealBounds() {
811  int non_closing_tab_count = 0;
812  int mini_tab_count = 0;
813  for (int i = 0; i < tab_count(); ++i) {
814    BaseTab* tab = base_tab_at_tab_index(i);
815    if (!tab->closing()) {
816      ++non_closing_tab_count;
817      if (tab->data().mini)
818        mini_tab_count++;
819    }
820  }
821
822  double unselected, selected;
823  GetDesiredTabWidths(non_closing_tab_count, mini_tab_count, &unselected,
824                      &selected);
825
826  current_unselected_width_ = unselected;
827  current_selected_width_ = selected;
828
829  // NOTE: This currently assumes a tab's height doesn't differ based on
830  // selected state or the number of tabs in the strip!
831  int tab_height = Tab::GetStandardSize().height();
832  double tab_x = 0;
833  bool last_was_mini = false;
834  for (int i = 0; i < tab_count(); ++i) {
835      Tab* tab = GetTabAtTabDataIndex(i);
836    if (!tab->closing()) {
837      double tab_width = unselected;
838      if (tab->data().mini) {
839        tab_width = Tab::GetMiniWidth();
840      } else {
841        if (last_was_mini) {
842          // Give a bigger gap between mini and non-mini tabs.
843          tab_x += mini_to_non_mini_gap_;
844        }
845        if (tab->IsSelected())
846          tab_width = selected;
847      }
848      double end_of_tab = tab_x + tab_width;
849      int rounded_tab_x = Round(tab_x);
850      set_ideal_bounds(i,
851          gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
852                    tab_height));
853      tab_x = end_of_tab + kTabHOffset;
854      last_was_mini = tab->data().mini;
855    }
856  }
857
858  // Update bounds of new tab button.
859  int new_tab_x;
860  int new_tab_y = browser_defaults::kSizeTabButtonToTopOfTabStrip ?
861      0 : kNewTabButtonVOffset;
862  if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 &&
863      !in_tab_close_) {
864    // We're shrinking tabs, so we need to anchor the New Tab button to the
865    // right edge of the TabStrip's bounds, rather than the right edge of the
866    // right-most Tab, otherwise it'll bounce when animating.
867    new_tab_x = width() - newtab_button_bounds_.width();
868  } else {
869    new_tab_x = Round(tab_x - kTabHOffset) + kNewTabButtonHOffset;
870  }
871  newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
872}
873
874void TabStrip::StartResizeLayoutAnimation() {
875  PrepareForAnimation();
876  GenerateIdealBounds();
877  AnimateToIdealBounds();
878}
879
880void TabStrip::StartMiniTabAnimation() {
881  in_tab_close_ = false;
882  available_width_for_tabs_ = -1;
883
884  PrepareForAnimation();
885
886  GenerateIdealBounds();
887  AnimateToIdealBounds();
888}
889
890void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
891  // The user initiated the close. We want to persist the bounds of all the
892  // existing tabs, so we manually shift ideal_bounds then animate.
893  int tab_data_index = ModelIndexToTabIndex(model_index);
894  DCHECK(tab_data_index != tab_count());
895  BaseTab* tab_closing = base_tab_at_tab_index(tab_data_index);
896  int delta = tab_closing->width() + kTabHOffset;
897  if (tab_closing->data().mini && model_index + 1 < GetModelCount() &&
898      !GetBaseTabAtModelIndex(model_index + 1)->data().mini) {
899    delta += mini_to_non_mini_gap_;
900  }
901
902  for (int i = tab_data_index + 1; i < tab_count(); ++i) {
903    BaseTab* tab = base_tab_at_tab_index(i);
904    if (!tab->closing()) {
905      gfx::Rect bounds = ideal_bounds(i);
906      bounds.set_x(bounds.x() - delta);
907      set_ideal_bounds(i, bounds);
908    }
909  }
910
911  newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta);
912
913  PrepareForAnimation();
914
915  // Mark the tab as closing.
916  tab_closing->set_closing(true);
917
918  AnimateToIdealBounds();
919
920  gfx::Rect tab_bounds = tab_closing->bounds();
921  if (type() == HORIZONTAL_TAB_STRIP)
922    tab_bounds.set_width(0);
923  else
924    tab_bounds.set_height(0);
925  bounds_animator().AnimateViewTo(tab_closing, tab_bounds);
926
927  // Register delegate to do cleanup when done, BoundsAnimator takes
928  // ownership of RemoveTabDelegate.
929  bounds_animator().SetAnimationDelegate(tab_closing,
930                                         CreateRemoveTabDelegate(tab_closing),
931                                         true);
932}
933
934int TabStrip::GetMiniTabCount() const {
935  int mini_count = 0;
936  for (int i = 0; i < tab_count(); ++i) {
937    if (base_tab_at_tab_index(i)->data().mini)
938      mini_count++;
939    else
940      return mini_count;
941  }
942  return mini_count;
943}
944
945int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const {
946  return last_tab->x() + last_tab->width();
947}
948
949bool TabStrip::IsPointInTab(Tab* tab,
950                            const gfx::Point& point_in_tabstrip_coords) {
951  gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
952  View::ConvertPointToView(this, tab, &point_in_tab_coords);
953  return tab->HitTest(point_in_tab_coords);
954}
955