• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
48 using views::DropTargetEvent;
49 
50 static const int kNewTabButtonHOffset = -5;
51 static const int kNewTabButtonVOffset = 5;
52 static const int kSuspendAnimationsTimeMs = 200;
53 static const int kTabHOffset = -16;
54 static const int kTabStripAnimationVSlop = 40;
55 
56 // Size of the drop indicator.
57 static int drop_indicator_width;
58 static int drop_indicator_height;
59 
Round(double x)60 static 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 
65 namespace {
66 
67 ///////////////////////////////////////////////////////////////////////////////
68 // NewTabButton
69 //
70 //  A subclass of button that hit-tests to the shape of the new tab button.
71 
72 class NewTabButton : public views::ImageButton {
73  public:
NewTabButton(views::ButtonListener * listener)74   explicit NewTabButton(views::ButtonListener* listener)
75       : views::ImageButton(listener) {
76   }
~NewTabButton()77   virtual ~NewTabButton() {}
78 
79  protected:
80   // Overridden from views::View:
HasHitTestMask() const81   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   }
GetHitTestMask(gfx::Path * path) const87   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
116 const int TabStrip::mini_to_non_mini_gap_ = 3;
117 
TabStrip(TabStripController * controller)118 TabStrip::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 
~TabStrip()129 TabStrip::~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 
InitTabStripButtons()146 void 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 
GetNewTabButtonBounds()158 gfx::Rect TabStrip::GetNewTabButtonBounds() {
159   return newtab_button_->bounds();
160 }
161 
MouseMovedOutOfView()162 void TabStrip::MouseMovedOutOfView() {
163   ResizeLayoutTabs();
164 }
165 
166 ////////////////////////////////////////////////////////////////////////////////
167 // TabStrip, AbstractTabStripView implementation:
168 
IsPositionInWindowCaption(const gfx::Point & point)169 bool 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 
SetBackgroundOffset(const gfx::Point & offset)193 void 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 
PrepareForCloseAt(int model_index)201 void 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 
RemoveTabAt(int model_index)222 void 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 
SelectTabAt(int old_model_index,int new_model_index)229 void 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 
TabTitleChangedNotLoading(int model_index)245 void TabStrip::TabTitleChangedNotLoading(int model_index) {
246   Tab* tab = GetTabAtModelIndex(model_index);
247   if (tab->data().mini && !tab->IsActive())
248     tab->StartMiniTabTitleAnimation();
249 }
250 
StartHighlight(int model_index)251 void TabStrip::StartHighlight(int model_index) {
252   GetTabAtModelIndex(model_index)->StartPulse();
253 }
254 
StopAllHighlighting()255 void TabStrip::StopAllHighlighting() {
256   for (int i = 0; i < tab_count(); ++i)
257     GetTabAtTabDataIndex(i)->StopPulse();
258 }
259 
CreateTabForDragging()260 BaseTab* 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 
PaintChildren(gfx::Canvas * canvas)271 void 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.
GetViewByID(int view_id) const337 const 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 
GetPreferredSize()354 gfx::Size TabStrip::GetPreferredSize() {
355   return gfx::Size(0, Tab::GetMinimumUnselectedSize().height());
356 }
357 
OnDragEntered(const DropTargetEvent & event)358 void 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 
OnDragUpdated(const DropTargetEvent & event)365 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
366   UpdateDropIndex(event);
367   return GetDropEffect(event);
368 }
369 
OnDragExited()370 void TabStrip::OnDragExited() {
371   SetDropIndex(-1, false);
372 }
373 
OnPerformDrop(const DropTargetEvent & event)374 int 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 
GetAccessibleState(ui::AccessibleViewState * state)394 void TabStrip::GetAccessibleState(ui::AccessibleViewState* state) {
395   state->role = ui::AccessibilityTypes::ROLE_PAGETABLIST;
396 }
397 
GetEventHandlerForPoint(const gfx::Point & point)398 views::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 
OnThemeChanged()422 void TabStrip::OnThemeChanged() {
423   LoadNewTabButtonImage();
424 }
425 
CreateTab()426 BaseTab* TabStrip::CreateTab() {
427   Tab* tab = new Tab(this);
428   tab->set_animation_container(animation_container_.get());
429   return tab;
430 }
431 
StartInsertTabAnimation(int model_index)432 void 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 
AnimateToIdealBounds()456 void 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 
ShouldHighlightCloseButtonAfterRemove()466 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
467   return in_tab_close_;
468 }
469 
DoLayout()470 void TabStrip::DoLayout() {
471   BaseTabStrip::DoLayout();
472 
473   newtab_button_->SetBoundsRect(newtab_button_bounds_);
474 }
475 
LayoutDraggedTabsAt(const std::vector<BaseTab * > & tabs,BaseTab * active_tab,const gfx::Point & location,bool initial_drag)476 void 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 
CalculateBoundsForDraggedTabs(const std::vector<BaseTab * > & tabs,std::vector<gfx::Rect> * bounds)505 void 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 
GetSizeNeededForTabs(const std::vector<BaseTab * > & tabs)519 int 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 
ViewHierarchyChanged(bool is_add,views::View * parent,views::View * child)532 void 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 
ButtonPressed(views::Button * sender,const views::Event & event)542 void TabStrip::ButtonPressed(views::Button* sender, const views::Event& event) {
543   if (sender == newtab_button_)
544     controller()->CreateNewTab();
545 }
546 
547 ///////////////////////////////////////////////////////////////////////////////
548 // TabStrip, private:
549 
Init()550 void 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 
LoadNewTabButtonImage()565 void 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 
GetTabAtTabDataIndex(int tab_data_index) const592 Tab* TabStrip::GetTabAtTabDataIndex(int tab_data_index) const {
593   return static_cast<Tab*>(base_tab_at_tab_index(tab_data_index));
594 }
595 
GetTabAtModelIndex(int model_index) const596 Tab* TabStrip::GetTabAtModelIndex(int model_index) const {
597   return GetTabAtTabDataIndex(ModelIndexToTabIndex(model_index));
598 }
599 
GetCurrentTabWidths(double * unselected_width,double * selected_width) const600 void TabStrip::GetCurrentTabWidths(double* unselected_width,
601                                    double* selected_width) const {
602   *unselected_width = current_unselected_width_;
603   *selected_width = current_selected_width_;
604 }
605 
GetDesiredTabWidths(int tab_count,int mini_tab_count,double * unselected_width,double * selected_width) const606 void 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 
ResizeLayoutTabs()686 void 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 
AddMessageLoopObserver()717 void 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 
RemoveMessageLoopObserver()726 void TabStrip::RemoveMessageLoopObserver() {
727   mouse_watcher_.reset(NULL);
728 }
729 
GetDropBounds(int drop_index,bool drop_before,bool * is_beneath)730 gfx::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 
UpdateDropIndex(const DropTargetEvent & event)771 void 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 
SetDropIndex(int tab_data_index,bool drop_before)796 void 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 
GetDropEffect(const views::DropTargetEvent & event)830 int 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
GetDropArrowImage(bool is_down)840 SkBitmap* 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 
DropInfo(int drop_index,bool drop_before,bool point_down)847 TabStrip::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 
~DropInfo()866 TabStrip::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
GenerateIdealBounds()877 void 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 
StartResizeLayoutAnimation()941 void TabStrip::StartResizeLayoutAnimation() {
942   PrepareForAnimation();
943   GenerateIdealBounds();
944   AnimateToIdealBounds();
945 }
946 
StartMiniTabAnimation()947 void TabStrip::StartMiniTabAnimation() {
948   in_tab_close_ = false;
949   available_width_for_tabs_ = -1;
950 
951   PrepareForAnimation();
952 
953   GenerateIdealBounds();
954   AnimateToIdealBounds();
955 }
956 
StartMouseInitiatedRemoveTabAnimation(int model_index)957 void 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 
GetMiniTabCount() const1001 int 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 
GetAvailableWidthForTabs(Tab * last_tab) const1012 int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const {
1013   return last_tab->x() + last_tab->width();
1014 }
1015 
IsPointInTab(Tab * tab,const gfx::Point & point_in_tabstrip_coords)1016 bool 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