1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/tabs/tab_strip.h"
16 #include "base/compiler_specific.h"
17 #include "base/metrics/histogram.h"
18 #include "base/stl_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/defaults.h"
21 #include "chrome/browser/ui/host_desktop.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/view_ids.h"
24 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
25 #include "chrome/browser/ui/views/tabs/tab.h"
26 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
27 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
29 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
30 #include "content/public/browser/user_metrics.h"
31 #include "grit/generated_resources.h"
32 #include "grit/theme_resources.h"
33 #include "ui/base/accessibility/accessible_view_state.h"
34 #include "ui/base/default_theme_provider.h"
35 #include "ui/base/dragdrop/drag_drop_types.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/layout.h"
38 #include "ui/base/models/list_selection_model.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/gfx/animation/animation_container.h"
41 #include "ui/gfx/animation/throb_animation.h"
42 #include "ui/gfx/canvas.h"
43 #include "ui/gfx/display.h"
44 #include "ui/gfx/image/image_skia.h"
45 #include "ui/gfx/image/image_skia_operations.h"
46 #include "ui/gfx/path.h"
47 #include "ui/gfx/rect_conversions.h"
48 #include "ui/gfx/screen.h"
49 #include "ui/gfx/size.h"
50 #include "ui/views/controls/image_view.h"
51 #include "ui/views/mouse_watcher_view_host.h"
52 #include "ui/views/rect_based_targeting_utils.h"
53 #include "ui/views/view_model_utils.h"
54 #include "ui/views/widget/root_view.h"
55 #include "ui/views/widget/widget.h"
56 #include "ui/views/window/non_client_view.h"
59 #include "ui/gfx/win/hwnd_util.h"
60 #include "ui/views/widget/monitor_win.h"
61 #include "ui/views/win/hwnd_util.h"
62 #include "win8/util/win8_util.h"
65 using base::UserMetricsAction
;
66 using ui::DropTargetEvent
;
70 static const int kTabStripAnimationVSlop
= 40;
71 // Inactive tabs in a native frame are slightly transparent.
72 static const int kGlassFrameInactiveTabAlpha
= 200;
73 // If there are multiple tabs selected then make non-selected inactive tabs
74 // even more transparent.
75 static const int kGlassFrameInactiveTabAlphaMultiSelection
= 150;
77 // Alpha applied to all elements save the selected tabs.
78 static const int kInactiveTabAndNewTabButtonAlphaAsh
= 230;
79 static const int kInactiveTabAndNewTabButtonAlpha
= 255;
81 // Inverse ratio of the width of a tab edge to the width of the tab. When
82 // hovering over the left or right edge of a tab, the drop indicator will
83 // point between tabs.
84 static const int kTabEdgeRatioInverse
= 4;
86 // Size of the drop indicator.
87 static int drop_indicator_width
;
88 static int drop_indicator_height
;
90 static inline int Round(double x
) {
91 // Why oh why is this not in a standard header?
92 return static_cast<int>(floor(x
+ 0.5));
95 // Max number of stacked tabs.
96 static const int kMaxStackedCount
= 4;
98 // Padding between stacked tabs.
99 static const int kStackedPadding
= 6;
101 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
102 #if !defined(USE_ASH)
103 const int kMouseMoveTimeMS
= 200;
104 const int kMouseMoveCountBeforeConsiderReal
= 3;
107 // Amount of time we delay before resizing after a close from a touch.
108 const int kTouchResizeLayoutTimeMS
= 2000;
110 // Horizontal offset for the new tab button to bring it closer to the
112 int newtab_button_h_offset() {
113 static int value
= -1;
115 switch (ui::GetDisplayLayout()) {
116 case ui::LAYOUT_DESKTOP
:
119 case ui::LAYOUT_TOUCH
:
129 // Vertical offset for the new tab button to bring it closer to the
131 int newtab_button_v_offset() {
132 static int value
= -1;
134 switch (ui::GetDisplayLayout()) {
135 case ui::LAYOUT_DESKTOP
:
138 case ui::LAYOUT_TOUCH
:
148 // Amount the left edge of a tab is offset from the rectangle of the tab's
149 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
150 // Affects the size of the "V" between adjacent tabs.
152 static int value
= -1;
154 switch (ui::GetDisplayLayout()) {
155 case ui::LAYOUT_DESKTOP
:
158 case ui::LAYOUT_TOUCH
:
168 // The size of the new tab button must be hardcoded because we need to be
169 // able to lay it out before we are able to get its image from the
170 // ui::ThemeProvider. It also makes sense to do this, because the size of the
171 // new tab button should not need to be calculated dynamically.
172 int newtab_button_asset_width() {
173 static int value
= -1;
175 switch (ui::GetDisplayLayout()) {
176 case ui::LAYOUT_DESKTOP
:
179 case ui::LAYOUT_TOUCH
:
189 int newtab_button_asset_height() {
190 static int value
= -1;
192 switch (ui::GetDisplayLayout()) {
193 case ui::LAYOUT_DESKTOP
:
196 case ui::LAYOUT_TOUCH
:
206 // Amount to adjust the clip by when the tab is stacked before the active index.
207 int stacked_tab_left_clip() {
208 static int value
= -1;
210 switch (ui::GetDisplayLayout()) {
211 case ui::LAYOUT_DESKTOP
:
214 case ui::LAYOUT_TOUCH
:
224 // Amount to adjust the clip by when the tab is stacked after the active index.
225 int stacked_tab_right_clip() {
226 static int value
= -1;
228 switch (ui::GetDisplayLayout()) {
229 case ui::LAYOUT_DESKTOP
:
232 case ui::LAYOUT_TOUCH
:
242 base::string16
GetClipboardText() {
243 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION
))
244 return base::string16();
245 ui::Clipboard
* clipboard
= ui::Clipboard::GetForCurrentThread();
247 base::string16 clipboard_text
;
248 clipboard
->ReadText(ui::CLIPBOARD_TYPE_SELECTION
, &clipboard_text
);
249 return clipboard_text
;
252 // Animation delegate used when a dragged tab is released. When done sets the
253 // dragging state to false.
254 class ResetDraggingStateDelegate
255 : public views::BoundsAnimator::OwnedAnimationDelegate
{
257 explicit ResetDraggingStateDelegate(Tab
* tab
) : tab_(tab
) {
260 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
{
261 tab_
->set_dragging(false);
264 virtual void AnimationCanceled(const gfx::Animation
* animation
) OVERRIDE
{
265 tab_
->set_dragging(false);
271 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate
);
274 // If |dest| contains the point |point_in_source| the event handler from |dest|
275 // is returned. Otherwise NULL is returned.
276 views::View
* ConvertPointToViewAndGetEventHandler(
279 const gfx::Point
& point_in_source
) {
280 gfx::Point
dest_point(point_in_source
);
281 views::View::ConvertPointToTarget(source
, dest
, &dest_point
);
282 return dest
->HitTestPoint(dest_point
) ?
283 dest
->GetEventHandlerForPoint(dest_point
) : NULL
;
286 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
287 // should return NULL if it does not contain the point.
288 views::View
* ConvertPointToViewAndGetTooltipHandler(
291 const gfx::Point
& point_in_source
) {
292 gfx::Point
dest_point(point_in_source
);
293 views::View::ConvertPointToTarget(source
, dest
, &dest_point
);
294 return dest
->GetTooltipHandlerForPoint(dest_point
);
297 TabDragController::EventSource
EventSourceFromEvent(
298 const ui::LocatedEvent
& event
) {
299 return event
.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH
:
300 TabDragController::EVENT_SOURCE_MOUSE
;
305 ///////////////////////////////////////////////////////////////////////////////
308 // A subclass of button that hit-tests to the shape of the new tab button and
309 // does custom drawing.
311 class NewTabButton
: public views::ImageButton
{
313 NewTabButton(TabStrip
* tab_strip
, views::ButtonListener
* listener
);
314 virtual ~NewTabButton();
316 // Set the background offset used to match the background image to the frame
318 void set_background_offset(const gfx::Point
& offset
) {
319 background_offset_
= offset
;
323 // Overridden from views::View:
324 virtual bool HasHitTestMask() const OVERRIDE
;
325 virtual void GetHitTestMask(HitTestSource source
,
326 gfx::Path
* path
) const OVERRIDE
;
328 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
;
330 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
332 // Overridden from ui::EventHandler:
333 virtual void OnGestureEvent(ui::GestureEvent
* event
) OVERRIDE
;
336 bool ShouldWindowContentsBeTransparent() const;
337 gfx::ImageSkia
GetBackgroundImage(views::CustomButton::ButtonState state
,
338 ui::ScaleFactor scale_factor
) const;
339 gfx::ImageSkia
GetImageForState(views::CustomButton::ButtonState state
,
340 ui::ScaleFactor scale_factor
) const;
341 gfx::ImageSkia
GetImageForScale(ui::ScaleFactor scale_factor
) const;
343 // Tab strip that contains this button.
344 TabStrip
* tab_strip_
;
346 // The offset used to paint the background image.
347 gfx::Point background_offset_
;
349 // were we destroyed?
352 DISALLOW_COPY_AND_ASSIGN(NewTabButton
);
355 NewTabButton::NewTabButton(TabStrip
* tab_strip
, views::ButtonListener
* listener
)
356 : views::ImageButton(listener
),
357 tab_strip_(tab_strip
),
359 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
360 set_triggerable_event_flags(triggerable_event_flags() |
361 ui::EF_MIDDLE_MOUSE_BUTTON
);
365 NewTabButton::~NewTabButton() {
370 bool NewTabButton::HasHitTestMask() const {
371 // When the button is sized to the top of the tab strip we want the user to
372 // be able to click on complete bounds, and so don't return a custom hit
374 return !tab_strip_
->SizeTabButtonToTopOfTabStrip();
377 void NewTabButton::GetHitTestMask(HitTestSource source
, gfx::Path
* path
) const {
380 SkScalar w
= SkIntToScalar(width());
381 SkScalar v_offset
= SkIntToScalar(newtab_button_v_offset());
383 // These values are defined by the shape of the new tab image. Should that
384 // image ever change, these values will need to be updated. They're so
385 // custom it's not really worth defining constants for.
386 // These values are correct for regular and USE_ASH versions of the image.
387 path
->moveTo(0, v_offset
+ 1);
388 path
->lineTo(w
- 7, v_offset
+ 1);
389 path
->lineTo(w
- 4, v_offset
+ 4);
390 path
->lineTo(w
, v_offset
+ 16);
391 path
->lineTo(w
- 1, v_offset
+ 17);
392 path
->lineTo(7, v_offset
+ 17);
393 path
->lineTo(4, v_offset
+ 13);
394 path
->lineTo(0, v_offset
+ 1);
399 void NewTabButton::OnMouseReleased(const ui::MouseEvent
& event
) {
400 if (event
.IsOnlyRightMouseButton()) {
401 gfx::Point point
= event
.location();
402 views::View::ConvertPointToScreen(this, &point
);
403 bool destroyed
= false;
404 destroyed_
= &destroyed
;
405 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point
);
410 SetState(views::CustomButton::STATE_NORMAL
);
413 views::ImageButton::OnMouseReleased(event
);
417 void NewTabButton::OnPaint(gfx::Canvas
* canvas
) {
418 gfx::ImageSkia image
=
419 GetImageForScale(ui::GetSupportedScaleFactor(canvas
->image_scale()));
420 canvas
->DrawImageInt(image
, 0, height() - image
.height());
423 void NewTabButton::OnGestureEvent(ui::GestureEvent
* event
) {
424 // Consume all gesture events here so that the parent (Tab) does not
425 // start consuming gestures.
426 views::ImageButton::OnGestureEvent(event
);
430 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
431 return GetWidget() &&
432 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
435 gfx::ImageSkia
NewTabButton::GetBackgroundImage(
436 views::CustomButton::ButtonState state
,
437 ui::ScaleFactor scale_factor
) const {
438 int background_id
= 0;
439 if (ShouldWindowContentsBeTransparent()) {
440 background_id
= IDR_THEME_TAB_BACKGROUND_V
;
441 } else if (tab_strip_
->controller()->IsIncognito()) {
442 background_id
= IDR_THEME_TAB_BACKGROUND_INCOGNITO
;
444 } else if (win8::IsSingleWindowMetroMode()) {
445 background_id
= IDR_THEME_TAB_BACKGROUND_V
;
448 background_id
= IDR_THEME_TAB_BACKGROUND
;
453 case views::CustomButton::STATE_NORMAL
:
454 case views::CustomButton::STATE_HOVERED
:
455 alpha
= ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
458 case views::CustomButton::STATE_PRESSED
:
466 gfx::ImageSkia
* mask
=
467 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK
);
468 int height
= mask
->height();
469 int width
= mask
->width();
470 float scale
= ui::GetImageScale(scale_factor
);
471 // The canvas and mask has to use the same scale factor.
472 if (!mask
->HasRepresentation(scale
))
473 scale_factor
= ui::SCALE_FACTOR_100P
;
475 gfx::Canvas
canvas(gfx::Size(width
, height
), scale
, false);
477 // For custom images the background starts at the top of the tab strip.
478 // Otherwise the background starts at the top of the frame.
479 gfx::ImageSkia
* background
=
480 GetThemeProvider()->GetImageSkiaNamed(background_id
);
481 int offset_y
= GetThemeProvider()->HasCustomImage(background_id
) ?
482 0 : background_offset_
.y();
484 // The new tab background is mirrored in RTL mode, but the theme background
485 // should never be mirrored. Mirror it here to compensate.
486 float x_scale
= 1.0f
;
487 int x
= GetMirroredX() + background_offset_
.x();
488 if (base::i18n::IsRTL()) {
490 // Offset by |width| such that the same region is painted as if there was no
494 canvas
.TileImageInt(*background
, x
, newtab_button_v_offset() + offset_y
,
495 x_scale
, 1.0f
, 0, 0, width
, height
);
499 paint
.setColor(SkColorSetARGB(alpha
, 255, 255, 255));
500 paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
501 paint
.setStyle(SkPaint::kFill_Style
);
502 canvas
.DrawRect(gfx::Rect(0, 0, width
, height
), paint
);
505 // White highlight on hover.
506 if (state
== views::CustomButton::STATE_HOVERED
)
507 canvas
.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
509 return gfx::ImageSkiaOperations::CreateMaskedImage(
510 gfx::ImageSkia(canvas
.ExtractImageRep()), *mask
);
513 gfx::ImageSkia
NewTabButton::GetImageForState(
514 views::CustomButton::ButtonState state
,
515 ui::ScaleFactor scale_factor
) const {
516 const int overlay_id
= state
== views::CustomButton::STATE_PRESSED
?
517 IDR_NEWTAB_BUTTON_P
: IDR_NEWTAB_BUTTON
;
518 gfx::ImageSkia
* overlay
= GetThemeProvider()->GetImageSkiaNamed(overlay_id
);
521 gfx::Size(overlay
->width(), overlay
->height()),
522 ui::GetImageScale(scale_factor
),
524 canvas
.DrawImageInt(GetBackgroundImage(state
, scale_factor
), 0, 0);
526 // Draw the button border with a slight alpha.
527 const int kGlassFrameOverlayAlpha
= 178;
528 const int kOpaqueFrameOverlayAlpha
= 230;
529 uint8 alpha
= ShouldWindowContentsBeTransparent() ?
530 kGlassFrameOverlayAlpha
: kOpaqueFrameOverlayAlpha
;
531 canvas
.DrawImageInt(*overlay
, 0, 0, alpha
);
533 return gfx::ImageSkia(canvas
.ExtractImageRep());
536 gfx::ImageSkia
NewTabButton::GetImageForScale(
537 ui::ScaleFactor scale_factor
) const {
538 if (!hover_animation_
->is_animating())
539 return GetImageForState(state(), scale_factor
);
540 return gfx::ImageSkiaOperations::CreateBlendedImage(
541 GetImageForState(views::CustomButton::STATE_NORMAL
, scale_factor
),
542 GetImageForState(views::CustomButton::STATE_HOVERED
, scale_factor
),
543 hover_animation_
->GetCurrentValue());
546 ///////////////////////////////////////////////////////////////////////////////
547 // TabStrip::RemoveTabDelegate
549 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
551 class TabStrip::RemoveTabDelegate
552 : public views::BoundsAnimator::OwnedAnimationDelegate
{
554 RemoveTabDelegate(TabStrip
* tab_strip
, Tab
* tab
);
556 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
;
557 virtual void AnimationCanceled(const gfx::Animation
* animation
) OVERRIDE
;
560 void CompleteRemove();
562 // When the animation completes, we send the Container a message to simulate
563 // a mouse moved event at the current mouse position. This tickles the Tab
564 // the mouse is currently over to show the "hot" state of the close button.
565 void HighlightCloseButton();
570 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate
);
573 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip
* tab_strip
,
575 : tabstrip_(tab_strip
),
579 void TabStrip::RemoveTabDelegate::AnimationEnded(
580 const gfx::Animation
* animation
) {
584 void TabStrip::RemoveTabDelegate::AnimationCanceled(
585 const gfx::Animation
* animation
) {
589 void TabStrip::RemoveTabDelegate::CompleteRemove() {
590 DCHECK(tab_
->closing());
591 tabstrip_
->RemoveAndDeleteTab(tab_
);
592 HighlightCloseButton();
595 void TabStrip::RemoveTabDelegate::HighlightCloseButton() {
596 if (tabstrip_
->IsDragSessionActive() ||
597 !tabstrip_
->ShouldHighlightCloseButtonAfterRemove()) {
598 // This function is not required (and indeed may crash!) for removes
599 // spawned by non-mouse closes and drag-detaches.
603 views::Widget
* widget
= tabstrip_
->GetWidget();
604 // This can be null during shutdown. See http://crbug.com/42737.
608 widget
->SynthesizeMouseMoveEvent();
611 ///////////////////////////////////////////////////////////////////////////////
615 const char TabStrip::kViewClassName
[] = "TabStrip";
618 const int TabStrip::kMiniToNonMiniGap
= 3;
620 TabStrip::TabStrip(TabStripController
* controller
)
621 : controller_(controller
),
622 newtab_button_(NULL
),
623 current_unselected_width_(Tab::GetStandardSize().width()),
624 current_selected_width_(Tab::GetStandardSize().width()),
625 available_width_for_tabs_(-1),
626 in_tab_close_(false),
627 animation_container_(new gfx::AnimationContainer()),
628 bounds_animator_(this),
629 layout_type_(TAB_STRIP_LAYOUT_SHRINK
),
630 adjust_layout_(false),
631 reset_to_shrink_on_exit_(false),
632 mouse_move_count_(0),
633 immersive_style_(false) {
637 TabStrip::~TabStrip() {
638 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
639 TabStripDeleted(this));
641 // The animations may reference the tabs. Shut down the animation before we
643 StopAnimating(false);
645 DestroyDragController();
647 // Make sure we unhook ourselves as a message loop observer so that we don't
648 // crash in the case where the user closes the window after closing a tab
649 // but before moving the mouse.
650 RemoveMessageLoopObserver();
652 // The children (tabs) may callback to us from their destructor. Delete them
653 // so that if they call back we aren't in a weird state.
654 RemoveAllChildViews(true);
657 void TabStrip::AddObserver(TabStripObserver
* observer
) {
658 observers_
.AddObserver(observer
);
661 void TabStrip::RemoveObserver(TabStripObserver
* observer
) {
662 observers_
.RemoveObserver(observer
);
665 void TabStrip::SetLayoutType(TabStripLayoutType layout_type
,
666 bool adjust_layout
) {
667 adjust_layout_
= adjust_layout
;
668 if (layout_type
== layout_type_
)
671 const int active_index
= controller_
->GetActiveIndex();
672 int active_center
= 0;
673 if (active_index
!= -1) {
674 active_center
= ideal_bounds(active_index
).x() +
675 ideal_bounds(active_index
).width() / 2;
677 layout_type_
= layout_type
;
678 SetResetToShrinkOnExit(false);
679 SwapLayoutIfNecessary();
680 // When transitioning to stacked try to keep the active tab centered.
681 if (touch_layout_
.get() && active_index
!= -1) {
682 touch_layout_
->SetActiveTabLocation(
683 active_center
- ideal_bounds(active_index
).width() / 2);
684 AnimateToIdealBounds();
688 gfx::Rect
TabStrip::GetNewTabButtonBounds() {
689 return newtab_button_
->bounds();
692 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
693 // Extend the button to the screen edge in maximized and immersive fullscreen.
694 views::Widget
* widget
= GetWidget();
695 return browser_defaults::kSizeTabButtonToTopOfTabStrip
||
696 (widget
&& (widget
->IsMaximized() || widget
->IsFullscreen()));
699 void TabStrip::StartHighlight(int model_index
) {
700 tab_at(model_index
)->StartPulse();
703 void TabStrip::StopAllHighlighting() {
704 for (int i
= 0; i
< tab_count(); ++i
)
705 tab_at(i
)->StopPulse();
708 void TabStrip::AddTabAt(int model_index
,
709 const TabRendererData
& data
,
711 // Stop dragging when a new tab is added and dragging a window. Doing
712 // otherwise results in a confusing state if the user attempts to reattach. We
713 // could allow this and make TabDragController update itself during the add,
714 // but this comes up infrequently enough that it's not work the complexity.
715 if (drag_controller_
.get() && !drag_controller_
->is_mutating() &&
716 drag_controller_
->is_dragging_window()) {
717 EndDrag(END_DRAG_COMPLETE
);
719 Tab
* tab
= CreateTab();
721 UpdateTabsClosingMap(model_index
, 1);
722 tabs_
.Add(tab
, model_index
);
725 if (touch_layout_
.get()) {
726 GenerateIdealBoundsForMiniTabs(NULL
);
729 add_types
|= StackedTabStripLayout::kAddTypeMini
;
731 add_types
|= StackedTabStripLayout::kAddTypeActive
;
732 touch_layout_
->AddTab(model_index
, add_types
, GetStartXForNormalTabs());
735 // Don't animate the first tab, it looks weird, and don't animate anything
736 // if the containing window isn't visible yet.
737 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
738 StartInsertTabAnimation(model_index
);
742 SwapLayoutIfNecessary();
744 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
745 TabStripAddedTabAt(this, model_index
));
748 void TabStrip::MoveTab(int from_model_index
,
750 const TabRendererData
& data
) {
751 DCHECK_GT(tabs_
.view_size(), 0);
752 Tab
* last_tab
= tab_at(tab_count() - 1);
753 tab_at(from_model_index
)->SetData(data
);
754 if (touch_layout_
.get()) {
755 tabs_
.MoveViewOnly(from_model_index
, to_model_index
);
757 GenerateIdealBoundsForMiniTabs(&mini_count
);
758 touch_layout_
->MoveTab(
759 from_model_index
, to_model_index
, controller_
->GetActiveIndex(),
760 GetStartXForNormalTabs(), mini_count
);
762 tabs_
.Move(from_model_index
, to_model_index
);
764 StartMoveTabAnimation();
765 if (TabDragController::IsAttachedTo(this) &&
766 (last_tab
!= tab_at(tab_count() - 1) || last_tab
->dragging())) {
767 newtab_button_
->SetVisible(false);
769 SwapLayoutIfNecessary();
771 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
772 TabStripMovedTab(this, from_model_index
, to_model_index
));
775 void TabStrip::RemoveTabAt(int model_index
) {
776 if (touch_layout_
.get()) {
777 Tab
* tab
= tab_at(model_index
);
778 tab
->set_closing(true);
779 int old_x
= tabs_
.ideal_bounds(model_index
).x();
780 // We still need to paint the tab until we actually remove it. Put it in
781 // tabs_closing_map_ so we can find it.
782 RemoveTabFromViewModel(model_index
);
783 touch_layout_
->RemoveTab(model_index
, GenerateIdealBoundsForMiniTabs(NULL
),
785 ScheduleRemoveTabAnimation(tab
);
786 } else if (in_tab_close_
&& model_index
!= GetModelCount()) {
787 StartMouseInitiatedRemoveTabAnimation(model_index
);
789 StartRemoveTabAnimation(model_index
);
791 SwapLayoutIfNecessary();
793 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
794 TabStripRemovedTabAt(this, model_index
));
797 void TabStrip::SetTabData(int model_index
, const TabRendererData
& data
) {
798 Tab
* tab
= tab_at(model_index
);
799 bool mini_state_changed
= tab
->data().mini
!= data
.mini
;
802 if (mini_state_changed
) {
803 if (touch_layout_
.get()) {
804 int mini_tab_count
= 0;
805 int start_x
= GenerateIdealBoundsForMiniTabs(&mini_tab_count
);
806 touch_layout_
->SetXAndMiniCount(start_x
, mini_tab_count
);
808 if (GetWidget() && GetWidget()->IsVisible())
809 StartMiniTabAnimation();
813 SwapLayoutIfNecessary();
816 void TabStrip::PrepareForCloseAt(int model_index
, CloseTabSource source
) {
817 if (!in_tab_close_
&& IsAnimating()) {
818 // Cancel any current animations. We do this as remove uses the current
819 // ideal bounds and we need to know ideal bounds is in a good state.
826 int model_count
= GetModelCount();
827 if (model_index
+ 1 != model_count
&& model_count
> 1) {
828 // The user is about to close a tab other than the last tab. Set
829 // available_width_for_tabs_ so that if we do a layout we don't position a
830 // tab past the end of the second to last tab. We do this so that as the
831 // user closes tabs with the mouse a tab continues to fall under the mouse.
832 Tab
* last_tab
= tab_at(model_count
- 1);
833 Tab
* tab_being_removed
= tab_at(model_index
);
834 available_width_for_tabs_
= last_tab
->x() + last_tab
->width() -
835 tab_being_removed
->width() - tab_h_offset();
836 if (model_index
== 0 && tab_being_removed
->data().mini
&&
837 !tab_at(1)->data().mini
) {
838 available_width_for_tabs_
-= kMiniToNonMiniGap
;
842 in_tab_close_
= true;
843 resize_layout_timer_
.Stop();
844 if (source
== CLOSE_TAB_FROM_TOUCH
) {
845 StartResizeLayoutTabsFromTouchTimer();
847 AddMessageLoopObserver();
851 void TabStrip::SetSelection(const ui::ListSelectionModel
& old_selection
,
852 const ui::ListSelectionModel
& new_selection
) {
853 if (touch_layout_
.get()) {
854 touch_layout_
->SetActiveIndex(new_selection
.active());
855 // Only start an animation if we need to. Otherwise clicking on an
856 // unselected tab and dragging won't work because dragging is only allowed
858 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_
))
859 AnimateToIdealBounds();
862 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
863 // a different size to the selected ones.
864 bool tiny_tabs
= current_unselected_width_
!= current_selected_width_
;
865 if (!IsAnimating() && (!in_tab_close_
|| tiny_tabs
)) {
872 ui::ListSelectionModel::SelectedIndices no_longer_selected
=
873 base::STLSetDifference
<ui::ListSelectionModel::SelectedIndices
>(
874 old_selection
.selected_indices(),
875 new_selection
.selected_indices());
876 for (size_t i
= 0; i
< no_longer_selected
.size(); ++i
)
877 tab_at(no_longer_selected
[i
])->StopMiniTabTitleAnimation();
880 void TabStrip::TabTitleChangedNotLoading(int model_index
) {
881 Tab
* tab
= tab_at(model_index
);
882 if (tab
->data().mini
&& !tab
->IsActive())
883 tab
->StartMiniTabTitleAnimation();
886 Tab
* TabStrip::tab_at(int index
) const {
887 return static_cast<Tab
*>(tabs_
.view_at(index
));
890 int TabStrip::GetModelIndexOfTab(const Tab
* tab
) const {
891 return tabs_
.GetIndexOfView(tab
);
894 int TabStrip::GetModelCount() const {
895 return controller_
->GetCount();
898 bool TabStrip::IsValidModelIndex(int model_index
) const {
899 return controller_
->IsValidIndex(model_index
);
902 bool TabStrip::IsDragSessionActive() const {
903 return drag_controller_
.get() != NULL
;
906 bool TabStrip::IsActiveDropTarget() const {
907 for (int i
= 0; i
< tab_count(); ++i
) {
908 Tab
* tab
= tab_at(i
);
915 bool TabStrip::IsTabStripEditable() const {
916 return !IsDragSessionActive() && !IsActiveDropTarget();
919 bool TabStrip::IsTabStripCloseable() const {
920 return !IsDragSessionActive();
923 void TabStrip::UpdateLoadingAnimations() {
924 controller_
->UpdateLoadingAnimations();
927 bool TabStrip::IsPositionInWindowCaption(const gfx::Point
& point
) {
928 return IsRectInWindowCaption(gfx::Rect(point
, gfx::Size(1, 1)));
931 bool TabStrip::IsRectInWindowCaption(const gfx::Rect
& rect
) {
932 views::View
* v
= GetEventHandlerForRect(rect
);
934 // If there is no control at this location, claim the hit was in the title
935 // bar to get a move action.
939 // Check to see if the rect intersects the non-button parts of the new tab
940 // button. The button has a non-rectangular shape, so if it's not in the
941 // visual portions of the button we treat it as a click to the caption.
942 gfx::RectF
rect_in_newtab_coords_f(rect
);
943 View::ConvertRectToTarget(this, newtab_button_
, &rect_in_newtab_coords_f
);
944 gfx::Rect rect_in_newtab_coords
= gfx::ToEnclosingRect(
945 rect_in_newtab_coords_f
);
946 if (newtab_button_
->GetLocalBounds().Intersects(rect_in_newtab_coords
) &&
947 !newtab_button_
->HitTestRect(rect_in_newtab_coords
))
950 // All other regions, including the new Tab button, should be considered part
951 // of the containing Window's client area so that regular events can be
952 // processed for them.
956 void TabStrip::SetBackgroundOffset(const gfx::Point
& offset
) {
957 for (int i
= 0; i
< tab_count(); ++i
)
958 tab_at(i
)->set_background_offset(offset
);
959 newtab_button_
->set_background_offset(offset
);
962 views::View
* TabStrip::newtab_button() {
963 return newtab_button_
;
966 void TabStrip::SetImmersiveStyle(bool enable
) {
967 if (immersive_style_
== enable
)
969 immersive_style_
= enable
;
972 bool TabStrip::IsAnimating() const {
973 return bounds_animator_
.IsAnimating();
976 void TabStrip::StopAnimating(bool layout
) {
980 bounds_animator_
.Cancel();
986 void TabStrip::FileSupported(const GURL
& url
, bool supported
) {
987 if (drop_info_
.get() && drop_info_
->url
== url
)
988 drop_info_
->file_supported
= supported
;
991 const ui::ListSelectionModel
& TabStrip::GetSelectionModel() {
992 return controller_
->GetSelectionModel();
995 bool TabStrip::SupportsMultipleSelection() {
996 // TODO: currently only allow single selection in touch layout mode.
997 return touch_layout_
.get() == NULL
;
1000 void TabStrip::SelectTab(Tab
* tab
) {
1001 int model_index
= GetModelIndexOfTab(tab
);
1002 if (IsValidModelIndex(model_index
))
1003 controller_
->SelectTab(model_index
);
1006 void TabStrip::ExtendSelectionTo(Tab
* tab
) {
1007 int model_index
= GetModelIndexOfTab(tab
);
1008 if (IsValidModelIndex(model_index
))
1009 controller_
->ExtendSelectionTo(model_index
);
1012 void TabStrip::ToggleSelected(Tab
* tab
) {
1013 int model_index
= GetModelIndexOfTab(tab
);
1014 if (IsValidModelIndex(model_index
))
1015 controller_
->ToggleSelected(model_index
);
1018 void TabStrip::AddSelectionFromAnchorTo(Tab
* tab
) {
1019 int model_index
= GetModelIndexOfTab(tab
);
1020 if (IsValidModelIndex(model_index
))
1021 controller_
->AddSelectionFromAnchorTo(model_index
);
1024 void TabStrip::CloseTab(Tab
* tab
, CloseTabSource source
) {
1025 if (tab
->closing()) {
1026 // If the tab is already closing, close the next tab. We do this so that the
1027 // user can rapidly close tabs by clicking the close button and not have
1028 // the animations interfere with that.
1029 for (TabsClosingMap::const_iterator
i(tabs_closing_map_
.begin());
1030 i
!= tabs_closing_map_
.end(); ++i
) {
1031 std::vector
<Tab
*>::const_iterator j
=
1032 std::find(i
->second
.begin(), i
->second
.end(), tab
);
1033 if (j
!= i
->second
.end()) {
1034 if (i
->first
+ 1 < GetModelCount())
1035 controller_
->CloseTab(i
->first
+ 1, source
);
1039 // If we get here, it means a tab has been marked as closing but isn't in
1040 // the set of known closing tabs.
1044 int model_index
= GetModelIndexOfTab(tab
);
1045 if (IsValidModelIndex(model_index
))
1046 controller_
->CloseTab(model_index
, source
);
1049 void TabStrip::ShowContextMenuForTab(Tab
* tab
,
1050 const gfx::Point
& p
,
1051 ui::MenuSourceType source_type
) {
1052 controller_
->ShowContextMenuForTab(tab
, p
, source_type
);
1055 bool TabStrip::IsActiveTab(const Tab
* tab
) const {
1056 int model_index
= GetModelIndexOfTab(tab
);
1057 return IsValidModelIndex(model_index
) &&
1058 controller_
->IsActiveTab(model_index
);
1061 bool TabStrip::IsTabSelected(const Tab
* tab
) const {
1062 int model_index
= GetModelIndexOfTab(tab
);
1063 return IsValidModelIndex(model_index
) &&
1064 controller_
->IsTabSelected(model_index
);
1067 bool TabStrip::IsTabPinned(const Tab
* tab
) const {
1071 int model_index
= GetModelIndexOfTab(tab
);
1072 return IsValidModelIndex(model_index
) &&
1073 controller_
->IsTabPinned(model_index
);
1076 void TabStrip::MaybeStartDrag(
1078 const ui::LocatedEvent
& event
,
1079 const ui::ListSelectionModel
& original_selection
) {
1080 // Don't accidentally start any drag operations during animations if the
1081 // mouse is down... during an animation tabs are being resized automatically,
1082 // so the View system can misinterpret this easily if the mouse is down that
1083 // the user is dragging.
1084 if (IsAnimating() || tab
->closing() ||
1085 controller_
->HasAvailableDragActions() == 0) {
1089 // Do not do any dragging of tabs when using the super short immersive style.
1090 if (IsImmersiveStyle())
1093 int model_index
= GetModelIndexOfTab(tab
);
1094 if (!IsValidModelIndex(model_index
)) {
1098 std::vector
<Tab
*> tabs
;
1099 int size_to_selected
= 0;
1100 int x
= tab
->GetMirroredXInView(event
.x());
1102 // Build the set of selected tabs to drag and calculate the offset from the
1103 // first selected tab.
1104 for (int i
= 0; i
< tab_count(); ++i
) {
1105 Tab
* other_tab
= tab_at(i
);
1106 if (IsTabSelected(other_tab
)) {
1107 tabs
.push_back(other_tab
);
1108 if (other_tab
== tab
) {
1109 size_to_selected
= GetSizeNeededForTabs(tabs
);
1110 x
= size_to_selected
- tab
->width() + x
;
1114 DCHECK(!tabs
.empty());
1115 DCHECK(std::find(tabs
.begin(), tabs
.end(), tab
) != tabs
.end());
1116 ui::ListSelectionModel selection_model
;
1117 if (!original_selection
.IsSelected(model_index
))
1118 selection_model
.Copy(original_selection
);
1119 // Delete the existing DragController before creating a new one. We do this as
1120 // creating the DragController remembers the WebContents delegates and we need
1121 // to make sure the existing DragController isn't still a delegate.
1122 drag_controller_
.reset();
1123 TabDragController::DetachBehavior detach_behavior
=
1124 TabDragController::DETACHABLE
;
1125 TabDragController::MoveBehavior move_behavior
=
1126 TabDragController::REORDER
;
1127 // Use MOVE_VISIBILE_TABS in the following conditions:
1128 // . Mouse event generated from touch and the left button is down (the right
1129 // button corresponds to a long press, which we want to reorder).
1130 // . Gesture begin and control key isn't down.
1131 // . Real mouse event and control is down. This is mostly for testing.
1132 DCHECK(event
.type() == ui::ET_MOUSE_PRESSED
||
1133 event
.type() == ui::ET_GESTURE_BEGIN
);
1134 if (touch_layout_
.get() &&
1135 ((event
.type() == ui::ET_MOUSE_PRESSED
&&
1136 (((event
.flags() & ui::EF_FROM_TOUCH
) &&
1137 static_cast<const ui::MouseEvent
&>(event
).IsLeftMouseButton()) ||
1138 (!(event
.flags() & ui::EF_FROM_TOUCH
) &&
1139 static_cast<const ui::MouseEvent
&>(event
).IsControlDown()))) ||
1140 (event
.type() == ui::ET_GESTURE_BEGIN
&& !event
.IsControlDown()))) {
1141 move_behavior
= TabDragController::MOVE_VISIBILE_TABS
;
1145 // It doesn't make sense to drag tabs out on Win8's single window Metro mode.
1146 if (win8::IsSingleWindowMetroMode())
1147 detach_behavior
= TabDragController::NOT_DETACHABLE
;
1149 drag_controller_
.reset(new TabDragController
);
1150 drag_controller_
->Init(
1151 this, tab
, tabs
, gfx::Point(x
, y
), event
.x(), selection_model
,
1152 detach_behavior
, move_behavior
, EventSourceFromEvent(event
));
1155 void TabStrip::ContinueDrag(views::View
* view
, const ui::LocatedEvent
& event
) {
1156 if (drag_controller_
.get() &&
1157 drag_controller_
->event_source() == EventSourceFromEvent(event
)) {
1158 gfx::Point
screen_location(event
.location());
1159 views::View::ConvertPointToScreen(view
, &screen_location
);
1160 drag_controller_
->Drag(screen_location
);
1164 bool TabStrip::EndDrag(EndDragReason reason
) {
1165 if (!drag_controller_
.get())
1167 bool started_drag
= drag_controller_
->started_drag();
1168 drag_controller_
->EndDrag(reason
);
1169 return started_drag
;
1172 Tab
* TabStrip::GetTabAt(Tab
* tab
, const gfx::Point
& tab_in_tab_coordinates
) {
1173 gfx::Point local_point
= tab_in_tab_coordinates
;
1174 ConvertPointToTarget(tab
, this, &local_point
);
1176 views::View
* view
= GetEventHandlerForPoint(local_point
);
1178 return NULL
; // No tab contains the point.
1180 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1181 while (view
&& view
!= this && view
->id() != VIEW_ID_TAB
)
1182 view
= view
->parent();
1184 return view
&& view
->id() == VIEW_ID_TAB
? static_cast<Tab
*>(view
) : NULL
;
1187 void TabStrip::OnMouseEventInTab(views::View
* source
,
1188 const ui::MouseEvent
& event
) {
1189 UpdateLayoutTypeFromMouseEvent(source
, event
);
1192 bool TabStrip::ShouldPaintTab(const Tab
* tab
, gfx::Rect
* clip
) {
1193 // Only touch layout needs to restrict the clip.
1194 if (!(touch_layout_
.get() || IsStackingDraggedTabs()))
1197 int index
= GetModelIndexOfTab(tab
);
1199 return true; // Tab is closing, paint it all.
1201 int active_index
= IsStackingDraggedTabs() ?
1202 controller_
->GetActiveIndex() : touch_layout_
->active_index();
1203 if (active_index
== tab_count())
1206 if (index
< active_index
) {
1207 if (tab_at(index
)->x() == tab_at(index
+ 1)->x())
1210 if (tab_at(index
)->x() > tab_at(index
+ 1)->x())
1211 return true; // Can happen during dragging.
1213 clip
->SetRect(0, 0, tab_at(index
+ 1)->x() - tab_at(index
)->x() +
1214 stacked_tab_left_clip(),
1215 tab_at(index
)->height());
1216 } else if (index
> active_index
&& index
> 0) {
1217 const gfx::Rect
& tab_bounds(tab_at(index
)->bounds());
1218 const gfx::Rect
& previous_tab_bounds(tab_at(index
- 1)->bounds());
1219 if (tab_bounds
.x() == previous_tab_bounds
.x())
1222 if (tab_bounds
.x() < previous_tab_bounds
.x())
1223 return true; // Can happen during dragging.
1225 if (previous_tab_bounds
.right() + tab_h_offset() != tab_bounds
.x()) {
1226 int x
= previous_tab_bounds
.right() - tab_bounds
.x() -
1227 stacked_tab_right_clip();
1228 clip
->SetRect(x
, 0, tab_bounds
.width() - x
, tab_bounds
.height());
1234 bool TabStrip::IsImmersiveStyle() const {
1235 return immersive_style_
;
1238 void TabStrip::MouseMovedOutOfHost() {
1240 if (reset_to_shrink_on_exit_
) {
1241 reset_to_shrink_on_exit_
= false;
1242 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK
, true);
1243 controller_
->LayoutTypeMaybeChanged();
1247 ///////////////////////////////////////////////////////////////////////////////
1248 // TabStrip, views::View overrides:
1250 void TabStrip::Layout() {
1251 // Only do a layout if our size changed.
1252 if (last_layout_size_
== size())
1254 if (IsDragSessionActive())
1259 void TabStrip::PaintChildren(gfx::Canvas
* canvas
) {
1260 // The view order doesn't match the paint order (tabs_ contains the tab
1261 // ordering). Additionally we need to paint the tabs that are closing in
1262 // |tabs_closing_map_|.
1263 Tab
* active_tab
= NULL
;
1264 std::vector
<Tab
*> tabs_dragging
;
1265 std::vector
<Tab
*> selected_tabs
;
1266 int selected_tab_count
= 0;
1267 bool is_dragging
= false;
1268 int active_tab_index
= -1;
1269 // Since |touch_layout_| is created based on number of tabs and width we use
1270 // the ideal state to determine if we should paint stacked. This minimizes
1271 // painting changes as we switch between the two.
1272 const bool stacking
= (layout_type_
== TAB_STRIP_LAYOUT_STACKED
) ||
1273 IsStackingDraggedTabs();
1275 const chrome::HostDesktopType host_desktop_type
=
1276 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1277 const int inactive_tab_alpha
=
1278 host_desktop_type
== chrome::HOST_DESKTOP_TYPE_ASH
?
1279 kInactiveTabAndNewTabButtonAlphaAsh
:
1280 kInactiveTabAndNewTabButtonAlpha
;
1282 if (inactive_tab_alpha
< 255)
1283 canvas
->SaveLayerAlpha(inactive_tab_alpha
);
1285 PaintClosingTabs(canvas
, tab_count());
1287 for (int i
= tab_count() - 1; i
>= 0; --i
) {
1288 Tab
* tab
= tab_at(i
);
1289 if (tab
->IsSelected())
1290 selected_tab_count
++;
1291 if (tab
->dragging() && !stacking
) {
1293 if (tab
->IsActive()) {
1295 active_tab_index
= i
;
1297 tabs_dragging
.push_back(tab
);
1299 } else if (!tab
->IsActive()) {
1300 if (!tab
->IsSelected()) {
1304 selected_tabs
.push_back(tab
);
1308 active_tab_index
= i
;
1310 PaintClosingTabs(canvas
, i
);
1313 // Draw from the left and then the right if we're in touch mode.
1314 if (stacking
&& active_tab_index
>= 0) {
1315 for (int i
= 0; i
< active_tab_index
; ++i
) {
1316 Tab
* tab
= tab_at(i
);
1320 for (int i
= tab_count() - 1; i
> active_tab_index
; --i
) {
1321 Tab
* tab
= tab_at(i
);
1325 if (inactive_tab_alpha
< 255)
1328 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1329 // Make sure non-active tabs are somewhat transparent.
1331 // If there are multiple tabs selected, fade non-selected tabs more to make
1332 // the selected tabs more noticable.
1333 int alpha
= selected_tab_count
> 1 ?
1334 kGlassFrameInactiveTabAlphaMultiSelection
:
1335 kGlassFrameInactiveTabAlpha
;
1336 paint
.setColor(SkColorSetARGB(alpha
, 255, 255, 255));
1337 paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
1338 paint
.setStyle(SkPaint::kFill_Style
);
1339 // The tabstrip area overlaps the toolbar area by 2 px.
1340 canvas
->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint
);
1343 // Now selected but not active. We don't want these dimmed if using native
1344 // frame, so they're painted after initial pass.
1345 for (size_t i
= 0; i
< selected_tabs
.size(); ++i
)
1346 selected_tabs
[i
]->Paint(canvas
);
1348 // Next comes the active tab.
1349 if (active_tab
&& !is_dragging
)
1350 active_tab
->Paint(canvas
);
1352 // Paint the New Tab button.
1353 if (inactive_tab_alpha
< 255)
1354 canvas
->SaveLayerAlpha(inactive_tab_alpha
);
1355 newtab_button_
->Paint(canvas
);
1356 if (inactive_tab_alpha
< 255)
1359 // And the dragged tabs.
1360 for (size_t i
= 0; i
< tabs_dragging
.size(); ++i
)
1361 tabs_dragging
[i
]->Paint(canvas
);
1363 // If the active tab is being dragged, it goes last.
1364 if (active_tab
&& is_dragging
)
1365 active_tab
->Paint(canvas
);
1368 const char* TabStrip::GetClassName() const {
1369 return kViewClassName
;
1372 gfx::Size
TabStrip::GetPreferredSize() {
1373 // For stacked tabs the minimum size is calculated as the size needed to
1374 // handle showing any number of tabs. Otherwise report the minimum width as
1375 // the size required for a single selected tab plus the new tab button. Don't
1376 // base it on the actual number of tabs because it's undesirable to have the
1377 // minimum window size change when a new tab is opened.
1379 if (touch_layout_
.get() || adjust_layout_
) {
1380 needed_width
= Tab::GetTouchWidth() +
1381 (2 * kStackedPadding
* kMaxStackedCount
);
1383 needed_width
= Tab::GetMinimumSelectedSize().width();
1385 needed_width
+= new_tab_button_width();
1386 if (immersive_style_
)
1387 return gfx::Size(needed_width
, Tab::GetImmersiveHeight());
1388 return gfx::Size(needed_width
, Tab::GetMinimumUnselectedSize().height());
1391 void TabStrip::OnDragEntered(const DropTargetEvent
& event
) {
1392 // Force animations to stop, otherwise it makes the index calculation tricky.
1393 StopAnimating(true);
1395 UpdateDropIndex(event
);
1398 base::string16 title
;
1400 // Check whether the event data includes supported drop data.
1401 if (event
.data().GetURLAndTitle(
1402 ui::OSExchangeData::CONVERT_FILENAMES
, &url
, &title
) &&
1404 drop_info_
->url
= url
;
1406 // For file:// URLs, kick off a MIME type request in case they're dropped.
1407 if (url
.SchemeIsFile())
1408 controller()->CheckFileSupported(url
);
1412 int TabStrip::OnDragUpdated(const DropTargetEvent
& event
) {
1413 // Update the drop index even if the file is unsupported, to allow
1414 // dragging a file to the contents of another tab.
1415 UpdateDropIndex(event
);
1417 if (!drop_info_
->file_supported
)
1418 return ui::DragDropTypes::DRAG_NONE
;
1420 return GetDropEffect(event
);
1423 void TabStrip::OnDragExited() {
1424 SetDropIndex(-1, false);
1427 int TabStrip::OnPerformDrop(const DropTargetEvent
& event
) {
1428 if (!drop_info_
.get())
1429 return ui::DragDropTypes::DRAG_NONE
;
1431 const int drop_index
= drop_info_
->drop_index
;
1432 const bool drop_before
= drop_info_
->drop_before
;
1433 const bool file_supported
= drop_info_
->file_supported
;
1435 // Hide the drop indicator.
1436 SetDropIndex(-1, false);
1438 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1439 // have been changed after |drop_info_| was created.
1441 base::string16 title
;
1442 if (!file_supported
||
1443 !event
.data().GetURLAndTitle(
1444 ui::OSExchangeData::CONVERT_FILENAMES
, &url
, &title
) ||
1446 return ui::DragDropTypes::DRAG_NONE
;
1448 controller()->PerformDrop(drop_before
, drop_index
, url
);
1450 return GetDropEffect(event
);
1453 void TabStrip::GetAccessibleState(ui::AccessibleViewState
* state
) {
1454 state
->role
= ui::AccessibilityTypes::ROLE_PAGETABLIST
;
1457 views::View
* TabStrip::GetEventHandlerForRect(const gfx::Rect
& rect
) {
1458 if (!views::UsePointBasedTargeting(rect
))
1459 return View::GetEventHandlerForRect(rect
);
1460 const gfx::Point
point(rect
.CenterPoint());
1462 if (!touch_layout_
.get()) {
1463 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1464 // want to interfere.
1465 views::View
* v
= View::GetEventHandlerForRect(rect
);
1466 if (v
&& v
!= this && strcmp(v
->GetClassName(), Tab::kViewClassName
))
1469 views::View
* tab
= FindTabHitByPoint(point
);
1473 if (newtab_button_
->visible()) {
1475 ConvertPointToViewAndGetEventHandler(this, newtab_button_
, point
);
1479 Tab
* tab
= FindTabForEvent(point
);
1481 return ConvertPointToViewAndGetEventHandler(this, tab
, point
);
1486 views::View
* TabStrip::GetTooltipHandlerForPoint(const gfx::Point
& point
) {
1487 if (!HitTestPoint(point
))
1490 if (!touch_layout_
.get()) {
1491 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1492 // want to interfere.
1493 views::View
* v
= View::GetTooltipHandlerForPoint(point
);
1494 if (v
&& v
!= this && strcmp(v
->GetClassName(), Tab::kViewClassName
))
1497 views::View
* tab
= FindTabHitByPoint(point
);
1501 if (newtab_button_
->visible()) {
1503 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_
, point
);
1507 Tab
* tab
= FindTabForEvent(point
);
1509 return ConvertPointToViewAndGetTooltipHandler(this, tab
, point
);
1515 int TabStrip::GetImmersiveHeight() {
1516 return Tab::GetImmersiveHeight();
1519 int TabStrip::GetMiniTabCount() const {
1521 while (mini_count
< tab_count() && tab_at(mini_count
)->data().mini
)
1526 ///////////////////////////////////////////////////////////////////////////////
1527 // TabStrip, views::ButtonListener implementation:
1529 void TabStrip::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
1530 if (sender
== newtab_button_
) {
1531 content::RecordAction(UserMetricsAction("NewTab_Button"));
1532 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON
,
1533 TabStripModel::NEW_TAB_ENUM_COUNT
);
1534 if (event
.IsMouseEvent()) {
1535 const ui::MouseEvent
& mouse
= static_cast<const ui::MouseEvent
&>(event
);
1536 if (mouse
.IsOnlyMiddleMouseButton()) {
1537 base::string16 clipboard_text
= GetClipboardText();
1538 if (!clipboard_text
.empty())
1539 controller()->CreateNewTabWithLocation(clipboard_text
);
1544 controller()->CreateNewTab();
1545 if (event
.type() == ui::ET_GESTURE_TAP
)
1546 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP
);
1550 ///////////////////////////////////////////////////////////////////////////////
1551 // TabStrip, protected:
1553 // Overridden to support automation. See automation_proxy_uitest.cc.
1554 const views::View
* TabStrip::GetViewByID(int view_id
) const {
1555 if (tab_count() > 0) {
1556 if (view_id
== VIEW_ID_TAB_LAST
) {
1557 return tab_at(tab_count() - 1);
1558 } else if ((view_id
>= VIEW_ID_TAB_0
) && (view_id
< VIEW_ID_TAB_LAST
)) {
1559 int index
= view_id
- VIEW_ID_TAB_0
;
1560 if (index
>= 0 && index
< tab_count()) {
1561 return tab_at(index
);
1568 return View::GetViewByID(view_id
);
1571 bool TabStrip::OnMousePressed(const ui::MouseEvent
& event
) {
1572 UpdateLayoutTypeFromMouseEvent(this, event
);
1573 // We can't return true here, else clicking in an empty area won't drag the
1578 bool TabStrip::OnMouseDragged(const ui::MouseEvent
& event
) {
1579 ContinueDrag(this, event
);
1583 void TabStrip::OnMouseReleased(const ui::MouseEvent
& event
) {
1584 EndDrag(END_DRAG_COMPLETE
);
1585 UpdateLayoutTypeFromMouseEvent(this, event
);
1588 void TabStrip::OnMouseCaptureLost() {
1589 EndDrag(END_DRAG_CAPTURE_LOST
);
1592 void TabStrip::OnMouseMoved(const ui::MouseEvent
& event
) {
1593 UpdateLayoutTypeFromMouseEvent(this, event
);
1596 void TabStrip::OnMouseEntered(const ui::MouseEvent
& event
) {
1597 SetResetToShrinkOnExit(true);
1600 void TabStrip::OnGestureEvent(ui::GestureEvent
* event
) {
1601 SetResetToShrinkOnExit(false);
1602 switch (event
->type()) {
1603 case ui::ET_GESTURE_SCROLL_END
:
1604 case ui::ET_SCROLL_FLING_START
:
1605 case ui::ET_GESTURE_END
:
1606 EndDrag(END_DRAG_COMPLETE
);
1607 if (adjust_layout_
) {
1608 SetLayoutType(TAB_STRIP_LAYOUT_STACKED
, true);
1609 controller_
->LayoutTypeMaybeChanged();
1613 case ui::ET_GESTURE_LONG_PRESS
:
1614 if (drag_controller_
.get())
1615 drag_controller_
->SetMoveBehavior(TabDragController::REORDER
);
1618 case ui::ET_GESTURE_LONG_TAP
: {
1619 EndDrag(END_DRAG_CANCEL
);
1620 gfx::Point local_point
= event
->location();
1621 Tab
* tab
= FindTabForEvent(local_point
);
1623 ConvertPointToScreen(this, &local_point
);
1624 ShowContextMenuForTab(tab
, local_point
, ui::MENU_SOURCE_TOUCH
);
1629 case ui::ET_GESTURE_SCROLL_UPDATE
:
1630 ContinueDrag(this, *event
);
1633 case ui::ET_GESTURE_BEGIN
:
1634 EndDrag(END_DRAG_CANCEL
);
1637 case ui::ET_GESTURE_TAP
: {
1638 const int active_index
= controller_
->GetActiveIndex();
1639 DCHECK_NE(-1, active_index
);
1640 Tab
* active_tab
= tab_at(active_index
);
1641 TouchUMA::GestureActionType action
= TouchUMA::GESTURE_TABNOSWITCH_TAP
;
1642 if (active_tab
->tab_activated_with_last_gesture_begin())
1643 action
= TouchUMA::GESTURE_TABSWITCH_TAP
;
1644 TouchUMA::RecordGestureAction(action
);
1651 event
->SetHandled();
1654 ///////////////////////////////////////////////////////////////////////////////
1655 // TabStrip, private:
1657 void TabStrip::Init() {
1658 set_id(VIEW_ID_TAB_STRIP
);
1659 // So we get enter/exit on children to switch layout type.
1660 set_notify_enter_exit_on_child(true);
1661 newtab_button_bounds_
.SetRect(0,
1663 newtab_button_asset_width(),
1664 newtab_button_asset_height() +
1665 newtab_button_v_offset());
1666 newtab_button_
= new NewTabButton(this, this);
1667 newtab_button_
->SetTooltipText(
1668 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB
));
1669 newtab_button_
->SetAccessibleName(
1670 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB
));
1671 newtab_button_
->SetImageAlignment(views::ImageButton::ALIGN_LEFT
,
1672 views::ImageButton::ALIGN_BOTTOM
);
1673 AddChildView(newtab_button_
);
1674 if (drop_indicator_width
== 0) {
1675 // Direction doesn't matter, both images are the same size.
1676 gfx::ImageSkia
* drop_image
= GetDropArrowImage(true);
1677 drop_indicator_width
= drop_image
->width();
1678 drop_indicator_height
= drop_image
->height();
1682 Tab
* TabStrip::CreateTab() {
1683 Tab
* tab
= new Tab(this);
1684 tab
->set_animation_container(animation_container_
.get());
1688 void TabStrip::StartInsertTabAnimation(int model_index
) {
1689 PrepareForAnimation();
1691 // The TabStrip can now use its entire width to lay out Tabs.
1692 in_tab_close_
= false;
1693 available_width_for_tabs_
= -1;
1695 GenerateIdealBounds();
1697 Tab
* tab
= tab_at(model_index
);
1698 if (model_index
== 0) {
1699 tab
->SetBounds(0, ideal_bounds(model_index
).y(), 0,
1700 ideal_bounds(model_index
).height());
1702 Tab
* last_tab
= tab_at(model_index
- 1);
1703 tab
->SetBounds(last_tab
->bounds().right() + tab_h_offset(),
1704 ideal_bounds(model_index
).y(), 0,
1705 ideal_bounds(model_index
).height());
1708 AnimateToIdealBounds();
1711 void TabStrip::StartMoveTabAnimation() {
1712 PrepareForAnimation();
1713 GenerateIdealBounds();
1714 AnimateToIdealBounds();
1717 void TabStrip::StartRemoveTabAnimation(int model_index
) {
1718 PrepareForAnimation();
1720 // Mark the tab as closing.
1721 Tab
* tab
= tab_at(model_index
);
1722 tab
->set_closing(true);
1724 RemoveTabFromViewModel(model_index
);
1726 ScheduleRemoveTabAnimation(tab
);
1729 void TabStrip::ScheduleRemoveTabAnimation(Tab
* tab
) {
1730 // Start an animation for the tabs.
1731 GenerateIdealBounds();
1732 AnimateToIdealBounds();
1734 // Animate the tab being closed to 0x0.
1735 gfx::Rect tab_bounds
= tab
->bounds();
1736 tab_bounds
.set_width(0);
1737 bounds_animator_
.AnimateViewTo(tab
, tab_bounds
);
1739 // Register delegate to do cleanup when done, BoundsAnimator takes
1740 // ownership of RemoveTabDelegate.
1741 bounds_animator_
.SetAnimationDelegate(tab
, new RemoveTabDelegate(this, tab
),
1744 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1745 // like the new tab button magically appears from beyond the end of the tab
1747 if (TabDragController::IsAttachedTo(this)) {
1748 bounds_animator_
.StopAnimatingView(newtab_button_
);
1749 newtab_button_
->SetBoundsRect(newtab_button_bounds_
);
1753 void TabStrip::AnimateToIdealBounds() {
1754 for (int i
= 0; i
< tab_count(); ++i
) {
1755 Tab
* tab
= tab_at(i
);
1756 if (!tab
->dragging())
1757 bounds_animator_
.AnimateViewTo(tab
, ideal_bounds(i
));
1760 bounds_animator_
.AnimateViewTo(newtab_button_
, newtab_button_bounds_
);
1763 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1764 return in_tab_close_
;
1767 void TabStrip::DoLayout() {
1768 last_layout_size_
= size();
1770 StopAnimating(false);
1772 SwapLayoutIfNecessary();
1774 if (touch_layout_
.get())
1775 touch_layout_
->SetWidth(size().width() - new_tab_button_width());
1777 GenerateIdealBounds();
1779 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_
);
1783 bounds_animator_
.StopAnimatingView(newtab_button_
);
1784 newtab_button_
->SetBoundsRect(newtab_button_bounds_
);
1787 void TabStrip::DragActiveTab(const std::vector
<int>& initial_positions
,
1789 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions
.size()));
1790 if (!touch_layout_
.get()) {
1791 StackDraggedTabs(delta
);
1794 SetIdealBoundsFromPositions(initial_positions
);
1795 touch_layout_
->DragActiveTab(delta
);
1799 void TabStrip::SetIdealBoundsFromPositions(const std::vector
<int>& positions
) {
1800 if (static_cast<size_t>(tab_count()) != positions
.size())
1803 for (int i
= 0; i
< tab_count(); ++i
) {
1804 gfx::Rect
bounds(ideal_bounds(i
));
1805 bounds
.set_x(positions
[i
]);
1806 set_ideal_bounds(i
, bounds
);
1810 void TabStrip::StackDraggedTabs(int delta
) {
1811 DCHECK(!touch_layout_
.get());
1812 GenerateIdealBounds();
1813 const int active_index
= controller_
->GetActiveIndex();
1814 DCHECK_NE(-1, active_index
);
1816 // Drag the tabs to the left, stacking tabs before the active tab.
1817 const int adjusted_delta
=
1818 std::min(ideal_bounds(active_index
).x() -
1819 kStackedPadding
* std::min(active_index
, kMaxStackedCount
),
1821 for (int i
= 0; i
<= active_index
; ++i
) {
1822 const int min_x
= std::min(i
, kMaxStackedCount
) * kStackedPadding
;
1823 gfx::Rect
new_bounds(ideal_bounds(i
));
1824 new_bounds
.set_x(std::max(min_x
, new_bounds
.x() - adjusted_delta
));
1825 set_ideal_bounds(i
, new_bounds
);
1827 const bool is_active_mini
= tab_at(active_index
)->data().mini
;
1828 const int active_width
= ideal_bounds(active_index
).width();
1829 for (int i
= active_index
+ 1; i
< tab_count(); ++i
) {
1830 const int max_x
= ideal_bounds(active_index
).x() +
1831 (kStackedPadding
* std::min(i
- active_index
, kMaxStackedCount
));
1832 gfx::Rect
new_bounds(ideal_bounds(i
));
1833 int new_x
= std::max(new_bounds
.x() + delta
, max_x
);
1834 if (new_x
== max_x
&& !tab_at(i
)->data().mini
&& !is_active_mini
&&
1835 new_bounds
.width() != active_width
)
1836 new_x
+= (active_width
- new_bounds
.width());
1837 new_bounds
.set_x(new_x
);
1838 set_ideal_bounds(i
, new_bounds
);
1841 // Drag the tabs to the right, stacking tabs after the active tab.
1842 const int last_tab_width
= ideal_bounds(tab_count() - 1).width();
1843 const int last_tab_x
= width() - new_tab_button_width() - last_tab_width
;
1844 if (active_index
== tab_count() - 1 &&
1845 ideal_bounds(tab_count() - 1).x() == last_tab_x
)
1847 const int adjusted_delta
=
1848 std::min(last_tab_x
-
1849 kStackedPadding
* std::min(tab_count() - active_index
- 1,
1851 ideal_bounds(active_index
).x(),
1853 for (int last_index
= tab_count() - 1, i
= last_index
; i
>= active_index
;
1855 const int max_x
= last_tab_x
-
1856 std::min(tab_count() - i
- 1, kMaxStackedCount
) * kStackedPadding
;
1857 gfx::Rect
new_bounds(ideal_bounds(i
));
1858 int new_x
= std::min(max_x
, new_bounds
.x() + adjusted_delta
);
1859 // Because of rounding not all tabs are the same width. Adjust the
1860 // position to accommodate this, otherwise the stacking is off.
1861 if (new_x
== max_x
&& !tab_at(i
)->data().mini
&&
1862 new_bounds
.width() != last_tab_width
)
1863 new_x
+= (last_tab_width
- new_bounds
.width());
1864 new_bounds
.set_x(new_x
);
1865 set_ideal_bounds(i
, new_bounds
);
1867 for (int i
= active_index
- 1; i
>= 0; --i
) {
1868 const int min_x
= ideal_bounds(active_index
).x() -
1869 std::min(active_index
- i
, kMaxStackedCount
) * kStackedPadding
;
1870 gfx::Rect
new_bounds(ideal_bounds(i
));
1871 new_bounds
.set_x(std::min(min_x
, new_bounds
.x() + delta
));
1872 set_ideal_bounds(i
, new_bounds
);
1874 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_
->x())
1875 newtab_button_
->SetVisible(false);
1877 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_
);
1881 bool TabStrip::IsStackingDraggedTabs() const {
1882 return drag_controller_
.get() && drag_controller_
->started_drag() &&
1883 (drag_controller_
->move_behavior() ==
1884 TabDragController::MOVE_VISIBILE_TABS
);
1887 void TabStrip::LayoutDraggedTabsAt(const std::vector
<Tab
*>& tabs
,
1889 const gfx::Point
& location
,
1890 bool initial_drag
) {
1891 // Immediately hide the new tab button if the last tab is being dragged.
1892 if (tab_at(tab_count() - 1)->dragging())
1893 newtab_button_
->SetVisible(false);
1894 std::vector
<gfx::Rect
> bounds
;
1895 CalculateBoundsForDraggedTabs(tabs
, &bounds
);
1896 DCHECK_EQ(tabs
.size(), bounds
.size());
1897 int active_tab_model_index
= GetModelIndexOfTab(active_tab
);
1898 int active_tab_index
= static_cast<int>(
1899 std::find(tabs
.begin(), tabs
.end(), active_tab
) - tabs
.begin());
1900 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1902 gfx::Rect new_bounds
= bounds
[i
];
1903 new_bounds
.Offset(location
.x(), location
.y());
1904 int consecutive_index
=
1905 active_tab_model_index
- (active_tab_index
- static_cast<int>(i
));
1906 // If this is the initial layout during a drag and the tabs aren't
1907 // consecutive animate the view into position. Do the same if the tab is
1908 // already animating (which means we previously caused it to animate).
1909 if ((initial_drag
&&
1910 GetModelIndexOfTab(tabs
[i
]) != consecutive_index
) ||
1911 bounds_animator_
.IsAnimating(tabs
[i
])) {
1912 bounds_animator_
.SetTargetBounds(tabs
[i
], new_bounds
);
1914 tab
->SetBoundsRect(new_bounds
);
1919 void TabStrip::CalculateBoundsForDraggedTabs(const std::vector
<Tab
*>& tabs
,
1920 std::vector
<gfx::Rect
>* bounds
) {
1922 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1924 if (i
> 0 && tab
->data().mini
!= tabs
[i
- 1]->data().mini
)
1925 x
+= kMiniToNonMiniGap
;
1926 gfx::Rect new_bounds
= tab
->bounds();
1927 new_bounds
.set_origin(gfx::Point(x
, 0));
1928 bounds
->push_back(new_bounds
);
1929 x
+= tab
->width() + tab_h_offset();
1933 int TabStrip::GetSizeNeededForTabs(const std::vector
<Tab
*>& tabs
) {
1935 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1937 width
+= tab
->width();
1938 if (i
> 0 && tab
->data().mini
!= tabs
[i
- 1]->data().mini
)
1939 width
+= kMiniToNonMiniGap
;
1941 if (tabs
.size() > 0)
1942 width
+= tab_h_offset() * static_cast<int>(tabs
.size() - 1);
1946 void TabStrip::RemoveTabFromViewModel(int index
) {
1947 // We still need to paint the tab until we actually remove it. Put it
1948 // in tabs_closing_map_ so we can find it.
1949 tabs_closing_map_
[index
].push_back(tab_at(index
));
1950 UpdateTabsClosingMap(index
+ 1, -1);
1951 tabs_
.Remove(index
);
1954 void TabStrip::RemoveAndDeleteTab(Tab
* tab
) {
1955 scoped_ptr
<Tab
> deleter(tab
);
1956 for (TabsClosingMap::iterator
i(tabs_closing_map_
.begin());
1957 i
!= tabs_closing_map_
.end(); ++i
) {
1958 std::vector
<Tab
*>::iterator j
=
1959 std::find(i
->second
.begin(), i
->second
.end(), tab
);
1960 if (j
!= i
->second
.end()) {
1962 if (i
->second
.empty())
1963 tabs_closing_map_
.erase(i
);
1970 void TabStrip::UpdateTabsClosingMap(int index
, int delta
) {
1971 if (tabs_closing_map_
.empty())
1975 tabs_closing_map_
.find(index
- 1) != tabs_closing_map_
.end() &&
1976 tabs_closing_map_
.find(index
) != tabs_closing_map_
.end()) {
1977 const std::vector
<Tab
*>& tabs(tabs_closing_map_
[index
]);
1978 tabs_closing_map_
[index
- 1].insert(
1979 tabs_closing_map_
[index
- 1].end(), tabs
.begin(), tabs
.end());
1981 TabsClosingMap updated_map
;
1982 for (TabsClosingMap::iterator
i(tabs_closing_map_
.begin());
1983 i
!= tabs_closing_map_
.end(); ++i
) {
1984 if (i
->first
> index
)
1985 updated_map
[i
->first
+ delta
] = i
->second
;
1986 else if (i
->first
< index
)
1987 updated_map
[i
->first
] = i
->second
;
1989 if (delta
> 0 && tabs_closing_map_
.find(index
) != tabs_closing_map_
.end())
1990 updated_map
[index
+ delta
] = tabs_closing_map_
[index
];
1991 tabs_closing_map_
.swap(updated_map
);
1994 void TabStrip::StartedDraggingTabs(const std::vector
<Tab
*>& tabs
) {
1995 // Let the controller know that the user started dragging tabs.
1996 controller()->OnStartedDraggingTabs();
1998 // Hide the new tab button immediately if we didn't originate the drag.
1999 if (!drag_controller_
.get())
2000 newtab_button_
->SetVisible(false);
2002 PrepareForAnimation();
2004 // Reset dragging state of existing tabs.
2005 for (int i
= 0; i
< tab_count(); ++i
)
2006 tab_at(i
)->set_dragging(false);
2008 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
2009 tabs
[i
]->set_dragging(true);
2010 bounds_animator_
.StopAnimatingView(tabs
[i
]);
2013 // Move the dragged tabs to their ideal bounds.
2014 GenerateIdealBounds();
2016 // Sets the bounds of the dragged tabs.
2017 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
2018 int tab_data_index
= GetModelIndexOfTab(tabs
[i
]);
2019 DCHECK_NE(-1, tab_data_index
);
2020 tabs
[i
]->SetBoundsRect(ideal_bounds(tab_data_index
));
2025 void TabStrip::DraggedTabsDetached() {
2026 // Let the controller know that the user is not dragging this tabstrip's tabs
2028 controller()->OnStoppedDraggingTabs();
2029 newtab_button_
->SetVisible(true);
2032 void TabStrip::StoppedDraggingTabs(const std::vector
<Tab
*>& tabs
,
2033 const std::vector
<int>& initial_positions
,
2036 // Let the controller know that the user stopped dragging tabs.
2037 controller()->OnStoppedDraggingTabs();
2039 newtab_button_
->SetVisible(true);
2040 if (move_only
&& touch_layout_
.get()) {
2042 touch_layout_
->SizeToFit();
2044 SetIdealBoundsFromPositions(initial_positions
);
2047 bool is_first_tab
= true;
2048 for (size_t i
= 0; i
< tabs
.size(); ++i
)
2049 StoppedDraggingTab(tabs
[i
], &is_first_tab
);
2052 void TabStrip::StoppedDraggingTab(Tab
* tab
, bool* is_first_tab
) {
2053 int tab_data_index
= GetModelIndexOfTab(tab
);
2054 if (tab_data_index
== -1) {
2055 // The tab was removed before the drag completed. Don't do anything.
2059 if (*is_first_tab
) {
2060 *is_first_tab
= false;
2061 PrepareForAnimation();
2063 // Animate the view back to its correct position.
2064 GenerateIdealBounds();
2065 AnimateToIdealBounds();
2067 bounds_animator_
.AnimateViewTo(tab
, ideal_bounds(tab_data_index
));
2068 // Install a delegate to reset the dragging state when done. We have to leave
2069 // dragging true for the tab otherwise it'll draw beneath the new tab button.
2070 bounds_animator_
.SetAnimationDelegate(
2071 tab
, new ResetDraggingStateDelegate(tab
), true);
2074 void TabStrip::OwnDragController(TabDragController
* controller
) {
2075 // Typically, ReleaseDragController() and OwnDragController() calls are paired
2076 // via corresponding calls to TabDragController::Detach() and
2077 // TabDragController::Attach(). There is one exception to that rule: when a
2078 // drag might start, we create a TabDragController that is owned by the
2079 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
2080 // we then call Attach() on the source tabstrip, but since the source tabstrip
2081 // already owns the TabDragController, so we don't need to do anything.
2082 if (controller
!= drag_controller_
.get())
2083 drag_controller_
.reset(controller
);
2086 void TabStrip::DestroyDragController() {
2087 newtab_button_
->SetVisible(true);
2088 drag_controller_
.reset();
2091 TabDragController
* TabStrip::ReleaseDragController() {
2092 return drag_controller_
.release();
2095 void TabStrip::PaintClosingTabs(gfx::Canvas
* canvas
, int index
) {
2096 if (tabs_closing_map_
.find(index
) == tabs_closing_map_
.end())
2099 const std::vector
<Tab
*>& tabs
= tabs_closing_map_
[index
];
2100 for (std::vector
<Tab
*>::const_reverse_iterator
i(tabs
.rbegin());
2101 i
!= tabs
.rend(); ++i
) {
2102 (*i
)->Paint(canvas
);
2106 void TabStrip::UpdateLayoutTypeFromMouseEvent(views::View
* source
,
2107 const ui::MouseEvent
& event
) {
2108 if (!GetAdjustLayout())
2111 // The following code attempts to switch to TAB_STRIP_LAYOUT_SHRINK when the
2112 // mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
2113 // TAB_STRIP_LAYOUT_STACKED when a touch device is used. This is made
2114 // problematic by windows generating mouse move events that do not clearly
2115 // indicate the move is the result of a touch device. This assumes a real
2116 // mouse is used if |kMouseMoveCountBeforeConsiderReal| mouse move events are
2117 // received within the time window |kMouseMoveTimeMS|. At the time we get a
2118 // mouse press we know whether its from a touch device or not, but we don't
2119 // layout then else everything shifts. Instead we wait for the release.
2121 // TODO(sky): revisit this when touch events are really plumbed through.
2123 switch (event
.type()) {
2124 case ui::ET_MOUSE_PRESSED
:
2125 mouse_move_count_
= 0;
2126 last_mouse_move_time_
= base::TimeTicks();
2127 SetResetToShrinkOnExit((event
.flags() & ui::EF_FROM_TOUCH
) == 0);
2128 if (reset_to_shrink_on_exit_
&& touch_layout_
.get()) {
2129 gfx::Point
tab_strip_point(event
.location());
2130 views::View::ConvertPointToTarget(source
, this, &tab_strip_point
);
2131 Tab
* tab
= FindTabForEvent(tab_strip_point
);
2132 if (tab
&& touch_layout_
->IsStacked(GetModelIndexOfTab(tab
))) {
2133 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK
, true);
2134 controller_
->LayoutTypeMaybeChanged();
2139 case ui::ET_MOUSE_MOVED
: {
2140 #if defined(USE_ASH)
2141 // Ash does not synthesize mouse events from touch events.
2142 SetResetToShrinkOnExit(true);
2144 gfx::Point
location(event
.location());
2145 ConvertPointToTarget(source
, this, &location
);
2146 if (location
== last_mouse_move_location_
)
2147 return; // Ignore spurious moves.
2148 last_mouse_move_location_
= location
;
2149 if ((event
.flags() & ui::EF_FROM_TOUCH
) == 0 &&
2150 (event
.flags() & ui::EF_IS_SYNTHESIZED
) == 0) {
2151 if ((base::TimeTicks::Now() - last_mouse_move_time_
).InMilliseconds() <
2153 if (mouse_move_count_
++ == kMouseMoveCountBeforeConsiderReal
)
2154 SetResetToShrinkOnExit(true);
2156 mouse_move_count_
= 1;
2157 last_mouse_move_time_
= base::TimeTicks::Now();
2160 last_mouse_move_time_
= base::TimeTicks();
2166 case ui::ET_MOUSE_RELEASED
: {
2167 gfx::Point
location(event
.location());
2168 ConvertPointToTarget(source
, this, &location
);
2169 last_mouse_move_location_
= location
;
2170 mouse_move_count_
= 0;
2171 last_mouse_move_time_
= base::TimeTicks();
2172 if ((event
.flags() & ui::EF_FROM_TOUCH
) == ui::EF_FROM_TOUCH
) {
2173 SetLayoutType(TAB_STRIP_LAYOUT_STACKED
, true);
2174 controller_
->LayoutTypeMaybeChanged();
2184 void TabStrip::GetCurrentTabWidths(double* unselected_width
,
2185 double* selected_width
) const {
2186 *unselected_width
= current_unselected_width_
;
2187 *selected_width
= current_selected_width_
;
2190 void TabStrip::GetDesiredTabWidths(int tab_count
,
2192 double* unselected_width
,
2193 double* selected_width
) const {
2194 DCHECK(tab_count
>= 0 && mini_tab_count
>= 0 && mini_tab_count
<= tab_count
);
2195 const double min_unselected_width
= Tab::GetMinimumUnselectedSize().width();
2196 const double min_selected_width
= Tab::GetMinimumSelectedSize().width();
2198 *unselected_width
= min_unselected_width
;
2199 *selected_width
= min_selected_width
;
2201 if (tab_count
== 0) {
2202 // Return immediately to avoid divide-by-zero below.
2206 // Determine how much space we can actually allocate to tabs.
2207 int available_width
;
2208 if (available_width_for_tabs_
< 0) {
2209 available_width
= width() - new_tab_button_width();
2211 // Interesting corner case: if |available_width_for_tabs_| > the result
2212 // of the calculation in the conditional arm above, the strip is in
2213 // overflow. We can either use the specified width or the true available
2214 // width here; the first preserves the consistent "leave the last tab under
2215 // the user's mouse so they can close many tabs" behavior at the cost of
2216 // prolonging the glitchy appearance of the overflow state, while the second
2217 // gets us out of overflow as soon as possible but forces the user to move
2218 // their mouse for a few tabs' worth of closing. We choose visual
2219 // imperfection over behavioral imperfection and select the first option.
2220 available_width
= available_width_for_tabs_
;
2223 if (mini_tab_count
> 0) {
2224 available_width
-= mini_tab_count
* (Tab::GetMiniWidth() + tab_h_offset());
2225 tab_count
-= mini_tab_count
;
2226 if (tab_count
== 0) {
2227 *selected_width
= *unselected_width
= Tab::GetStandardSize().width();
2230 // Account for gap between the last mini-tab and first non-mini-tab.
2231 available_width
-= kMiniToNonMiniGap
;
2234 // Calculate the desired tab widths by dividing the available space into equal
2235 // portions. Don't let tabs get larger than the "standard width" or smaller
2236 // than the minimum width for each type, respectively.
2237 const int total_offset
= tab_h_offset() * (tab_count
- 1);
2238 const double desired_tab_width
= std::min((static_cast<double>(
2239 available_width
- total_offset
) / static_cast<double>(tab_count
)),
2240 static_cast<double>(Tab::GetStandardSize().width()));
2241 *unselected_width
= std::max(desired_tab_width
, min_unselected_width
);
2242 *selected_width
= std::max(desired_tab_width
, min_selected_width
);
2244 // When there are multiple tabs, we'll have one selected and some unselected
2245 // tabs. If the desired width was between the minimum sizes of these types,
2246 // try to shrink the tabs with the smaller minimum. For example, if we have
2247 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2248 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2249 // width of 1, the above code would set *unselected_width = 2.5,
2250 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2251 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2253 if (tab_count
> 1) {
2254 if ((min_unselected_width
< min_selected_width
) &&
2255 (desired_tab_width
< min_selected_width
)) {
2256 // Unselected width = (total width - selected width) / (num_tabs - 1)
2257 *unselected_width
= std::max(static_cast<double>(
2258 available_width
- total_offset
- min_selected_width
) /
2259 static_cast<double>(tab_count
- 1), min_unselected_width
);
2260 } else if ((min_unselected_width
> min_selected_width
) &&
2261 (desired_tab_width
< min_unselected_width
)) {
2262 // Selected width = (total width - (unselected width * (num_tabs - 1)))
2263 *selected_width
= std::max(available_width
- total_offset
-
2264 (min_unselected_width
* (tab_count
- 1)), min_selected_width
);
2269 void TabStrip::ResizeLayoutTabs() {
2270 // We've been called back after the TabStrip has been emptied out (probably
2271 // just prior to the window being destroyed). We need to do nothing here or
2272 // else GetTabAt below will crash.
2273 if (tab_count() == 0)
2276 // It is critically important that this is unhooked here, otherwise we will
2277 // keep spying on messages forever.
2278 RemoveMessageLoopObserver();
2280 in_tab_close_
= false;
2281 available_width_for_tabs_
= -1;
2282 int mini_tab_count
= GetMiniTabCount();
2283 if (mini_tab_count
== tab_count()) {
2284 // Only mini-tabs, we know the tab widths won't have changed (all
2285 // mini-tabs have the same width), so there is nothing to do.
2288 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2289 // then the width of the active tab may not change, but other widths may
2290 // have. This is particularly important if we've overflowed (all tabs are at
2292 StartResizeLayoutAnimation();
2295 void TabStrip::ResizeLayoutTabsFromTouch() {
2296 // Don't resize if the user is interacting with the tabstrip.
2297 if (!drag_controller_
.get())
2300 StartResizeLayoutTabsFromTouchTimer();
2303 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2304 resize_layout_timer_
.Stop();
2305 resize_layout_timer_
.Start(
2306 FROM_HERE
, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS
),
2307 this, &TabStrip::ResizeLayoutTabsFromTouch
);
2310 void TabStrip::SetTabBoundsForDrag(const std::vector
<gfx::Rect
>& tab_bounds
) {
2311 StopAnimating(false);
2312 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds
.size()));
2313 for (int i
= 0; i
< tab_count(); ++i
)
2314 tab_at(i
)->SetBoundsRect(tab_bounds
[i
]);
2315 // Reset the layout size as we've effectively layed out a different size.
2316 // This ensures a layout happens after the drag is done.
2317 last_layout_size_
= gfx::Size();
2320 void TabStrip::AddMessageLoopObserver() {
2321 if (!mouse_watcher_
.get()) {
2322 mouse_watcher_
.reset(
2323 new views::MouseWatcher(
2324 new views::MouseWatcherViewHost(
2325 this, gfx::Insets(0, 0, kTabStripAnimationVSlop
, 0)),
2328 mouse_watcher_
->Start();
2331 void TabStrip::RemoveMessageLoopObserver() {
2332 mouse_watcher_
.reset(NULL
);
2335 gfx::Rect
TabStrip::GetDropBounds(int drop_index
,
2338 DCHECK_NE(drop_index
, -1);
2340 if (drop_index
< tab_count()) {
2341 Tab
* tab
= tab_at(drop_index
);
2343 center_x
= tab
->x() - (tab_h_offset() / 2);
2345 center_x
= tab
->x() + (tab
->width() / 2);
2347 Tab
* last_tab
= tab_at(drop_index
- 1);
2348 center_x
= last_tab
->x() + last_tab
->width() + (tab_h_offset() / 2);
2351 // Mirror the center point if necessary.
2352 center_x
= GetMirroredXInView(center_x
);
2354 // Determine the screen bounds.
2355 gfx::Point
drop_loc(center_x
- drop_indicator_width
/ 2,
2356 -drop_indicator_height
);
2357 ConvertPointToScreen(this, &drop_loc
);
2358 gfx::Rect
drop_bounds(drop_loc
.x(), drop_loc
.y(), drop_indicator_width
,
2359 drop_indicator_height
);
2361 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2362 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2363 gfx::Display display
= screen
->GetDisplayMatching(drop_bounds
);
2364 *is_beneath
= !display
.bounds().Contains(drop_bounds
);
2366 drop_bounds
.Offset(0, drop_bounds
.height() + height());
2371 void TabStrip::UpdateDropIndex(const DropTargetEvent
& event
) {
2372 // If the UI layout is right-to-left, we need to mirror the mouse
2373 // coordinates since we calculate the drop index based on the
2374 // original (and therefore non-mirrored) positions of the tabs.
2375 const int x
= GetMirroredXInView(event
.x());
2376 // We don't allow replacing the urls of mini-tabs.
2377 for (int i
= GetMiniTabCount(); i
< tab_count(); ++i
) {
2378 Tab
* tab
= tab_at(i
);
2379 const int tab_max_x
= tab
->x() + tab
->width();
2380 const int hot_width
= tab
->width() / kTabEdgeRatioInverse
;
2381 if (x
< tab_max_x
) {
2382 if (x
< tab
->x() + hot_width
)
2383 SetDropIndex(i
, true);
2384 else if (x
>= tab_max_x
- hot_width
)
2385 SetDropIndex(i
+ 1, true);
2387 SetDropIndex(i
, false);
2392 // The drop isn't over a tab, add it to the end.
2393 SetDropIndex(tab_count(), true);
2396 void TabStrip::SetDropIndex(int tab_data_index
, bool drop_before
) {
2397 // Let the controller know of the index update.
2398 controller()->OnDropIndexUpdate(tab_data_index
, drop_before
);
2400 if (tab_data_index
== -1) {
2401 if (drop_info_
.get())
2402 drop_info_
.reset(NULL
);
2406 if (drop_info_
.get() && drop_info_
->drop_index
== tab_data_index
&&
2407 drop_info_
->drop_before
== drop_before
) {
2412 gfx::Rect drop_bounds
= GetDropBounds(tab_data_index
, drop_before
,
2415 if (!drop_info_
.get()) {
2417 new DropInfo(tab_data_index
, drop_before
, !is_beneath
, GetWidget()));
2419 drop_info_
->drop_index
= tab_data_index
;
2420 drop_info_
->drop_before
= drop_before
;
2421 if (is_beneath
== drop_info_
->point_down
) {
2422 drop_info_
->point_down
= !is_beneath
;
2423 drop_info_
->arrow_view
->SetImage(
2424 GetDropArrowImage(drop_info_
->point_down
));
2428 // Reposition the window. Need to show it too as the window is initially
2430 drop_info_
->arrow_window
->SetBounds(drop_bounds
);
2431 drop_info_
->arrow_window
->Show();
2434 int TabStrip::GetDropEffect(const ui::DropTargetEvent
& event
) {
2435 const int source_ops
= event
.source_operations();
2436 if (source_ops
& ui::DragDropTypes::DRAG_COPY
)
2437 return ui::DragDropTypes::DRAG_COPY
;
2438 if (source_ops
& ui::DragDropTypes::DRAG_LINK
)
2439 return ui::DragDropTypes::DRAG_LINK
;
2440 return ui::DragDropTypes::DRAG_MOVE
;
2444 gfx::ImageSkia
* TabStrip::GetDropArrowImage(bool is_down
) {
2445 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2446 is_down
? IDR_TAB_DROP_DOWN
: IDR_TAB_DROP_UP
);
2449 // TabStrip::DropInfo ----------------------------------------------------------
2451 TabStrip::DropInfo::DropInfo(int drop_index
,
2454 views::Widget
* context
)
2455 : drop_index(drop_index
),
2456 drop_before(drop_before
),
2457 point_down(point_down
),
2458 file_supported(true) {
2459 arrow_view
= new views::ImageView
;
2460 arrow_view
->SetImage(GetDropArrowImage(point_down
));
2462 arrow_window
= new views::Widget
;
2463 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
2464 params
.keep_on_top
= true;
2465 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
2466 params
.accept_events
= false;
2467 params
.can_activate
= false;
2468 params
.bounds
= gfx::Rect(drop_indicator_width
, drop_indicator_height
);
2469 params
.context
= context
->GetNativeView();
2470 arrow_window
->Init(params
);
2471 arrow_window
->SetContentsView(arrow_view
);
2474 TabStrip::DropInfo::~DropInfo() {
2475 // Close eventually deletes the window, which deletes arrow_view too.
2476 arrow_window
->Close();
2479 ///////////////////////////////////////////////////////////////////////////////
2481 void TabStrip::PrepareForAnimation() {
2482 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2483 for (int i
= 0; i
< tab_count(); ++i
)
2484 tab_at(i
)->set_dragging(false);
2488 void TabStrip::GenerateIdealBounds() {
2491 if (touch_layout_
.get()) {
2492 if (tabs_
.view_size() == 0)
2495 int new_tab_x
= tabs_
.ideal_bounds(tabs_
.view_size() - 1).right() +
2496 newtab_button_h_offset();
2497 newtab_button_bounds_
.set_origin(gfx::Point(new_tab_x
, new_tab_y
));
2501 double unselected
, selected
;
2502 GetDesiredTabWidths(tab_count(), GetMiniTabCount(), &unselected
, &selected
);
2503 current_unselected_width_
= unselected
;
2504 current_selected_width_
= selected
;
2506 // NOTE: This currently assumes a tab's height doesn't differ based on
2507 // selected state or the number of tabs in the strip!
2508 int tab_height
= Tab::GetStandardSize().height();
2509 int first_non_mini_index
= 0;
2510 double tab_x
= GenerateIdealBoundsForMiniTabs(&first_non_mini_index
);
2511 for (int i
= first_non_mini_index
; i
< tab_count(); ++i
) {
2512 Tab
* tab
= tab_at(i
);
2513 DCHECK(!tab
->data().mini
);
2514 double tab_width
= tab
->IsActive() ? selected
: unselected
;
2515 double end_of_tab
= tab_x
+ tab_width
;
2516 int rounded_tab_x
= Round(tab_x
);
2519 gfx::Rect(rounded_tab_x
, 0, Round(end_of_tab
) - rounded_tab_x
,
2521 tab_x
= end_of_tab
+ tab_h_offset();
2524 // Update bounds of new tab button.
2526 if (abs(Round(unselected
) - Tab::GetStandardSize().width()) > 1 &&
2528 // We're shrinking tabs, so we need to anchor the New Tab button to the
2529 // right edge of the TabStrip's bounds, rather than the right edge of the
2530 // right-most Tab, otherwise it'll bounce when animating.
2531 new_tab_x
= width() - newtab_button_bounds_
.width();
2533 new_tab_x
= Round(tab_x
- tab_h_offset()) + newtab_button_h_offset();
2535 newtab_button_bounds_
.set_origin(gfx::Point(new_tab_x
, new_tab_y
));
2538 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index
) {
2540 int mini_width
= Tab::GetMiniWidth();
2541 int tab_height
= Tab::GetStandardSize().height();
2543 for (; index
< tab_count() && tab_at(index
)->data().mini
; ++index
) {
2544 set_ideal_bounds(index
,
2545 gfx::Rect(next_x
, 0, mini_width
, tab_height
));
2546 next_x
+= mini_width
+ tab_h_offset();
2548 if (index
> 0 && index
< tab_count())
2549 next_x
+= kMiniToNonMiniGap
;
2550 if (first_non_mini_index
)
2551 *first_non_mini_index
= index
;
2556 int TabStrip::new_tab_button_width() {
2557 return newtab_button_asset_width() + newtab_button_h_offset();
2561 int TabStrip::button_v_offset() {
2562 return newtab_button_v_offset();
2565 int TabStrip::tab_area_width() const {
2566 return width() - new_tab_button_width();
2569 void TabStrip::StartResizeLayoutAnimation() {
2570 PrepareForAnimation();
2571 GenerateIdealBounds();
2572 AnimateToIdealBounds();
2575 void TabStrip::StartMiniTabAnimation() {
2576 in_tab_close_
= false;
2577 available_width_for_tabs_
= -1;
2579 PrepareForAnimation();
2581 GenerateIdealBounds();
2582 AnimateToIdealBounds();
2585 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index
) {
2586 // The user initiated the close. We want to persist the bounds of all the
2587 // existing tabs, so we manually shift ideal_bounds then animate.
2588 Tab
* tab_closing
= tab_at(model_index
);
2589 int delta
= tab_closing
->width() + tab_h_offset();
2590 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2591 // add the extra padding.
2592 DCHECK_NE(model_index
+ 1, tab_count());
2593 if (tab_closing
->data().mini
&& model_index
+ 1 < tab_count() &&
2594 !tab_at(model_index
+ 1)->data().mini
) {
2595 delta
+= kMiniToNonMiniGap
;
2598 for (int i
= model_index
+ 1; i
< tab_count(); ++i
) {
2599 gfx::Rect bounds
= ideal_bounds(i
);
2600 bounds
.set_x(bounds
.x() - delta
);
2601 set_ideal_bounds(i
, bounds
);
2604 newtab_button_bounds_
.set_x(newtab_button_bounds_
.x() - delta
);
2606 PrepareForAnimation();
2608 tab_closing
->set_closing(true);
2610 // We still need to paint the tab until we actually remove it. Put it in
2611 // tabs_closing_map_ so we can find it.
2612 RemoveTabFromViewModel(model_index
);
2614 AnimateToIdealBounds();
2616 gfx::Rect tab_bounds
= tab_closing
->bounds();
2617 tab_bounds
.set_width(0);
2618 bounds_animator_
.AnimateViewTo(tab_closing
, tab_bounds
);
2620 // Register delegate to do cleanup when done, BoundsAnimator takes
2621 // ownership of RemoveTabDelegate.
2622 bounds_animator_
.SetAnimationDelegate(
2624 new RemoveTabDelegate(this, tab_closing
),
2628 bool TabStrip::IsPointInTab(Tab
* tab
,
2629 const gfx::Point
& point_in_tabstrip_coords
) {
2630 gfx::Point
point_in_tab_coords(point_in_tabstrip_coords
);
2631 View::ConvertPointToTarget(this, tab
, &point_in_tab_coords
);
2632 return tab
->HitTestPoint(point_in_tab_coords
);
2635 int TabStrip::GetStartXForNormalTabs() const {
2636 int mini_tab_count
= GetMiniTabCount();
2637 if (mini_tab_count
== 0)
2639 return mini_tab_count
* (Tab::GetMiniWidth() + tab_h_offset()) +
2643 Tab
* TabStrip::FindTabForEvent(const gfx::Point
& point
) {
2644 if (touch_layout_
.get()) {
2645 int active_tab_index
= touch_layout_
->active_index();
2646 if (active_tab_index
!= -1) {
2647 Tab
* tab
= FindTabForEventFrom(point
, active_tab_index
, -1);
2649 tab
= FindTabForEventFrom(point
, active_tab_index
+ 1, 1);
2651 } else if (tab_count()) {
2652 return FindTabForEventFrom(point
, 0, 1);
2655 for (int i
= 0; i
< tab_count(); ++i
) {
2656 if (IsPointInTab(tab_at(i
), point
))
2663 Tab
* TabStrip::FindTabForEventFrom(const gfx::Point
& point
,
2666 // |start| equals tab_count() when there are only pinned tabs.
2667 if (start
== tab_count())
2669 for (int i
= start
; i
>= 0 && i
< tab_count(); i
+= delta
) {
2670 if (IsPointInTab(tab_at(i
), point
))
2676 views::View
* TabStrip::FindTabHitByPoint(const gfx::Point
& point
) {
2677 // The display order doesn't necessarily match the child list order, so we
2678 // walk the display list hit-testing Tabs. Since the active tab always
2679 // renders on top of adjacent tabs, it needs to be hit-tested before any
2680 // left-adjacent Tab, so we look ahead for it as we walk.
2681 for (int i
= 0; i
< tab_count(); ++i
) {
2682 Tab
* next_tab
= i
< (tab_count() - 1) ? tab_at(i
+ 1) : NULL
;
2683 if (next_tab
&& next_tab
->IsActive() && IsPointInTab(next_tab
, point
))
2685 if (IsPointInTab(tab_at(i
), point
))
2692 std::vector
<int> TabStrip::GetTabXCoordinates() {
2693 std::vector
<int> results
;
2694 for (int i
= 0; i
< tab_count(); ++i
)
2695 results
.push_back(ideal_bounds(i
).x());
2699 void TabStrip::SwapLayoutIfNecessary() {
2700 bool needs_touch
= NeedsTouchLayout();
2701 bool using_touch
= touch_layout_
.get() != NULL
;
2702 if (needs_touch
== using_touch
)
2706 gfx::Size
tab_size(Tab::GetMinimumSelectedSize());
2707 tab_size
.set_width(Tab::GetTouchWidth());
2708 touch_layout_
.reset(new StackedTabStripLayout(
2714 touch_layout_
->SetWidth(width() - new_tab_button_width());
2715 // This has to be after SetWidth() as SetWidth() is going to reset the
2716 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2717 // many mini-tabs there are).
2718 GenerateIdealBoundsForMiniTabs(NULL
);
2719 touch_layout_
->SetXAndMiniCount(GetStartXForNormalTabs(),
2721 touch_layout_
->SetActiveIndex(controller_
->GetActiveIndex());
2723 touch_layout_
.reset();
2725 PrepareForAnimation();
2726 GenerateIdealBounds();
2727 AnimateToIdealBounds();
2730 bool TabStrip::NeedsTouchLayout() const {
2731 if (layout_type_
== TAB_STRIP_LAYOUT_SHRINK
)
2734 int mini_tab_count
= GetMiniTabCount();
2735 int normal_count
= tab_count() - mini_tab_count
;
2736 if (normal_count
<= 1 || normal_count
== mini_tab_count
)
2738 int x
= GetStartXForNormalTabs();
2739 int available_width
= width() - x
- new_tab_button_width();
2740 return (Tab::GetTouchWidth() * normal_count
+
2741 tab_h_offset() * (normal_count
- 1)) > available_width
;
2744 void TabStrip::SetResetToShrinkOnExit(bool value
) {
2745 if (!GetAdjustLayout())
2748 if (value
&& layout_type_
== TAB_STRIP_LAYOUT_SHRINK
)
2749 value
= false; // We're already at TAB_STRIP_LAYOUT_SHRINK.
2751 if (value
== reset_to_shrink_on_exit_
)
2754 reset_to_shrink_on_exit_
= value
;
2755 // Add an observer so we know when the mouse moves out of the tabstrip.
2756 if (reset_to_shrink_on_exit_
)
2757 AddMessageLoopObserver();
2759 RemoveMessageLoopObserver();
2762 bool TabStrip::GetAdjustLayout() const {
2763 if (!adjust_layout_
)
2766 #if defined(USE_AURA)
2767 return chrome::GetHostDesktopTypeForNativeView(
2768 GetWidget()->GetNativeView()) == chrome::HOST_DESKTOP_TYPE_ASH
;
2770 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH
)