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