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 "chrome/grit/generated_resources.h"
31 #include "content/public/browser/user_metrics.h"
32 #include "grit/theme_resources.h"
33 #include "ui/accessibility/ax_view_state.h"
34 #include "ui/base/default_theme_provider.h"
35 #include "ui/base/dragdrop/drag_drop_types.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/models/list_selection_model.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/gfx/animation/animation_container.h"
40 #include "ui/gfx/animation/throb_animation.h"
41 #include "ui/gfx/canvas.h"
42 #include "ui/gfx/display.h"
43 #include "ui/gfx/geometry/rect_conversions.h"
44 #include "ui/gfx/geometry/size.h"
45 #include "ui/gfx/image/image_skia.h"
46 #include "ui/gfx/image/image_skia_operations.h"
47 #include "ui/gfx/path.h"
48 #include "ui/gfx/screen.h"
49 #include "ui/gfx/skia_util.h"
50 #include "ui/views/controls/image_view.h"
51 #include "ui/views/masked_targeter_delegate.h"
52 #include "ui/views/mouse_watcher_view_host.h"
53 #include "ui/views/rect_based_targeting_utils.h"
54 #include "ui/views/view_model_utils.h"
55 #include "ui/views/view_targeter.h"
56 #include "ui/views/widget/root_view.h"
57 #include "ui/views/widget/widget.h"
58 #include "ui/views/window/non_client_view.h"
61 #include "ui/gfx/win/dpi.h"
62 #include "ui/gfx/win/hwnd_util.h"
63 #include "ui/views/widget/monitor_win.h"
64 #include "ui/views/win/hwnd_util.h"
67 using base::UserMetricsAction
;
68 using ui::DropTargetEvent
;
72 static const int kTabStripAnimationVSlop
= 40;
73 // Inactive tabs in a native frame are slightly transparent.
74 static const int kGlassFrameInactiveTabAlpha
= 200;
75 // If there are multiple tabs selected then make non-selected inactive tabs
76 // even more transparent.
77 static const int kGlassFrameInactiveTabAlphaMultiSelection
= 150;
79 // Alpha applied to all elements save the selected tabs.
80 static const int kInactiveTabAndNewTabButtonAlphaAsh
= 230;
81 static const int kInactiveTabAndNewTabButtonAlpha
= 255;
83 // Inverse ratio of the width of a tab edge to the width of the tab. When
84 // hovering over the left or right edge of a tab, the drop indicator will
85 // point between tabs.
86 static const int kTabEdgeRatioInverse
= 4;
88 // Size of the drop indicator.
89 static int drop_indicator_width
;
90 static int drop_indicator_height
;
92 static inline int Round(double x
) {
93 // Why oh why is this not in a standard header?
94 return static_cast<int>(floor(x
+ 0.5));
97 // Max number of stacked tabs.
98 static const int kMaxStackedCount
= 4;
100 // Padding between stacked tabs.
101 static const int kStackedPadding
= 6;
103 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
104 #if !defined(USE_ASH)
105 const int kMouseMoveTimeMS
= 200;
106 const int kMouseMoveCountBeforeConsiderReal
= 3;
109 // Amount of time we delay before resizing after a close from a touch.
110 const int kTouchResizeLayoutTimeMS
= 2000;
112 // Amount the left edge of a tab is offset from the rectangle of the tab's
113 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
114 // Affects the size of the "V" between adjacent tabs.
115 const int kTabHorizontalOffset
= -26;
117 // Amount to adjust the clip by when the tab is stacked before the active index.
118 const int kStackedTabLeftClip
= 20;
120 // Amount to adjust the clip by when the tab is stacked after the active index.
121 const int kStackedTabRightClip
= 20;
123 base::string16
GetClipboardText() {
124 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION
))
125 return base::string16();
126 ui::Clipboard
* clipboard
= ui::Clipboard::GetForCurrentThread();
128 base::string16 clipboard_text
;
129 clipboard
->ReadText(ui::CLIPBOARD_TYPE_SELECTION
, &clipboard_text
);
130 return clipboard_text
;
133 // Animation delegate used for any automatic tab movement. Hides the tab if it
134 // is not fully visible within the tabstrip area, to prevent overflow clipping.
135 class TabAnimationDelegate
: public gfx::AnimationDelegate
{
137 TabAnimationDelegate(TabStrip
* tab_strip
, Tab
* tab
);
138 ~TabAnimationDelegate() override
;
140 void AnimationProgressed(const gfx::Animation
* animation
) override
;
143 TabStrip
* tab_strip() { return tab_strip_
; }
144 Tab
* tab() { return tab_
; }
147 TabStrip
* const tab_strip_
;
150 DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate
);
153 TabAnimationDelegate::TabAnimationDelegate(TabStrip
* tab_strip
, Tab
* tab
)
154 : tab_strip_(tab_strip
),
158 TabAnimationDelegate::~TabAnimationDelegate() {
161 void TabAnimationDelegate::AnimationProgressed(
162 const gfx::Animation
* animation
) {
163 tab_
->SetVisible(tab_strip_
->ShouldTabBeVisible(tab_
));
166 // Animation delegate used when a dragged tab is released. When done sets the
167 // dragging state to false.
168 class ResetDraggingStateDelegate
: public TabAnimationDelegate
{
170 ResetDraggingStateDelegate(TabStrip
* tab_strip
, Tab
* tab
);
171 ~ResetDraggingStateDelegate() override
;
173 void AnimationEnded(const gfx::Animation
* animation
) override
;
174 void AnimationCanceled(const gfx::Animation
* animation
) override
;
177 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate
);
180 ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip
* tab_strip
,
182 : TabAnimationDelegate(tab_strip
, tab
) {
185 ResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
188 void ResetDraggingStateDelegate::AnimationEnded(
189 const gfx::Animation
* animation
) {
190 tab()->set_dragging(false);
191 AnimationProgressed(animation
); // Forces tab visibility to update.
194 void ResetDraggingStateDelegate::AnimationCanceled(
195 const gfx::Animation
* animation
) {
196 AnimationEnded(animation
);
199 // If |dest| contains the point |point_in_source| the event handler from |dest|
200 // is returned. Otherwise NULL is returned.
201 views::View
* ConvertPointToViewAndGetEventHandler(
204 const gfx::Point
& point_in_source
) {
205 gfx::Point
dest_point(point_in_source
);
206 views::View::ConvertPointToTarget(source
, dest
, &dest_point
);
207 return dest
->HitTestPoint(dest_point
) ?
208 dest
->GetEventHandlerForPoint(dest_point
) : NULL
;
211 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
212 // should return NULL if it does not contain the point.
213 views::View
* ConvertPointToViewAndGetTooltipHandler(
216 const gfx::Point
& point_in_source
) {
217 gfx::Point
dest_point(point_in_source
);
218 views::View::ConvertPointToTarget(source
, dest
, &dest_point
);
219 return dest
->GetTooltipHandlerForPoint(dest_point
);
222 TabDragController::EventSource
EventSourceFromEvent(
223 const ui::LocatedEvent
& event
) {
224 return event
.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH
:
225 TabDragController::EVENT_SOURCE_MOUSE
;
230 ///////////////////////////////////////////////////////////////////////////////
233 // A subclass of button that hit-tests to the shape of the new tab button and
234 // does custom drawing.
236 class NewTabButton
: public views::ImageButton
,
237 public views::MaskedTargeterDelegate
{
239 NewTabButton(TabStrip
* tab_strip
, views::ButtonListener
* listener
);
240 ~NewTabButton() override
;
242 // Set the background offset used to match the background image to the frame
244 void set_background_offset(const gfx::Point
& offset
) {
245 background_offset_
= offset
;
251 virtual void OnMouseReleased(const ui::MouseEvent
& event
) override
;
253 void OnPaint(gfx::Canvas
* canvas
) override
;
256 void OnGestureEvent(ui::GestureEvent
* event
) override
;
259 // views::MaskedTargeterDelegate:
260 bool GetHitTestMask(gfx::Path
* mask
) const override
;
262 bool ShouldWindowContentsBeTransparent() const;
263 gfx::ImageSkia
GetBackgroundImage(views::CustomButton::ButtonState state
,
265 gfx::ImageSkia
GetImageForState(views::CustomButton::ButtonState state
,
267 gfx::ImageSkia
GetImageForScale(float scale
) const;
269 // Tab strip that contains this button.
270 TabStrip
* tab_strip_
;
272 // The offset used to paint the background image.
273 gfx::Point background_offset_
;
275 // were we destroyed?
278 DISALLOW_COPY_AND_ASSIGN(NewTabButton
);
281 NewTabButton::NewTabButton(TabStrip
* tab_strip
, views::ButtonListener
* listener
)
282 : views::ImageButton(listener
),
283 tab_strip_(tab_strip
),
285 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
286 set_triggerable_event_flags(triggerable_event_flags() |
287 ui::EF_MIDDLE_MOUSE_BUTTON
);
291 NewTabButton::~NewTabButton() {
297 void NewTabButton::OnMouseReleased(const ui::MouseEvent
& event
) {
298 if (event
.IsOnlyRightMouseButton()) {
299 gfx::Point point
= event
.location();
300 views::View::ConvertPointToScreen(this, &point
);
301 point
= gfx::win::DIPToScreenPoint(point
);
302 bool destroyed
= false;
303 destroyed_
= &destroyed
;
304 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point
);
309 SetState(views::CustomButton::STATE_NORMAL
);
312 views::ImageButton::OnMouseReleased(event
);
316 void NewTabButton::OnPaint(gfx::Canvas
* canvas
) {
317 gfx::ImageSkia image
= GetImageForScale(canvas
->image_scale());
318 canvas
->DrawImageInt(image
, 0, height() - image
.height());
321 void NewTabButton::OnGestureEvent(ui::GestureEvent
* event
) {
322 // Consume all gesture events here so that the parent (Tab) does not
323 // start consuming gestures.
324 views::ImageButton::OnGestureEvent(event
);
328 bool NewTabButton::GetHitTestMask(gfx::Path
* mask
) const {
331 // When the button is sized to the top of the tab strip, we want the hit
332 // test mask to be defined as the complete (rectangular) bounds of the
334 if (tab_strip_
->SizeTabButtonToTopOfTabStrip()) {
335 gfx::Rect
button_bounds(GetContentsBounds());
336 button_bounds
.set_x(GetMirroredXForRect(button_bounds
));
337 mask
->addRect(RectToSkRect(button_bounds
));
341 SkScalar w
= SkIntToScalar(width());
342 SkScalar v_offset
= SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset
);
344 // These values are defined by the shape of the new tab image. Should that
345 // image ever change, these values will need to be updated. They're so
346 // custom it's not really worth defining constants for.
347 // These values are correct for regular and USE_ASH versions of the image.
348 mask
->moveTo(0, v_offset
+ 1);
349 mask
->lineTo(w
- 7, v_offset
+ 1);
350 mask
->lineTo(w
- 4, v_offset
+ 4);
351 mask
->lineTo(w
, v_offset
+ 16);
352 mask
->lineTo(w
- 1, v_offset
+ 17);
353 mask
->lineTo(7, v_offset
+ 17);
354 mask
->lineTo(4, v_offset
+ 13);
355 mask
->lineTo(0, v_offset
+ 1);
361 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
362 return GetWidget() &&
363 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
366 gfx::ImageSkia
NewTabButton::GetBackgroundImage(
367 views::CustomButton::ButtonState state
,
369 int background_id
= 0;
370 if (ShouldWindowContentsBeTransparent()) {
371 background_id
= IDR_THEME_TAB_BACKGROUND_V
;
372 } else if (tab_strip_
->controller()->IsIncognito()) {
373 background_id
= IDR_THEME_TAB_BACKGROUND_INCOGNITO
;
375 background_id
= IDR_THEME_TAB_BACKGROUND
;
380 case views::CustomButton::STATE_NORMAL
:
381 case views::CustomButton::STATE_HOVERED
:
382 alpha
= ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
385 case views::CustomButton::STATE_PRESSED
:
393 gfx::ImageSkia
* mask
=
394 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK
);
395 int height
= mask
->height();
396 int width
= mask
->width();
397 // The canvas and mask has to use the same scale factor.
398 if (!mask
->HasRepresentation(scale
))
399 scale
= ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P
);
401 gfx::Canvas
canvas(gfx::Size(width
, height
), scale
, false);
403 // For custom images the background starts at the top of the tab strip.
404 // Otherwise the background starts at the top of the frame.
405 gfx::ImageSkia
* background
=
406 GetThemeProvider()->GetImageSkiaNamed(background_id
);
407 int offset_y
= GetThemeProvider()->HasCustomImage(background_id
) ?
408 0 : background_offset_
.y();
410 // The new tab background is mirrored in RTL mode, but the theme background
411 // should never be mirrored. Mirror it here to compensate.
412 float x_scale
= 1.0f
;
413 int x
= GetMirroredX() + background_offset_
.x();
414 if (base::i18n::IsRTL()) {
416 // Offset by |width| such that the same region is painted as if there was no
420 canvas
.TileImageInt(*background
, x
,
421 TabStrip::kNewTabButtonVerticalOffset
+ offset_y
,
422 x_scale
, 1.0f
, 0, 0, width
, height
);
426 paint
.setColor(SkColorSetARGB(alpha
, 255, 255, 255));
427 paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
428 paint
.setStyle(SkPaint::kFill_Style
);
429 canvas
.DrawRect(gfx::Rect(0, 0, width
, height
), paint
);
432 // White highlight on hover.
433 if (state
== views::CustomButton::STATE_HOVERED
)
434 canvas
.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
436 return gfx::ImageSkiaOperations::CreateMaskedImage(
437 gfx::ImageSkia(canvas
.ExtractImageRep()), *mask
);
440 gfx::ImageSkia
NewTabButton::GetImageForState(
441 views::CustomButton::ButtonState state
,
443 const int overlay_id
= state
== views::CustomButton::STATE_PRESSED
?
444 IDR_NEWTAB_BUTTON_P
: IDR_NEWTAB_BUTTON
;
445 gfx::ImageSkia
* overlay
= GetThemeProvider()->GetImageSkiaNamed(overlay_id
);
448 gfx::Size(overlay
->width(), overlay
->height()),
451 canvas
.DrawImageInt(GetBackgroundImage(state
, scale
), 0, 0);
453 // Draw the button border with a slight alpha.
454 const int kGlassFrameOverlayAlpha
= 178;
455 const int kOpaqueFrameOverlayAlpha
= 230;
456 uint8 alpha
= ShouldWindowContentsBeTransparent() ?
457 kGlassFrameOverlayAlpha
: kOpaqueFrameOverlayAlpha
;
458 canvas
.DrawImageInt(*overlay
, 0, 0, alpha
);
460 return gfx::ImageSkia(canvas
.ExtractImageRep());
463 gfx::ImageSkia
NewTabButton::GetImageForScale(float scale
) const {
464 if (!hover_animation_
->is_animating())
465 return GetImageForState(state(), scale
);
466 return gfx::ImageSkiaOperations::CreateBlendedImage(
467 GetImageForState(views::CustomButton::STATE_NORMAL
, scale
),
468 GetImageForState(views::CustomButton::STATE_HOVERED
, scale
),
469 hover_animation_
->GetCurrentValue());
472 ///////////////////////////////////////////////////////////////////////////////
473 // TabStrip::RemoveTabDelegate
475 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
477 class TabStrip::RemoveTabDelegate
: public TabAnimationDelegate
{
479 RemoveTabDelegate(TabStrip
* tab_strip
, Tab
* tab
);
481 void AnimationEnded(const gfx::Animation
* animation
) override
;
482 void AnimationCanceled(const gfx::Animation
* animation
) override
;
485 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate
);
488 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip
* tab_strip
,
490 : TabAnimationDelegate(tab_strip
, tab
) {
493 void TabStrip::RemoveTabDelegate::AnimationEnded(
494 const gfx::Animation
* animation
) {
495 DCHECK(tab()->closing());
496 tab_strip()->RemoveAndDeleteTab(tab());
498 // Send the Container a message to simulate a mouse moved event at the current
499 // mouse position. This tickles the Tab the mouse is currently over to show
500 // the "hot" state of the close button. Note that this is not required (and
501 // indeed may crash!) for removes spawned by non-mouse closes and
503 if (!tab_strip()->IsDragSessionActive() &&
504 tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
505 // The widget can apparently be null during shutdown.
506 views::Widget
* widget
= tab_strip()->GetWidget();
508 widget
->SynthesizeMouseMoveEvent();
512 void TabStrip::RemoveTabDelegate::AnimationCanceled(
513 const gfx::Animation
* animation
) {
514 AnimationEnded(animation
);
517 ///////////////////////////////////////////////////////////////////////////////
521 const char TabStrip::kViewClassName
[] = "TabStrip";
522 const int TabStrip::kNewTabButtonHorizontalOffset
= -11;
523 const int TabStrip::kNewTabButtonVerticalOffset
= 7;
524 const int TabStrip::kMiniToNonMiniGap
= 3;
525 const int TabStrip::kNewTabButtonAssetWidth
= 34;
526 const int TabStrip::kNewTabButtonAssetHeight
= 18;
528 TabStrip::TabStrip(TabStripController
* controller
)
529 : controller_(controller
),
530 newtab_button_(NULL
),
531 current_unselected_width_(Tab::GetStandardSize().width()),
532 current_selected_width_(Tab::GetStandardSize().width()),
533 available_width_for_tabs_(-1),
534 in_tab_close_(false),
535 animation_container_(new gfx::AnimationContainer()),
536 bounds_animator_(this),
537 stacked_layout_(false),
538 adjust_layout_(false),
539 reset_to_shrink_on_exit_(false),
540 mouse_move_count_(0),
541 immersive_style_(false) {
544 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
547 TabStrip::~TabStrip() {
548 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
549 TabStripDeleted(this));
551 // The animations may reference the tabs. Shut down the animation before we
553 StopAnimating(false);
555 DestroyDragController();
557 // Make sure we unhook ourselves as a message loop observer so that we don't
558 // crash in the case where the user closes the window after closing a tab
559 // but before moving the mouse.
560 RemoveMessageLoopObserver();
562 // The children (tabs) may callback to us from their destructor. Delete them
563 // so that if they call back we aren't in a weird state.
564 RemoveAllChildViews(true);
567 void TabStrip::AddObserver(TabStripObserver
* observer
) {
568 observers_
.AddObserver(observer
);
571 void TabStrip::RemoveObserver(TabStripObserver
* observer
) {
572 observers_
.RemoveObserver(observer
);
575 void TabStrip::SetStackedLayout(bool stacked_layout
) {
576 if (stacked_layout
== stacked_layout_
)
579 const int active_index
= controller_
->GetActiveIndex();
580 int active_center
= 0;
581 if (active_index
!= -1) {
582 active_center
= ideal_bounds(active_index
).x() +
583 ideal_bounds(active_index
).width() / 2;
585 stacked_layout_
= stacked_layout
;
586 SetResetToShrinkOnExit(false);
587 SwapLayoutIfNecessary();
588 // When transitioning to stacked try to keep the active tab centered.
589 if (touch_layout_
&& active_index
!= -1) {
590 touch_layout_
->SetActiveTabLocation(
591 active_center
- ideal_bounds(active_index
).width() / 2);
592 AnimateToIdealBounds();
596 gfx::Rect
TabStrip::GetNewTabButtonBounds() {
597 return newtab_button_
->bounds();
600 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
601 // Extend the button to the screen edge in maximized and immersive fullscreen.
602 views::Widget
* widget
= GetWidget();
603 return browser_defaults::kSizeTabButtonToTopOfTabStrip
||
604 (widget
&& (widget
->IsMaximized() || widget
->IsFullscreen()));
607 void TabStrip::StartHighlight(int model_index
) {
608 tab_at(model_index
)->StartPulse();
611 void TabStrip::StopAllHighlighting() {
612 for (int i
= 0; i
< tab_count(); ++i
)
613 tab_at(i
)->StopPulse();
616 void TabStrip::AddTabAt(int model_index
,
617 const TabRendererData
& data
,
619 // Stop dragging when a new tab is added and dragging a window. Doing
620 // otherwise results in a confusing state if the user attempts to reattach. We
621 // could allow this and make TabDragController update itself during the add,
622 // but this comes up infrequently enough that it's not work the complexity.
623 if (drag_controller_
.get() && !drag_controller_
->is_mutating() &&
624 drag_controller_
->is_dragging_window()) {
625 EndDrag(END_DRAG_COMPLETE
);
627 Tab
* tab
= CreateTab();
629 UpdateTabsClosingMap(model_index
, 1);
630 tabs_
.Add(tab
, model_index
);
634 GenerateIdealBoundsForMiniTabs(NULL
);
637 add_types
|= StackedTabStripLayout::kAddTypeMini
;
639 add_types
|= StackedTabStripLayout::kAddTypeActive
;
640 touch_layout_
->AddTab(model_index
, add_types
, GetStartXForNormalTabs());
643 // Don't animate the first tab, it looks weird, and don't animate anything
644 // if the containing window isn't visible yet.
645 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
646 StartInsertTabAnimation(model_index
);
650 SwapLayoutIfNecessary();
652 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
653 TabStripAddedTabAt(this, model_index
));
656 void TabStrip::MoveTab(int from_model_index
,
658 const TabRendererData
& data
) {
659 DCHECK_GT(tabs_
.view_size(), 0);
660 const Tab
* last_tab
= GetLastVisibleTab();
661 tab_at(from_model_index
)->SetData(data
);
663 tabs_
.MoveViewOnly(from_model_index
, to_model_index
);
665 GenerateIdealBoundsForMiniTabs(&mini_count
);
666 touch_layout_
->MoveTab(
667 from_model_index
, to_model_index
, controller_
->GetActiveIndex(),
668 GetStartXForNormalTabs(), mini_count
);
670 tabs_
.Move(from_model_index
, to_model_index
);
672 StartMoveTabAnimation();
673 if (TabDragController::IsAttachedTo(this) &&
674 (last_tab
!= GetLastVisibleTab() || last_tab
->dragging())) {
675 newtab_button_
->SetVisible(false);
677 SwapLayoutIfNecessary();
679 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
680 TabStripMovedTab(this, from_model_index
, to_model_index
));
683 void TabStrip::RemoveTabAt(int model_index
) {
685 Tab
* tab
= tab_at(model_index
);
686 tab
->set_closing(true);
687 int old_x
= tabs_
.ideal_bounds(model_index
).x();
688 // We still need to paint the tab until we actually remove it. Put it in
689 // tabs_closing_map_ so we can find it.
690 RemoveTabFromViewModel(model_index
);
691 touch_layout_
->RemoveTab(model_index
, GenerateIdealBoundsForMiniTabs(NULL
),
693 ScheduleRemoveTabAnimation(tab
);
694 } else if (in_tab_close_
&& model_index
!= GetModelCount()) {
695 StartMouseInitiatedRemoveTabAnimation(model_index
);
697 StartRemoveTabAnimation(model_index
);
699 SwapLayoutIfNecessary();
701 FOR_EACH_OBSERVER(TabStripObserver
, observers_
,
702 TabStripRemovedTabAt(this, model_index
));
705 void TabStrip::SetTabData(int model_index
, const TabRendererData
& data
) {
706 Tab
* tab
= tab_at(model_index
);
707 bool mini_state_changed
= tab
->data().mini
!= data
.mini
;
710 if (mini_state_changed
) {
712 int mini_tab_count
= 0;
713 int start_x
= GenerateIdealBoundsForMiniTabs(&mini_tab_count
);
714 touch_layout_
->SetXAndMiniCount(start_x
, mini_tab_count
);
716 if (GetWidget() && GetWidget()->IsVisible())
717 StartMiniTabAnimation();
721 SwapLayoutIfNecessary();
724 bool TabStrip::ShouldTabBeVisible(const Tab
* tab
) const {
725 // Detached tabs should always be invisible (as they close).
729 // When stacking tabs, all tabs should always be visible.
733 // If the tab is currently clipped, it shouldn't be visible. Note that we
734 // allow dragged tabs to draw over the "New Tab button" region as well,
735 // because either the New Tab button will be hidden, or the dragged tabs will
736 // be animating back to their normal positions and we don't want to hide them
737 // in the New Tab button region in case they re-appear after leaving it.
738 // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab
739 // button area, even when the button is invisible, so that they don't appear
740 // to "pop in" when the button disappears.
741 // TODO: Probably doesn't work for RTL
742 int right_edge
= tab
->bounds().right();
743 const int visible_width
= tab
->dragging() ? width() : tab_area_width();
744 if (right_edge
> visible_width
)
747 // Non-clipped dragging tabs should always be visible.
751 // Let all non-clipped closing tabs be visible. These will probably finish
752 // closing before the user changes the active tab, so there's little reason to
753 // try and make the more complex logic below apply.
757 // Now we need to check whether the tab isn't currently clipped, but could
758 // become clipped if we changed the active tab, widening either this tab or
759 // the tabstrip portion before it.
761 // Mini tabs don't change size when activated, so any tab in the mini tab
763 if (tab
->data().mini
)
766 // If the active tab is on or before this tab, we're safe.
767 if (controller_
->GetActiveIndex() <= GetModelIndexOfTab(tab
))
770 // We need to check what would happen if the active tab were to move to this
772 return (right_edge
+ current_selected_width_
- current_unselected_width_
) <=
776 void TabStrip::PrepareForCloseAt(int model_index
, CloseTabSource source
) {
777 if (!in_tab_close_
&& IsAnimating()) {
778 // Cancel any current animations. We do this as remove uses the current
779 // ideal bounds and we need to know ideal bounds is in a good state.
786 int model_count
= GetModelCount();
787 if (model_count
> 1 && model_index
!= model_count
- 1) {
788 // The user is about to close a tab other than the last tab. Set
789 // available_width_for_tabs_ so that if we do a layout we don't position a
790 // tab past the end of the second to last tab. We do this so that as the
791 // user closes tabs with the mouse a tab continues to fall under the mouse.
792 Tab
* last_tab
= tab_at(model_count
- 1);
793 Tab
* tab_being_removed
= tab_at(model_index
);
794 available_width_for_tabs_
= last_tab
->x() + last_tab
->width() -
795 tab_being_removed
->width() - kTabHorizontalOffset
;
796 if (model_index
== 0 && tab_being_removed
->data().mini
&&
797 !tab_at(1)->data().mini
) {
798 available_width_for_tabs_
-= kMiniToNonMiniGap
;
802 in_tab_close_
= true;
803 resize_layout_timer_
.Stop();
804 if (source
== CLOSE_TAB_FROM_TOUCH
) {
805 StartResizeLayoutTabsFromTouchTimer();
807 AddMessageLoopObserver();
811 void TabStrip::SetSelection(const ui::ListSelectionModel
& old_selection
,
812 const ui::ListSelectionModel
& new_selection
) {
814 touch_layout_
->SetActiveIndex(new_selection
.active());
815 // Only start an animation if we need to. Otherwise clicking on an
816 // unselected tab and dragging won't work because dragging is only allowed
818 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_
))
819 AnimateToIdealBounds();
822 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
823 // a different size to the selected ones.
824 bool tiny_tabs
= current_unselected_width_
!= current_selected_width_
;
825 if (!IsAnimating() && (!in_tab_close_
|| tiny_tabs
)) {
832 // Use STLSetDifference to get the indices of elements newly selected
833 // and no longer selected, since selected_indices() is always sorted.
834 ui::ListSelectionModel::SelectedIndices no_longer_selected
=
835 base::STLSetDifference
<ui::ListSelectionModel::SelectedIndices
>(
836 old_selection
.selected_indices(),
837 new_selection
.selected_indices());
838 ui::ListSelectionModel::SelectedIndices newly_selected
=
839 base::STLSetDifference
<ui::ListSelectionModel::SelectedIndices
>(
840 new_selection
.selected_indices(),
841 old_selection
.selected_indices());
843 // Fire accessibility events that reflect the changes to selection, and
844 // stop the mini tab title animation on tabs no longer selected.
845 for (size_t i
= 0; i
< no_longer_selected
.size(); ++i
) {
846 tab_at(no_longer_selected
[i
])->StopMiniTabTitleAnimation();
847 tab_at(no_longer_selected
[i
])->NotifyAccessibilityEvent(
848 ui::AX_EVENT_SELECTION_REMOVE
, true);
850 for (size_t i
= 0; i
< newly_selected
.size(); ++i
) {
851 tab_at(newly_selected
[i
])->NotifyAccessibilityEvent(
852 ui::AX_EVENT_SELECTION_ADD
, true);
854 tab_at(new_selection
.active())->NotifyAccessibilityEvent(
855 ui::AX_EVENT_SELECTION
, true);
858 void TabStrip::TabTitleChangedNotLoading(int model_index
) {
859 Tab
* tab
= tab_at(model_index
);
860 if (tab
->data().mini
&& !tab
->IsActive())
861 tab
->StartMiniTabTitleAnimation();
864 int TabStrip::GetModelIndexOfTab(const Tab
* tab
) const {
865 return tabs_
.GetIndexOfView(tab
);
868 int TabStrip::GetModelCount() const {
869 return controller_
->GetCount();
872 bool TabStrip::IsValidModelIndex(int model_index
) const {
873 return controller_
->IsValidIndex(model_index
);
876 bool TabStrip::IsDragSessionActive() const {
877 return drag_controller_
.get() != NULL
;
880 bool TabStrip::IsActiveDropTarget() const {
881 for (int i
= 0; i
< tab_count(); ++i
) {
882 Tab
* tab
= tab_at(i
);
889 bool TabStrip::IsTabStripEditable() const {
890 return !IsDragSessionActive() && !IsActiveDropTarget();
893 bool TabStrip::IsTabStripCloseable() const {
894 return !IsDragSessionActive();
897 void TabStrip::UpdateLoadingAnimations() {
898 controller_
->UpdateLoadingAnimations();
901 bool TabStrip::IsPositionInWindowCaption(const gfx::Point
& point
) {
902 return IsRectInWindowCaption(gfx::Rect(point
, gfx::Size(1, 1)));
905 bool TabStrip::IsRectInWindowCaption(const gfx::Rect
& rect
) {
906 views::View
* v
= GetEventHandlerForRect(rect
);
908 // If there is no control at this location, claim the hit was in the title
909 // bar to get a move action.
913 // Check to see if the rect intersects the non-button parts of the new tab
914 // button. The button has a non-rectangular shape, so if it's not in the
915 // visual portions of the button we treat it as a click to the caption.
916 gfx::RectF
rect_in_newtab_coords_f(rect
);
917 View::ConvertRectToTarget(this, newtab_button_
, &rect_in_newtab_coords_f
);
918 gfx::Rect rect_in_newtab_coords
= gfx::ToEnclosingRect(
919 rect_in_newtab_coords_f
);
920 if (newtab_button_
->GetLocalBounds().Intersects(rect_in_newtab_coords
) &&
921 !newtab_button_
->HitTestRect(rect_in_newtab_coords
))
924 // All other regions, including the new Tab button, should be considered part
925 // of the containing Window's client area so that regular events can be
926 // processed for them.
930 void TabStrip::SetBackgroundOffset(const gfx::Point
& offset
) {
931 for (int i
= 0; i
< tab_count(); ++i
)
932 tab_at(i
)->set_background_offset(offset
);
933 newtab_button_
->set_background_offset(offset
);
936 void TabStrip::SetImmersiveStyle(bool enable
) {
937 if (immersive_style_
== enable
)
939 immersive_style_
= enable
;
942 bool TabStrip::IsAnimating() const {
943 return bounds_animator_
.IsAnimating();
946 void TabStrip::StopAnimating(bool layout
) {
950 bounds_animator_
.Cancel();
956 void TabStrip::FileSupported(const GURL
& url
, bool supported
) {
957 if (drop_info_
.get() && drop_info_
->url
== url
)
958 drop_info_
->file_supported
= supported
;
961 const ui::ListSelectionModel
& TabStrip::GetSelectionModel() {
962 return controller_
->GetSelectionModel();
965 bool TabStrip::SupportsMultipleSelection() {
966 // TODO: currently only allow single selection in touch layout mode.
967 return touch_layout_
== NULL
;
970 void TabStrip::SelectTab(Tab
* tab
) {
971 int model_index
= GetModelIndexOfTab(tab
);
972 if (IsValidModelIndex(model_index
))
973 controller_
->SelectTab(model_index
);
976 void TabStrip::ExtendSelectionTo(Tab
* tab
) {
977 int model_index
= GetModelIndexOfTab(tab
);
978 if (IsValidModelIndex(model_index
))
979 controller_
->ExtendSelectionTo(model_index
);
982 void TabStrip::ToggleSelected(Tab
* tab
) {
983 int model_index
= GetModelIndexOfTab(tab
);
984 if (IsValidModelIndex(model_index
))
985 controller_
->ToggleSelected(model_index
);
988 void TabStrip::AddSelectionFromAnchorTo(Tab
* tab
) {
989 int model_index
= GetModelIndexOfTab(tab
);
990 if (IsValidModelIndex(model_index
))
991 controller_
->AddSelectionFromAnchorTo(model_index
);
994 void TabStrip::CloseTab(Tab
* tab
, CloseTabSource source
) {
995 if (tab
->closing()) {
996 // If the tab is already closing, close the next tab. We do this so that the
997 // user can rapidly close tabs by clicking the close button and not have
998 // the animations interfere with that.
999 const int closed_tab_index
= FindClosingTab(tab
).first
->first
;
1000 if (closed_tab_index
< GetModelCount())
1001 controller_
->CloseTab(closed_tab_index
, source
);
1004 int model_index
= GetModelIndexOfTab(tab
);
1005 if (IsValidModelIndex(model_index
))
1006 controller_
->CloseTab(model_index
, source
);
1009 void TabStrip::ToggleTabAudioMute(Tab
* tab
) {
1010 int model_index
= GetModelIndexOfTab(tab
);
1011 if (IsValidModelIndex(model_index
))
1012 controller_
->ToggleTabAudioMute(model_index
);
1015 void TabStrip::ShowContextMenuForTab(Tab
* tab
,
1016 const gfx::Point
& p
,
1017 ui::MenuSourceType source_type
) {
1018 controller_
->ShowContextMenuForTab(tab
, p
, source_type
);
1021 bool TabStrip::IsActiveTab(const Tab
* tab
) const {
1022 int model_index
= GetModelIndexOfTab(tab
);
1023 return IsValidModelIndex(model_index
) &&
1024 controller_
->IsActiveTab(model_index
);
1027 bool TabStrip::IsTabSelected(const Tab
* tab
) const {
1028 int model_index
= GetModelIndexOfTab(tab
);
1029 return IsValidModelIndex(model_index
) &&
1030 controller_
->IsTabSelected(model_index
);
1033 bool TabStrip::IsTabPinned(const Tab
* tab
) const {
1037 int model_index
= GetModelIndexOfTab(tab
);
1038 return IsValidModelIndex(model_index
) &&
1039 controller_
->IsTabPinned(model_index
);
1042 void TabStrip::MaybeStartDrag(
1044 const ui::LocatedEvent
& event
,
1045 const ui::ListSelectionModel
& original_selection
) {
1046 // Don't accidentally start any drag operations during animations if the
1047 // mouse is down... during an animation tabs are being resized automatically,
1048 // so the View system can misinterpret this easily if the mouse is down that
1049 // the user is dragging.
1050 if (IsAnimating() || tab
->closing() ||
1051 controller_
->HasAvailableDragActions() == 0) {
1055 // Do not do any dragging of tabs when using the super short immersive style.
1056 if (IsImmersiveStyle())
1059 int model_index
= GetModelIndexOfTab(tab
);
1060 if (!IsValidModelIndex(model_index
)) {
1065 int size_to_selected
= 0;
1066 int x
= tab
->GetMirroredXInView(event
.x());
1068 // Build the set of selected tabs to drag and calculate the offset from the
1069 // first selected tab.
1070 for (int i
= 0; i
< tab_count(); ++i
) {
1071 Tab
* other_tab
= tab_at(i
);
1072 if (IsTabSelected(other_tab
)) {
1073 tabs
.push_back(other_tab
);
1074 if (other_tab
== tab
) {
1075 size_to_selected
= GetSizeNeededForTabs(tabs
);
1076 x
= size_to_selected
- tab
->width() + x
;
1080 DCHECK(!tabs
.empty());
1081 DCHECK(std::find(tabs
.begin(), tabs
.end(), tab
) != tabs
.end());
1082 ui::ListSelectionModel selection_model
;
1083 if (!original_selection
.IsSelected(model_index
))
1084 selection_model
.Copy(original_selection
);
1085 // Delete the existing DragController before creating a new one. We do this as
1086 // creating the DragController remembers the WebContents delegates and we need
1087 // to make sure the existing DragController isn't still a delegate.
1088 drag_controller_
.reset();
1089 TabDragController::MoveBehavior move_behavior
=
1090 TabDragController::REORDER
;
1091 // Use MOVE_VISIBILE_TABS in the following conditions:
1092 // . Mouse event generated from touch and the left button is down (the right
1093 // button corresponds to a long press, which we want to reorder).
1094 // . Gesture tap down and control key isn't down.
1095 // . Real mouse event and control is down. This is mostly for testing.
1096 DCHECK(event
.type() == ui::ET_MOUSE_PRESSED
||
1097 event
.type() == ui::ET_GESTURE_TAP_DOWN
);
1098 if (touch_layout_
&&
1099 ((event
.type() == ui::ET_MOUSE_PRESSED
&&
1100 (((event
.flags() & ui::EF_FROM_TOUCH
) &&
1101 static_cast<const ui::MouseEvent
&>(event
).IsLeftMouseButton()) ||
1102 (!(event
.flags() & ui::EF_FROM_TOUCH
) &&
1103 static_cast<const ui::MouseEvent
&>(event
).IsControlDown()))) ||
1104 (event
.type() == ui::ET_GESTURE_TAP_DOWN
&& !event
.IsControlDown()))) {
1105 move_behavior
= TabDragController::MOVE_VISIBILE_TABS
;
1108 drag_controller_
.reset(new TabDragController
);
1109 drag_controller_
->Init(
1110 this, tab
, tabs
, gfx::Point(x
, y
), event
.x(), selection_model
,
1111 move_behavior
, EventSourceFromEvent(event
));
1114 void TabStrip::ContinueDrag(views::View
* view
, const ui::LocatedEvent
& event
) {
1115 if (drag_controller_
.get() &&
1116 drag_controller_
->event_source() == EventSourceFromEvent(event
)) {
1117 gfx::Point
screen_location(event
.location());
1118 views::View::ConvertPointToScreen(view
, &screen_location
);
1119 drag_controller_
->Drag(screen_location
);
1123 bool TabStrip::EndDrag(EndDragReason reason
) {
1124 if (!drag_controller_
.get())
1126 bool started_drag
= drag_controller_
->started_drag();
1127 drag_controller_
->EndDrag(reason
);
1128 return started_drag
;
1131 Tab
* TabStrip::GetTabAt(Tab
* tab
, const gfx::Point
& tab_in_tab_coordinates
) {
1132 gfx::Point local_point
= tab_in_tab_coordinates
;
1133 ConvertPointToTarget(tab
, this, &local_point
);
1135 views::View
* view
= GetEventHandlerForPoint(local_point
);
1137 return NULL
; // No tab contains the point.
1139 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1140 while (view
&& view
!= this && view
->id() != VIEW_ID_TAB
)
1141 view
= view
->parent();
1143 return view
&& view
->id() == VIEW_ID_TAB
? static_cast<Tab
*>(view
) : NULL
;
1146 void TabStrip::OnMouseEventInTab(views::View
* source
,
1147 const ui::MouseEvent
& event
) {
1148 UpdateStackedLayoutFromMouseEvent(source
, event
);
1151 bool TabStrip::ShouldPaintTab(const Tab
* tab
, gfx::Rect
* clip
) {
1152 // Only touch layout needs to restrict the clip.
1153 if (!touch_layout_
&& !IsStackingDraggedTabs())
1156 int index
= GetModelIndexOfTab(tab
);
1158 return true; // Tab is closing, paint it all.
1160 int active_index
= IsStackingDraggedTabs() ?
1161 controller_
->GetActiveIndex() : touch_layout_
->active_index();
1162 if (active_index
== tab_count())
1165 if (index
< active_index
) {
1166 if (tab_at(index
)->x() == tab_at(index
+ 1)->x())
1169 if (tab_at(index
)->x() > tab_at(index
+ 1)->x())
1170 return true; // Can happen during dragging.
1173 0, 0, tab_at(index
+ 1)->x() - tab_at(index
)->x() + kStackedTabLeftClip
,
1174 tab_at(index
)->height());
1175 } else if (index
> active_index
&& index
> 0) {
1176 const gfx::Rect
& tab_bounds(tab_at(index
)->bounds());
1177 const gfx::Rect
& previous_tab_bounds(tab_at(index
- 1)->bounds());
1178 if (tab_bounds
.x() == previous_tab_bounds
.x())
1181 if (tab_bounds
.x() < previous_tab_bounds
.x())
1182 return true; // Can happen during dragging.
1184 if (previous_tab_bounds
.right() + kTabHorizontalOffset
!= tab_bounds
.x()) {
1185 int x
= previous_tab_bounds
.right() - tab_bounds
.x() -
1186 kStackedTabRightClip
;
1187 clip
->SetRect(x
, 0, tab_bounds
.width() - x
, tab_bounds
.height());
1193 bool TabStrip::IsImmersiveStyle() const {
1194 return immersive_style_
;
1197 void TabStrip::UpdateTabAccessibilityState(const Tab
* tab
,
1198 ui::AXViewState
* state
) {
1199 state
->count
= tab_count();
1200 state
->index
= GetModelIndexOfTab(tab
);
1203 void TabStrip::MouseMovedOutOfHost() {
1205 if (reset_to_shrink_on_exit_
) {
1206 reset_to_shrink_on_exit_
= false;
1207 SetStackedLayout(false);
1208 controller_
->StackedLayoutMaybeChanged();
1212 ///////////////////////////////////////////////////////////////////////////////
1213 // TabStrip, views::View overrides:
1215 void TabStrip::Layout() {
1216 // Only do a layout if our size changed.
1217 if (last_layout_size_
== size())
1219 if (IsDragSessionActive())
1224 void TabStrip::PaintChildren(gfx::Canvas
* canvas
,
1225 const views::CullSet
& cull_set
) {
1226 // The view order doesn't match the paint order (tabs_ contains the tab
1227 // ordering). Additionally we need to paint the tabs that are closing in
1228 // |tabs_closing_map_|.
1229 Tab
* active_tab
= NULL
;
1232 int selected_tab_count
= 0;
1233 bool is_dragging
= false;
1234 int active_tab_index
= -1;
1236 const chrome::HostDesktopType host_desktop_type
=
1237 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1238 const int inactive_tab_alpha
=
1239 (host_desktop_type
== chrome::HOST_DESKTOP_TYPE_ASH
) ?
1240 kInactiveTabAndNewTabButtonAlphaAsh
: kInactiveTabAndNewTabButtonAlpha
;
1242 if (inactive_tab_alpha
< 255)
1243 canvas
->SaveLayerAlpha(inactive_tab_alpha
);
1245 PaintClosingTabs(canvas
, tab_count(), cull_set
);
1247 for (int i
= tab_count() - 1; i
>= 0; --i
) {
1248 Tab
* tab
= tab_at(i
);
1249 if (tab
->IsSelected())
1250 selected_tab_count
++;
1251 if (tab
->dragging() && !stacked_layout_
) {
1253 if (tab
->IsActive()) {
1255 active_tab_index
= i
;
1257 tabs_dragging
.push_back(tab
);
1259 } else if (!tab
->IsActive()) {
1260 if (!tab
->IsSelected()) {
1261 if (!stacked_layout_
)
1262 tab
->Paint(canvas
, cull_set
);
1264 selected_tabs
.push_back(tab
);
1268 active_tab_index
= i
;
1270 PaintClosingTabs(canvas
, i
, cull_set
);
1273 // Draw from the left and then the right if we're in touch mode.
1274 if (stacked_layout_
&& active_tab_index
>= 0) {
1275 for (int i
= 0; i
< active_tab_index
; ++i
) {
1276 Tab
* tab
= tab_at(i
);
1277 tab
->Paint(canvas
, cull_set
);
1280 for (int i
= tab_count() - 1; i
> active_tab_index
; --i
) {
1281 Tab
* tab
= tab_at(i
);
1282 tab
->Paint(canvas
, cull_set
);
1285 if (inactive_tab_alpha
< 255)
1288 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1289 // Make sure non-active tabs are somewhat transparent.
1291 // If there are multiple tabs selected, fade non-selected tabs more to make
1292 // the selected tabs more noticable.
1293 int alpha
= selected_tab_count
> 1 ?
1294 kGlassFrameInactiveTabAlphaMultiSelection
:
1295 kGlassFrameInactiveTabAlpha
;
1296 paint
.setColor(SkColorSetARGB(alpha
, 255, 255, 255));
1297 paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
1298 paint
.setStyle(SkPaint::kFill_Style
);
1300 // The tab graphics include some shadows at the top, so the actual
1301 // tabstrip top is 4 px. above the apparent top of the tab, to provide room
1302 // to draw these. Exclude this region when trying to make tabs transparent
1303 // as it's transparent enough already, and drawing in this region can
1304 // overlap the avatar button, leading to visual artifacts.
1305 const int kTopOffset
= 4;
1306 // The tabstrip area overlaps the toolbar area by 2 px.
1308 gfx::Rect(0, kTopOffset
, width(), height() - kTopOffset
- 2), paint
);
1311 // Now selected but not active. We don't want these dimmed if using native
1312 // frame, so they're painted after initial pass.
1313 for (size_t i
= 0; i
< selected_tabs
.size(); ++i
)
1314 selected_tabs
[i
]->Paint(canvas
, cull_set
);
1316 // Next comes the active tab.
1317 if (active_tab
&& !is_dragging
)
1318 active_tab
->Paint(canvas
, cull_set
);
1320 // Paint the New Tab button.
1321 if (inactive_tab_alpha
< 255)
1322 canvas
->SaveLayerAlpha(inactive_tab_alpha
);
1323 newtab_button_
->Paint(canvas
, cull_set
);
1324 if (inactive_tab_alpha
< 255)
1327 // And the dragged tabs.
1328 for (size_t i
= 0; i
< tabs_dragging
.size(); ++i
)
1329 tabs_dragging
[i
]->Paint(canvas
, cull_set
);
1331 // If the active tab is being dragged, it goes last.
1332 if (active_tab
&& is_dragging
)
1333 active_tab
->Paint(canvas
, cull_set
);
1336 const char* TabStrip::GetClassName() const {
1337 return kViewClassName
;
1340 gfx::Size
TabStrip::GetPreferredSize() const {
1341 int needed_tab_width
;
1342 if (touch_layout_
|| adjust_layout_
) {
1343 // For stacked tabs the minimum size is calculated as the size needed to
1344 // handle showing any number of tabs.
1346 Tab::GetTouchWidth() + (2 * kStackedPadding
* kMaxStackedCount
);
1348 // Otherwise the minimum width is based on the actual number of tabs.
1349 const int mini_tab_count
= GetMiniTabCount();
1350 needed_tab_width
= mini_tab_count
* Tab::GetMiniWidth();
1351 const int remaining_tab_count
= tab_count() - mini_tab_count
;
1352 const int min_selected_width
= Tab::GetMinimumSelectedSize().width();
1353 const int min_unselected_width
= Tab::GetMinimumUnselectedSize().width();
1354 if (remaining_tab_count
> 0) {
1355 needed_tab_width
+= kMiniToNonMiniGap
+ min_selected_width
+
1356 ((remaining_tab_count
- 1) * min_unselected_width
);
1358 if (tab_count() > 1)
1359 needed_tab_width
+= (tab_count() - 1) * kTabHorizontalOffset
;
1361 // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1362 // and don't force it to be larger than is necessary to show 20 tabs.
1363 const int largest_min_tab_width
=
1364 min_selected_width
+ 19 * (min_unselected_width
+ kTabHorizontalOffset
);
1365 needed_tab_width
= std::min(
1366 std::max(needed_tab_width
, min_selected_width
), largest_min_tab_width
);
1369 needed_tab_width
+ new_tab_button_width(),
1371 Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1374 void TabStrip::OnDragEntered(const DropTargetEvent
& event
) {
1375 // Force animations to stop, otherwise it makes the index calculation tricky.
1376 StopAnimating(true);
1378 UpdateDropIndex(event
);
1381 base::string16 title
;
1383 // Check whether the event data includes supported drop data.
1384 if (event
.data().GetURLAndTitle(
1385 ui::OSExchangeData::CONVERT_FILENAMES
, &url
, &title
) &&
1387 drop_info_
->url
= url
;
1389 // For file:// URLs, kick off a MIME type request in case they're dropped.
1390 if (url
.SchemeIsFile())
1391 controller()->CheckFileSupported(url
);
1395 int TabStrip::OnDragUpdated(const DropTargetEvent
& event
) {
1396 // Update the drop index even if the file is unsupported, to allow
1397 // dragging a file to the contents of another tab.
1398 UpdateDropIndex(event
);
1400 if (!drop_info_
->file_supported
)
1401 return ui::DragDropTypes::DRAG_NONE
;
1403 return GetDropEffect(event
);
1406 void TabStrip::OnDragExited() {
1407 SetDropIndex(-1, false);
1410 int TabStrip::OnPerformDrop(const DropTargetEvent
& event
) {
1411 if (!drop_info_
.get())
1412 return ui::DragDropTypes::DRAG_NONE
;
1414 const int drop_index
= drop_info_
->drop_index
;
1415 const bool drop_before
= drop_info_
->drop_before
;
1416 const bool file_supported
= drop_info_
->file_supported
;
1418 // Hide the drop indicator.
1419 SetDropIndex(-1, false);
1421 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1422 // have been changed after |drop_info_| was created.
1424 base::string16 title
;
1425 if (!file_supported
||
1426 !event
.data().GetURLAndTitle(
1427 ui::OSExchangeData::CONVERT_FILENAMES
, &url
, &title
) ||
1429 return ui::DragDropTypes::DRAG_NONE
;
1431 controller()->PerformDrop(drop_before
, drop_index
, url
);
1433 return GetDropEffect(event
);
1436 void TabStrip::GetAccessibleState(ui::AXViewState
* state
) {
1437 state
->role
= ui::AX_ROLE_TAB_LIST
;
1440 views::View
* TabStrip::GetTooltipHandlerForPoint(const gfx::Point
& point
) {
1441 if (!HitTestPoint(point
))
1444 if (!touch_layout_
) {
1445 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1446 // want to interfere.
1447 views::View
* v
= View::GetTooltipHandlerForPoint(point
);
1448 if (v
&& v
!= this && strcmp(v
->GetClassName(), Tab::kViewClassName
))
1451 views::View
* tab
= FindTabHitByPoint(point
);
1455 if (newtab_button_
->visible()) {
1457 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_
, point
);
1461 Tab
* tab
= FindTabForEvent(point
);
1463 return ConvertPointToViewAndGetTooltipHandler(this, tab
, point
);
1469 int TabStrip::GetImmersiveHeight() {
1470 return Tab::GetImmersiveHeight();
1473 ///////////////////////////////////////////////////////////////////////////////
1474 // TabStrip, private:
1476 void TabStrip::Init() {
1477 set_id(VIEW_ID_TAB_STRIP
);
1478 // So we get enter/exit on children to switch stacked layout on and off.
1479 set_notify_enter_exit_on_child(true);
1480 newtab_button_bounds_
.SetRect(0,
1482 kNewTabButtonAssetWidth
,
1483 kNewTabButtonAssetHeight
+
1484 kNewTabButtonVerticalOffset
);
1485 newtab_button_
= new NewTabButton(this, this);
1486 newtab_button_
->SetTooltipText(
1487 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB
));
1488 newtab_button_
->SetAccessibleName(
1489 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB
));
1490 newtab_button_
->SetImageAlignment(views::ImageButton::ALIGN_LEFT
,
1491 views::ImageButton::ALIGN_BOTTOM
);
1492 newtab_button_
->SetEventTargeter(
1493 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(newtab_button_
)));
1494 AddChildView(newtab_button_
);
1496 if (drop_indicator_width
== 0) {
1497 // Direction doesn't matter, both images are the same size.
1498 gfx::ImageSkia
* drop_image
= GetDropArrowImage(true);
1499 drop_indicator_width
= drop_image
->width();
1500 drop_indicator_height
= drop_image
->height();
1504 Tab
* TabStrip::CreateTab() {
1505 Tab
* tab
= new Tab(this);
1506 tab
->set_animation_container(animation_container_
.get());
1510 void TabStrip::StartInsertTabAnimation(int model_index
) {
1511 PrepareForAnimation();
1513 // The TabStrip can now use its entire width to lay out Tabs.
1514 in_tab_close_
= false;
1515 available_width_for_tabs_
= -1;
1517 GenerateIdealBounds();
1519 Tab
* tab
= tab_at(model_index
);
1520 if (model_index
== 0) {
1521 tab
->SetBounds(0, ideal_bounds(model_index
).y(), 0,
1522 ideal_bounds(model_index
).height());
1524 Tab
* last_tab
= tab_at(model_index
- 1);
1525 tab
->SetBounds(last_tab
->bounds().right() + kTabHorizontalOffset
,
1526 ideal_bounds(model_index
).y(), 0,
1527 ideal_bounds(model_index
).height());
1530 AnimateToIdealBounds();
1533 void TabStrip::StartMoveTabAnimation() {
1534 PrepareForAnimation();
1535 GenerateIdealBounds();
1536 AnimateToIdealBounds();
1539 void TabStrip::StartRemoveTabAnimation(int model_index
) {
1540 PrepareForAnimation();
1542 // Mark the tab as closing.
1543 Tab
* tab
= tab_at(model_index
);
1544 tab
->set_closing(true);
1546 RemoveTabFromViewModel(model_index
);
1548 ScheduleRemoveTabAnimation(tab
);
1551 void TabStrip::ScheduleRemoveTabAnimation(Tab
* tab
) {
1552 // Start an animation for the tabs.
1553 GenerateIdealBounds();
1554 AnimateToIdealBounds();
1556 // Animate the tab being closed to zero width.
1557 gfx::Rect tab_bounds
= tab
->bounds();
1558 tab_bounds
.set_width(0);
1559 bounds_animator_
.AnimateViewTo(tab
, tab_bounds
);
1560 bounds_animator_
.SetAnimationDelegate(
1562 scoped_ptr
<gfx::AnimationDelegate
>(new RemoveTabDelegate(this, tab
)));
1564 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1565 // like the new tab button magically appears from beyond the end of the tab
1567 if (TabDragController::IsAttachedTo(this)) {
1568 bounds_animator_
.StopAnimatingView(newtab_button_
);
1569 newtab_button_
->SetBoundsRect(newtab_button_bounds_
);
1573 void TabStrip::AnimateToIdealBounds() {
1574 for (int i
= 0; i
< tab_count(); ++i
) {
1575 Tab
* tab
= tab_at(i
);
1576 if (!tab
->dragging()) {
1577 bounds_animator_
.AnimateViewTo(tab
, ideal_bounds(i
));
1578 bounds_animator_
.SetAnimationDelegate(
1580 scoped_ptr
<gfx::AnimationDelegate
>(
1581 new TabAnimationDelegate(this, tab
)));
1585 bounds_animator_
.AnimateViewTo(newtab_button_
, newtab_button_bounds_
);
1588 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1589 return in_tab_close_
;
1592 void TabStrip::DoLayout() {
1593 last_layout_size_
= size();
1595 StopAnimating(false);
1597 SwapLayoutIfNecessary();
1600 touch_layout_
->SetWidth(tab_area_width());
1602 GenerateIdealBounds();
1604 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_
);
1609 bounds_animator_
.StopAnimatingView(newtab_button_
);
1610 newtab_button_
->SetBoundsRect(newtab_button_bounds_
);
1613 void TabStrip::SetTabVisibility() {
1614 // We could probably be more efficient here by making use of the fact that the
1615 // tabstrip will always have any visible tabs, and then any invisible tabs, so
1616 // we could e.g. binary-search for the changeover point. But since we have to
1617 // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1619 for (int i
= 0; i
< tab_count(); ++i
) {
1620 Tab
* tab
= tab_at(i
);
1621 tab
->SetVisible(ShouldTabBeVisible(tab
));
1623 for (TabsClosingMap::const_iterator
i(tabs_closing_map_
.begin());
1624 i
!= tabs_closing_map_
.end(); ++i
) {
1625 for (Tabs::const_iterator
j(i
->second
.begin()); j
!= i
->second
.end(); ++j
) {
1627 tab
->SetVisible(ShouldTabBeVisible(tab
));
1632 void TabStrip::DragActiveTab(const std::vector
<int>& initial_positions
,
1634 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions
.size()));
1635 if (!touch_layout_
) {
1636 StackDraggedTabs(delta
);
1639 SetIdealBoundsFromPositions(initial_positions
);
1640 touch_layout_
->DragActiveTab(delta
);
1644 void TabStrip::SetIdealBoundsFromPositions(const std::vector
<int>& positions
) {
1645 if (static_cast<size_t>(tab_count()) != positions
.size())
1648 for (int i
= 0; i
< tab_count(); ++i
) {
1649 gfx::Rect
bounds(ideal_bounds(i
));
1650 bounds
.set_x(positions
[i
]);
1651 tabs_
.set_ideal_bounds(i
, bounds
);
1655 void TabStrip::StackDraggedTabs(int delta
) {
1656 DCHECK(!touch_layout_
);
1657 GenerateIdealBounds();
1658 const int active_index
= controller_
->GetActiveIndex();
1659 DCHECK_NE(-1, active_index
);
1661 // Drag the tabs to the left, stacking tabs before the active tab.
1662 const int adjusted_delta
=
1663 std::min(ideal_bounds(active_index
).x() -
1664 kStackedPadding
* std::min(active_index
, kMaxStackedCount
),
1666 for (int i
= 0; i
<= active_index
; ++i
) {
1667 const int min_x
= std::min(i
, kMaxStackedCount
) * kStackedPadding
;
1668 gfx::Rect
new_bounds(ideal_bounds(i
));
1669 new_bounds
.set_x(std::max(min_x
, new_bounds
.x() - adjusted_delta
));
1670 tabs_
.set_ideal_bounds(i
, new_bounds
);
1672 const bool is_active_mini
= tab_at(active_index
)->data().mini
;
1673 const int active_width
= ideal_bounds(active_index
).width();
1674 for (int i
= active_index
+ 1; i
< tab_count(); ++i
) {
1675 const int max_x
= ideal_bounds(active_index
).x() +
1676 (kStackedPadding
* std::min(i
- active_index
, kMaxStackedCount
));
1677 gfx::Rect
new_bounds(ideal_bounds(i
));
1678 int new_x
= std::max(new_bounds
.x() + delta
, max_x
);
1679 if (new_x
== max_x
&& !tab_at(i
)->data().mini
&& !is_active_mini
&&
1680 new_bounds
.width() != active_width
)
1681 new_x
+= (active_width
- new_bounds
.width());
1682 new_bounds
.set_x(new_x
);
1683 tabs_
.set_ideal_bounds(i
, new_bounds
);
1686 // Drag the tabs to the right, stacking tabs after the active tab.
1687 const int last_tab_width
= ideal_bounds(tab_count() - 1).width();
1688 const int last_tab_x
= tab_area_width() - last_tab_width
;
1689 if (active_index
== tab_count() - 1 &&
1690 ideal_bounds(tab_count() - 1).x() == last_tab_x
)
1692 const int adjusted_delta
=
1693 std::min(last_tab_x
-
1694 kStackedPadding
* std::min(tab_count() - active_index
- 1,
1696 ideal_bounds(active_index
).x(),
1698 for (int last_index
= tab_count() - 1, i
= last_index
; i
>= active_index
;
1700 const int max_x
= last_tab_x
-
1701 std::min(tab_count() - i
- 1, kMaxStackedCount
) * kStackedPadding
;
1702 gfx::Rect
new_bounds(ideal_bounds(i
));
1703 int new_x
= std::min(max_x
, new_bounds
.x() + adjusted_delta
);
1704 // Because of rounding not all tabs are the same width. Adjust the
1705 // position to accommodate this, otherwise the stacking is off.
1706 if (new_x
== max_x
&& !tab_at(i
)->data().mini
&&
1707 new_bounds
.width() != last_tab_width
)
1708 new_x
+= (last_tab_width
- new_bounds
.width());
1709 new_bounds
.set_x(new_x
);
1710 tabs_
.set_ideal_bounds(i
, new_bounds
);
1712 for (int i
= active_index
- 1; i
>= 0; --i
) {
1713 const int min_x
= ideal_bounds(active_index
).x() -
1714 std::min(active_index
- i
, kMaxStackedCount
) * kStackedPadding
;
1715 gfx::Rect
new_bounds(ideal_bounds(i
));
1716 new_bounds
.set_x(std::min(min_x
, new_bounds
.x() + delta
));
1717 tabs_
.set_ideal_bounds(i
, new_bounds
);
1719 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_
->x())
1720 newtab_button_
->SetVisible(false);
1722 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_
);
1726 bool TabStrip::IsStackingDraggedTabs() const {
1727 return drag_controller_
.get() && drag_controller_
->started_drag() &&
1728 (drag_controller_
->move_behavior() ==
1729 TabDragController::MOVE_VISIBILE_TABS
);
1732 void TabStrip::LayoutDraggedTabsAt(const Tabs
& tabs
,
1734 const gfx::Point
& location
,
1735 bool initial_drag
) {
1736 // Immediately hide the new tab button if the last tab is being dragged.
1737 const Tab
* last_visible_tab
= GetLastVisibleTab();
1738 if (last_visible_tab
&& last_visible_tab
->dragging())
1739 newtab_button_
->SetVisible(false);
1740 std::vector
<gfx::Rect
> bounds
;
1741 CalculateBoundsForDraggedTabs(tabs
, &bounds
);
1742 DCHECK_EQ(tabs
.size(), bounds
.size());
1743 int active_tab_model_index
= GetModelIndexOfTab(active_tab
);
1744 int active_tab_index
= static_cast<int>(
1745 std::find(tabs
.begin(), tabs
.end(), active_tab
) - tabs
.begin());
1746 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1748 gfx::Rect new_bounds
= bounds
[i
];
1749 new_bounds
.Offset(location
.x(), location
.y());
1750 int consecutive_index
=
1751 active_tab_model_index
- (active_tab_index
- static_cast<int>(i
));
1752 // If this is the initial layout during a drag and the tabs aren't
1753 // consecutive animate the view into position. Do the same if the tab is
1754 // already animating (which means we previously caused it to animate).
1755 if ((initial_drag
&&
1756 GetModelIndexOfTab(tabs
[i
]) != consecutive_index
) ||
1757 bounds_animator_
.IsAnimating(tabs
[i
])) {
1758 bounds_animator_
.SetTargetBounds(tabs
[i
], new_bounds
);
1760 tab
->SetBoundsRect(new_bounds
);
1766 void TabStrip::CalculateBoundsForDraggedTabs(const Tabs
& tabs
,
1767 std::vector
<gfx::Rect
>* bounds
) {
1769 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1771 if (i
> 0 && tab
->data().mini
!= tabs
[i
- 1]->data().mini
)
1772 x
+= kMiniToNonMiniGap
;
1773 gfx::Rect new_bounds
= tab
->bounds();
1774 new_bounds
.set_origin(gfx::Point(x
, 0));
1775 bounds
->push_back(new_bounds
);
1776 x
+= tab
->width() + kTabHorizontalOffset
;
1780 int TabStrip::GetSizeNeededForTabs(const Tabs
& tabs
) {
1782 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1784 width
+= tab
->width();
1785 if (i
> 0 && tab
->data().mini
!= tabs
[i
- 1]->data().mini
)
1786 width
+= kMiniToNonMiniGap
;
1788 if (tabs
.size() > 0)
1789 width
+= kTabHorizontalOffset
* static_cast<int>(tabs
.size() - 1);
1793 int TabStrip::GetMiniTabCount() const {
1795 while (mini_count
< tab_count() && tab_at(mini_count
)->data().mini
)
1800 const Tab
* TabStrip::GetLastVisibleTab() const {
1801 for (int i
= tab_count() - 1; i
>= 0; --i
) {
1802 const Tab
* tab
= tab_at(i
);
1806 // While in normal use the tabstrip should always be wide enough to have at
1807 // least one visible tab, it can be zero-width in tests, meaning we get here.
1811 void TabStrip::RemoveTabFromViewModel(int index
) {
1812 // We still need to paint the tab until we actually remove it. Put it
1813 // in tabs_closing_map_ so we can find it.
1814 tabs_closing_map_
[index
].push_back(tab_at(index
));
1815 UpdateTabsClosingMap(index
+ 1, -1);
1816 tabs_
.Remove(index
);
1819 void TabStrip::RemoveAndDeleteTab(Tab
* tab
) {
1820 scoped_ptr
<Tab
> deleter(tab
);
1821 FindClosingTabResult
res(FindClosingTab(tab
));
1822 res
.first
->second
.erase(res
.second
);
1823 if (res
.first
->second
.empty())
1824 tabs_closing_map_
.erase(res
.first
);
1827 void TabStrip::UpdateTabsClosingMap(int index
, int delta
) {
1828 if (tabs_closing_map_
.empty())
1832 tabs_closing_map_
.find(index
- 1) != tabs_closing_map_
.end() &&
1833 tabs_closing_map_
.find(index
) != tabs_closing_map_
.end()) {
1834 const Tabs
& tabs(tabs_closing_map_
[index
]);
1835 tabs_closing_map_
[index
- 1].insert(
1836 tabs_closing_map_
[index
- 1].end(), tabs
.begin(), tabs
.end());
1838 TabsClosingMap updated_map
;
1839 for (TabsClosingMap::iterator
i(tabs_closing_map_
.begin());
1840 i
!= tabs_closing_map_
.end(); ++i
) {
1841 if (i
->first
> index
)
1842 updated_map
[i
->first
+ delta
] = i
->second
;
1843 else if (i
->first
< index
)
1844 updated_map
[i
->first
] = i
->second
;
1846 if (delta
> 0 && tabs_closing_map_
.find(index
) != tabs_closing_map_
.end())
1847 updated_map
[index
+ delta
] = tabs_closing_map_
[index
];
1848 tabs_closing_map_
.swap(updated_map
);
1851 void TabStrip::StartedDraggingTabs(const Tabs
& tabs
) {
1852 // Let the controller know that the user started dragging tabs.
1853 controller()->OnStartedDraggingTabs();
1855 // Hide the new tab button immediately if we didn't originate the drag.
1856 if (!drag_controller_
.get())
1857 newtab_button_
->SetVisible(false);
1859 PrepareForAnimation();
1861 // Reset dragging state of existing tabs.
1862 for (int i
= 0; i
< tab_count(); ++i
)
1863 tab_at(i
)->set_dragging(false);
1865 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1866 tabs
[i
]->set_dragging(true);
1867 bounds_animator_
.StopAnimatingView(tabs
[i
]);
1870 // Move the dragged tabs to their ideal bounds.
1871 GenerateIdealBounds();
1873 // Sets the bounds of the dragged tabs.
1874 for (size_t i
= 0; i
< tabs
.size(); ++i
) {
1875 int tab_data_index
= GetModelIndexOfTab(tabs
[i
]);
1876 DCHECK_NE(-1, tab_data_index
);
1877 tabs
[i
]->SetBoundsRect(ideal_bounds(tab_data_index
));
1883 void TabStrip::DraggedTabsDetached() {
1884 // Let the controller know that the user is not dragging this tabstrip's tabs
1886 controller()->OnStoppedDraggingTabs();
1887 newtab_button_
->SetVisible(true);
1890 void TabStrip::StoppedDraggingTabs(const Tabs
& tabs
,
1891 const std::vector
<int>& initial_positions
,
1894 // Let the controller know that the user stopped dragging tabs.
1895 controller()->OnStoppedDraggingTabs();
1897 newtab_button_
->SetVisible(true);
1898 if (move_only
&& touch_layout_
) {
1900 touch_layout_
->SizeToFit();
1902 SetIdealBoundsFromPositions(initial_positions
);
1904 bool is_first_tab
= true;
1905 for (size_t i
= 0; i
< tabs
.size(); ++i
)
1906 StoppedDraggingTab(tabs
[i
], &is_first_tab
);
1909 void TabStrip::StoppedDraggingTab(Tab
* tab
, bool* is_first_tab
) {
1910 int tab_data_index
= GetModelIndexOfTab(tab
);
1911 if (tab_data_index
== -1) {
1912 // The tab was removed before the drag completed. Don't do anything.
1916 if (*is_first_tab
) {
1917 *is_first_tab
= false;
1918 PrepareForAnimation();
1920 // Animate the view back to its correct position.
1921 GenerateIdealBounds();
1922 AnimateToIdealBounds();
1924 bounds_animator_
.AnimateViewTo(tab
, ideal_bounds(tab_data_index
));
1925 // Install a delegate to reset the dragging state when done. We have to leave
1926 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1927 bounds_animator_
.SetAnimationDelegate(
1929 scoped_ptr
<gfx::AnimationDelegate
>(
1930 new ResetDraggingStateDelegate(this, tab
)));
1933 void TabStrip::OwnDragController(TabDragController
* controller
) {
1934 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1935 // via corresponding calls to TabDragController::Detach() and
1936 // TabDragController::Attach(). There is one exception to that rule: when a
1937 // drag might start, we create a TabDragController that is owned by the
1938 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1939 // we then call Attach() on the source tabstrip, but since the source tabstrip
1940 // already owns the TabDragController, so we don't need to do anything.
1941 if (controller
!= drag_controller_
.get())
1942 drag_controller_
.reset(controller
);
1945 void TabStrip::DestroyDragController() {
1946 newtab_button_
->SetVisible(true);
1947 drag_controller_
.reset();
1950 TabDragController
* TabStrip::ReleaseDragController() {
1951 return drag_controller_
.release();
1954 TabStrip::FindClosingTabResult
TabStrip::FindClosingTab(const Tab
* tab
) {
1955 DCHECK(tab
->closing());
1956 for (TabsClosingMap::iterator
i(tabs_closing_map_
.begin());
1957 i
!= tabs_closing_map_
.end(); ++i
) {
1958 Tabs::iterator j
= std::find(i
->second
.begin(), i
->second
.end(), tab
);
1959 if (j
!= i
->second
.end())
1960 return FindClosingTabResult(i
, j
);
1963 return FindClosingTabResult(tabs_closing_map_
.end(), Tabs::iterator());
1966 void TabStrip::PaintClosingTabs(gfx::Canvas
* canvas
,
1968 const views::CullSet
& cull_set
) {
1969 if (tabs_closing_map_
.find(index
) == tabs_closing_map_
.end())
1972 const Tabs
& tabs
= tabs_closing_map_
[index
];
1973 for (Tabs::const_reverse_iterator
i(tabs
.rbegin()); i
!= tabs
.rend(); ++i
)
1974 (*i
)->Paint(canvas
, cull_set
);
1977 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View
* source
,
1978 const ui::MouseEvent
& event
) {
1979 if (!adjust_layout_
)
1982 // The following code attempts to switch to shrink (not stacked) layout when
1983 // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
1984 // to stacked layout when a touch device is used. This is made problematic by
1985 // windows generating mouse move events that do not clearly indicate the move
1986 // is the result of a touch device. This assumes a real mouse is used if
1987 // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
1988 // the time window |kMouseMoveTimeMS|. At the time we get a mouse press we
1989 // know whether its from a touch device or not, but we don't layout then else
1990 // everything shifts. Instead we wait for the release.
1992 // TODO(sky): revisit this when touch events are really plumbed through.
1994 switch (event
.type()) {
1995 case ui::ET_MOUSE_PRESSED
:
1996 mouse_move_count_
= 0;
1997 last_mouse_move_time_
= base::TimeTicks();
1998 SetResetToShrinkOnExit((event
.flags() & ui::EF_FROM_TOUCH
) == 0);
1999 if (reset_to_shrink_on_exit_
&& touch_layout_
) {
2000 gfx::Point
tab_strip_point(event
.location());
2001 views::View::ConvertPointToTarget(source
, this, &tab_strip_point
);
2002 Tab
* tab
= FindTabForEvent(tab_strip_point
);
2003 if (tab
&& touch_layout_
->IsStacked(GetModelIndexOfTab(tab
))) {
2004 SetStackedLayout(false);
2005 controller_
->StackedLayoutMaybeChanged();
2010 case ui::ET_MOUSE_MOVED
: {
2011 #if defined(USE_ASH)
2012 // Ash does not synthesize mouse events from touch events.
2013 SetResetToShrinkOnExit(true);
2015 gfx::Point
location(event
.location());
2016 ConvertPointToTarget(source
, this, &location
);
2017 if (location
== last_mouse_move_location_
)
2018 return; // Ignore spurious moves.
2019 last_mouse_move_location_
= location
;
2020 if ((event
.flags() & ui::EF_FROM_TOUCH
) == 0 &&
2021 (event
.flags() & ui::EF_IS_SYNTHESIZED
) == 0) {
2022 if ((base::TimeTicks::Now() - last_mouse_move_time_
).InMilliseconds() <
2024 if (mouse_move_count_
++ == kMouseMoveCountBeforeConsiderReal
)
2025 SetResetToShrinkOnExit(true);
2027 mouse_move_count_
= 1;
2028 last_mouse_move_time_
= base::TimeTicks::Now();
2031 last_mouse_move_time_
= base::TimeTicks();
2037 case ui::ET_MOUSE_RELEASED
: {
2038 gfx::Point
location(event
.location());
2039 ConvertPointToTarget(source
, this, &location
);
2040 last_mouse_move_location_
= location
;
2041 mouse_move_count_
= 0;
2042 last_mouse_move_time_
= base::TimeTicks();
2043 if ((event
.flags() & ui::EF_FROM_TOUCH
) == ui::EF_FROM_TOUCH
) {
2044 SetStackedLayout(true);
2045 controller_
->StackedLayoutMaybeChanged();
2055 void TabStrip::GetCurrentTabWidths(double* unselected_width
,
2056 double* selected_width
) const {
2057 *unselected_width
= current_unselected_width_
;
2058 *selected_width
= current_selected_width_
;
2061 void TabStrip::GetDesiredTabWidths(int tab_count
,
2063 double* unselected_width
,
2064 double* selected_width
) const {
2065 DCHECK(tab_count
>= 0 && mini_tab_count
>= 0 && mini_tab_count
<= tab_count
);
2066 const double min_unselected_width
= Tab::GetMinimumUnselectedSize().width();
2067 const double min_selected_width
= Tab::GetMinimumSelectedSize().width();
2069 *unselected_width
= min_unselected_width
;
2070 *selected_width
= min_selected_width
;
2072 if (tab_count
== 0) {
2073 // Return immediately to avoid divide-by-zero below.
2077 // Determine how much space we can actually allocate to tabs.
2078 int available_width
= (available_width_for_tabs_
< 0) ?
2079 tab_area_width() : available_width_for_tabs_
;
2080 if (mini_tab_count
> 0) {
2082 mini_tab_count
* (Tab::GetMiniWidth() + kTabHorizontalOffset
);
2083 tab_count
-= mini_tab_count
;
2084 if (tab_count
== 0) {
2085 *selected_width
= *unselected_width
= Tab::GetStandardSize().width();
2088 // Account for gap between the last mini-tab and first non-mini-tab.
2089 available_width
-= kMiniToNonMiniGap
;
2092 // Calculate the desired tab widths by dividing the available space into equal
2093 // portions. Don't let tabs get larger than the "standard width" or smaller
2094 // than the minimum width for each type, respectively.
2095 const int total_offset
= kTabHorizontalOffset
* (tab_count
- 1);
2096 const double desired_tab_width
= std::min((static_cast<double>(
2097 available_width
- total_offset
) / static_cast<double>(tab_count
)),
2098 static_cast<double>(Tab::GetStandardSize().width()));
2099 *unselected_width
= std::max(desired_tab_width
, min_unselected_width
);
2100 *selected_width
= std::max(desired_tab_width
, min_selected_width
);
2102 // When there are multiple tabs, we'll have one selected and some unselected
2103 // tabs. If the desired width was between the minimum sizes of these types,
2104 // try to shrink the tabs with the smaller minimum. For example, if we have
2105 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2106 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2107 // width of 1, the above code would set *unselected_width = 2.5,
2108 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2109 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2111 if (tab_count
> 1) {
2112 if (desired_tab_width
< min_selected_width
) {
2113 // Unselected width = (total width - selected width) / (num_tabs - 1)
2114 *unselected_width
= std::max(static_cast<double>(
2115 available_width
- total_offset
- min_selected_width
) /
2116 static_cast<double>(tab_count
- 1), min_unselected_width
);
2121 void TabStrip::ResizeLayoutTabs() {
2122 // We've been called back after the TabStrip has been emptied out (probably
2123 // just prior to the window being destroyed). We need to do nothing here or
2124 // else GetTabAt below will crash.
2125 if (tab_count() == 0)
2128 // It is critically important that this is unhooked here, otherwise we will
2129 // keep spying on messages forever.
2130 RemoveMessageLoopObserver();
2132 in_tab_close_
= false;
2133 available_width_for_tabs_
= -1;
2134 int mini_tab_count
= GetMiniTabCount();
2135 if (mini_tab_count
== tab_count()) {
2136 // Only mini-tabs, we know the tab widths won't have changed (all
2137 // mini-tabs have the same width), so there is nothing to do.
2140 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2141 // then the width of the active tab may not change, but other widths may
2142 // have. This is particularly important if we've overflowed (all tabs are at
2144 StartResizeLayoutAnimation();
2147 void TabStrip::ResizeLayoutTabsFromTouch() {
2148 // Don't resize if the user is interacting with the tabstrip.
2149 if (!drag_controller_
.get())
2152 StartResizeLayoutTabsFromTouchTimer();
2155 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2156 resize_layout_timer_
.Stop();
2157 resize_layout_timer_
.Start(
2158 FROM_HERE
, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS
),
2159 this, &TabStrip::ResizeLayoutTabsFromTouch
);
2162 void TabStrip::SetTabBoundsForDrag(const std::vector
<gfx::Rect
>& tab_bounds
) {
2163 StopAnimating(false);
2164 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds
.size()));
2165 for (int i
= 0; i
< tab_count(); ++i
)
2166 tab_at(i
)->SetBoundsRect(tab_bounds
[i
]);
2167 // Reset the layout size as we've effectively layed out a different size.
2168 // This ensures a layout happens after the drag is done.
2169 last_layout_size_
= gfx::Size();
2172 void TabStrip::AddMessageLoopObserver() {
2173 if (!mouse_watcher_
.get()) {
2174 mouse_watcher_
.reset(
2175 new views::MouseWatcher(
2176 new views::MouseWatcherViewHost(
2177 this, gfx::Insets(0, 0, kTabStripAnimationVSlop
, 0)),
2180 mouse_watcher_
->Start();
2183 void TabStrip::RemoveMessageLoopObserver() {
2184 mouse_watcher_
.reset(NULL
);
2187 gfx::Rect
TabStrip::GetDropBounds(int drop_index
,
2190 DCHECK_NE(drop_index
, -1);
2192 if (drop_index
< tab_count()) {
2193 Tab
* tab
= tab_at(drop_index
);
2195 center_x
= tab
->x() - (kTabHorizontalOffset
/ 2);
2197 center_x
= tab
->x() + (tab
->width() / 2);
2199 Tab
* last_tab
= tab_at(drop_index
- 1);
2200 center_x
= last_tab
->x() + last_tab
->width() + (kTabHorizontalOffset
/ 2);
2203 // Mirror the center point if necessary.
2204 center_x
= GetMirroredXInView(center_x
);
2206 // Determine the screen bounds.
2207 gfx::Point
drop_loc(center_x
- drop_indicator_width
/ 2,
2208 -drop_indicator_height
);
2209 ConvertPointToScreen(this, &drop_loc
);
2210 gfx::Rect
drop_bounds(drop_loc
.x(), drop_loc
.y(), drop_indicator_width
,
2211 drop_indicator_height
);
2213 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2214 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2215 gfx::Display display
= screen
->GetDisplayMatching(drop_bounds
);
2216 *is_beneath
= !display
.bounds().Contains(drop_bounds
);
2218 drop_bounds
.Offset(0, drop_bounds
.height() + height());
2223 void TabStrip::UpdateDropIndex(const DropTargetEvent
& event
) {
2224 // If the UI layout is right-to-left, we need to mirror the mouse
2225 // coordinates since we calculate the drop index based on the
2226 // original (and therefore non-mirrored) positions of the tabs.
2227 const int x
= GetMirroredXInView(event
.x());
2228 // We don't allow replacing the urls of mini-tabs.
2229 for (int i
= GetMiniTabCount(); i
< tab_count(); ++i
) {
2230 Tab
* tab
= tab_at(i
);
2231 const int tab_max_x
= tab
->x() + tab
->width();
2232 const int hot_width
= tab
->width() / kTabEdgeRatioInverse
;
2233 if (x
< tab_max_x
) {
2234 if (x
< tab
->x() + hot_width
)
2235 SetDropIndex(i
, true);
2236 else if (x
>= tab_max_x
- hot_width
)
2237 SetDropIndex(i
+ 1, true);
2239 SetDropIndex(i
, false);
2244 // The drop isn't over a tab, add it to the end.
2245 SetDropIndex(tab_count(), true);
2248 void TabStrip::SetDropIndex(int tab_data_index
, bool drop_before
) {
2249 // Let the controller know of the index update.
2250 controller()->OnDropIndexUpdate(tab_data_index
, drop_before
);
2252 if (tab_data_index
== -1) {
2253 if (drop_info_
.get())
2254 drop_info_
.reset(NULL
);
2258 if (drop_info_
.get() && drop_info_
->drop_index
== tab_data_index
&&
2259 drop_info_
->drop_before
== drop_before
) {
2264 gfx::Rect drop_bounds
= GetDropBounds(tab_data_index
, drop_before
,
2267 if (!drop_info_
.get()) {
2269 new DropInfo(tab_data_index
, drop_before
, !is_beneath
, GetWidget()));
2271 drop_info_
->drop_index
= tab_data_index
;
2272 drop_info_
->drop_before
= drop_before
;
2273 if (is_beneath
== drop_info_
->point_down
) {
2274 drop_info_
->point_down
= !is_beneath
;
2275 drop_info_
->arrow_view
->SetImage(
2276 GetDropArrowImage(drop_info_
->point_down
));
2280 // Reposition the window. Need to show it too as the window is initially
2282 drop_info_
->arrow_window
->SetBounds(drop_bounds
);
2283 drop_info_
->arrow_window
->Show();
2286 int TabStrip::GetDropEffect(const ui::DropTargetEvent
& event
) {
2287 const int source_ops
= event
.source_operations();
2288 if (source_ops
& ui::DragDropTypes::DRAG_COPY
)
2289 return ui::DragDropTypes::DRAG_COPY
;
2290 if (source_ops
& ui::DragDropTypes::DRAG_LINK
)
2291 return ui::DragDropTypes::DRAG_LINK
;
2292 return ui::DragDropTypes::DRAG_MOVE
;
2296 gfx::ImageSkia
* TabStrip::GetDropArrowImage(bool is_down
) {
2297 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2298 is_down
? IDR_TAB_DROP_DOWN
: IDR_TAB_DROP_UP
);
2301 // TabStrip::DropInfo ----------------------------------------------------------
2303 TabStrip::DropInfo::DropInfo(int drop_index
,
2306 views::Widget
* context
)
2307 : drop_index(drop_index
),
2308 drop_before(drop_before
),
2309 point_down(point_down
),
2310 file_supported(true) {
2311 arrow_view
= new views::ImageView
;
2312 arrow_view
->SetImage(GetDropArrowImage(point_down
));
2314 arrow_window
= new views::Widget
;
2315 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
2316 params
.keep_on_top
= true;
2317 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
2318 params
.accept_events
= false;
2319 params
.bounds
= gfx::Rect(drop_indicator_width
, drop_indicator_height
);
2320 params
.context
= context
->GetNativeWindow();
2321 arrow_window
->Init(params
);
2322 arrow_window
->SetContentsView(arrow_view
);
2325 TabStrip::DropInfo::~DropInfo() {
2326 // Close eventually deletes the window, which deletes arrow_view too.
2327 arrow_window
->Close();
2330 ///////////////////////////////////////////////////////////////////////////////
2332 void TabStrip::PrepareForAnimation() {
2333 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2334 for (int i
= 0; i
< tab_count(); ++i
)
2335 tab_at(i
)->set_dragging(false);
2339 void TabStrip::GenerateIdealBounds() {
2342 if (touch_layout_
) {
2343 if (tabs_
.view_size() == 0)
2346 int new_tab_x
= tabs_
.ideal_bounds(tabs_
.view_size() - 1).right() +
2347 kNewTabButtonHorizontalOffset
;
2348 newtab_button_bounds_
.set_origin(gfx::Point(new_tab_x
, new_tab_y
));
2352 GetDesiredTabWidths(tab_count(), GetMiniTabCount(),
2353 ¤t_unselected_width_
, ¤t_selected_width_
);
2355 // NOTE: This currently assumes a tab's height doesn't differ based on
2356 // selected state or the number of tabs in the strip!
2357 int tab_height
= Tab::GetStandardSize().height();
2358 int first_non_mini_index
= 0;
2359 double tab_x
= GenerateIdealBoundsForMiniTabs(&first_non_mini_index
);
2360 for (int i
= first_non_mini_index
; i
< tab_count(); ++i
) {
2361 Tab
* tab
= tab_at(i
);
2362 DCHECK(!tab
->data().mini
);
2364 tab
->IsActive() ? current_selected_width_
: current_unselected_width_
;
2365 double end_of_tab
= tab_x
+ tab_width
;
2366 int rounded_tab_x
= Round(tab_x
);
2367 tabs_
.set_ideal_bounds(
2369 gfx::Rect(rounded_tab_x
, 0, Round(end_of_tab
) - rounded_tab_x
,
2371 tab_x
= end_of_tab
+ kTabHorizontalOffset
;
2374 // Update bounds of new tab button.
2376 if ((Tab::GetStandardSize().width() - Round(current_unselected_width_
)) > 1 &&
2378 // We're shrinking tabs, so we need to anchor the New Tab button to the
2379 // right edge of the TabStrip's bounds, rather than the right edge of the
2380 // right-most Tab, otherwise it'll bounce when animating.
2381 new_tab_x
= width() - newtab_button_bounds_
.width();
2383 new_tab_x
= Round(tab_x
- kTabHorizontalOffset
) +
2384 kNewTabButtonHorizontalOffset
;
2386 newtab_button_bounds_
.set_origin(gfx::Point(new_tab_x
, new_tab_y
));
2389 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index
) {
2391 int mini_width
= Tab::GetMiniWidth();
2392 int tab_height
= Tab::GetStandardSize().height();
2394 for (; index
< tab_count() && tab_at(index
)->data().mini
; ++index
) {
2395 tabs_
.set_ideal_bounds(index
, gfx::Rect(next_x
, 0, mini_width
, tab_height
));
2396 next_x
+= mini_width
+ kTabHorizontalOffset
;
2398 if (index
> 0 && index
< tab_count())
2399 next_x
+= kMiniToNonMiniGap
;
2400 if (first_non_mini_index
)
2401 *first_non_mini_index
= index
;
2405 void TabStrip::StartResizeLayoutAnimation() {
2406 PrepareForAnimation();
2407 GenerateIdealBounds();
2408 AnimateToIdealBounds();
2411 void TabStrip::StartMiniTabAnimation() {
2412 in_tab_close_
= false;
2413 available_width_for_tabs_
= -1;
2415 PrepareForAnimation();
2417 GenerateIdealBounds();
2418 AnimateToIdealBounds();
2421 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index
) {
2422 // The user initiated the close. We want to persist the bounds of all the
2423 // existing tabs, so we manually shift ideal_bounds then animate.
2424 Tab
* tab_closing
= tab_at(model_index
);
2425 int delta
= tab_closing
->width() + kTabHorizontalOffset
;
2426 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2427 // add the extra padding.
2428 DCHECK_LT(model_index
, tab_count() - 1);
2429 if (tab_closing
->data().mini
&& !tab_at(model_index
+ 1)->data().mini
)
2430 delta
+= kMiniToNonMiniGap
;
2432 for (int i
= model_index
+ 1; i
< tab_count(); ++i
) {
2433 gfx::Rect bounds
= ideal_bounds(i
);
2434 bounds
.set_x(bounds
.x() - delta
);
2435 tabs_
.set_ideal_bounds(i
, bounds
);
2438 // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2439 // overflow tabs that will be able to animate into the strip, in which case
2440 // the new tab button should stay where it is.
2441 newtab_button_bounds_
.set_x(std::min(
2442 width() - newtab_button_bounds_
.width(),
2443 ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset
));
2445 PrepareForAnimation();
2447 tab_closing
->set_closing(true);
2449 // We still need to paint the tab until we actually remove it. Put it in
2450 // tabs_closing_map_ so we can find it.
2451 RemoveTabFromViewModel(model_index
);
2453 AnimateToIdealBounds();
2455 gfx::Rect tab_bounds
= tab_closing
->bounds();
2456 tab_bounds
.set_width(0);
2457 bounds_animator_
.AnimateViewTo(tab_closing
, tab_bounds
);
2459 // Register delegate to do cleanup when done, BoundsAnimator takes
2460 // ownership of RemoveTabDelegate.
2461 bounds_animator_
.SetAnimationDelegate(
2463 scoped_ptr
<gfx::AnimationDelegate
>(
2464 new RemoveTabDelegate(this, tab_closing
)));
2467 bool TabStrip::IsPointInTab(Tab
* tab
,
2468 const gfx::Point
& point_in_tabstrip_coords
) {
2469 gfx::Point
point_in_tab_coords(point_in_tabstrip_coords
);
2470 View::ConvertPointToTarget(this, tab
, &point_in_tab_coords
);
2471 return tab
->HitTestPoint(point_in_tab_coords
);
2474 int TabStrip::GetStartXForNormalTabs() const {
2475 int mini_tab_count
= GetMiniTabCount();
2476 if (mini_tab_count
== 0)
2478 return mini_tab_count
* (Tab::GetMiniWidth() + kTabHorizontalOffset
) +
2482 Tab
* TabStrip::FindTabForEvent(const gfx::Point
& point
) {
2483 if (touch_layout_
) {
2484 int active_tab_index
= touch_layout_
->active_index();
2485 if (active_tab_index
!= -1) {
2486 Tab
* tab
= FindTabForEventFrom(point
, active_tab_index
, -1);
2488 tab
= FindTabForEventFrom(point
, active_tab_index
+ 1, 1);
2492 return FindTabForEventFrom(point
, 0, 1);
2494 for (int i
= 0; i
< tab_count(); ++i
) {
2495 if (IsPointInTab(tab_at(i
), point
))
2502 Tab
* TabStrip::FindTabForEventFrom(const gfx::Point
& point
,
2505 // |start| equals tab_count() when there are only pinned tabs.
2506 if (start
== tab_count())
2508 for (int i
= start
; i
>= 0 && i
< tab_count(); i
+= delta
) {
2509 if (IsPointInTab(tab_at(i
), point
))
2515 views::View
* TabStrip::FindTabHitByPoint(const gfx::Point
& point
) {
2516 // The display order doesn't necessarily match the child list order, so we
2517 // walk the display list hit-testing Tabs. Since the active tab always
2518 // renders on top of adjacent tabs, it needs to be hit-tested before any
2519 // left-adjacent Tab, so we look ahead for it as we walk.
2520 for (int i
= 0; i
< tab_count(); ++i
) {
2521 Tab
* next_tab
= i
< (tab_count() - 1) ? tab_at(i
+ 1) : NULL
;
2522 if (next_tab
&& next_tab
->IsActive() && IsPointInTab(next_tab
, point
))
2524 if (IsPointInTab(tab_at(i
), point
))
2531 std::vector
<int> TabStrip::GetTabXCoordinates() {
2532 std::vector
<int> results
;
2533 for (int i
= 0; i
< tab_count(); ++i
)
2534 results
.push_back(ideal_bounds(i
).x());
2538 void TabStrip::SwapLayoutIfNecessary() {
2539 bool needs_touch
= NeedsTouchLayout();
2540 bool using_touch
= touch_layout_
!= NULL
;
2541 if (needs_touch
== using_touch
)
2545 gfx::Size
tab_size(Tab::GetMinimumSelectedSize());
2546 tab_size
.set_width(Tab::GetTouchWidth());
2547 touch_layout_
.reset(new StackedTabStripLayout(
2549 kTabHorizontalOffset
,
2553 touch_layout_
->SetWidth(tab_area_width());
2554 // This has to be after SetWidth() as SetWidth() is going to reset the
2555 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2556 // many mini-tabs there are).
2557 GenerateIdealBoundsForMiniTabs(NULL
);
2558 touch_layout_
->SetXAndMiniCount(GetStartXForNormalTabs(),
2560 touch_layout_
->SetActiveIndex(controller_
->GetActiveIndex());
2562 touch_layout_
.reset();
2564 PrepareForAnimation();
2565 GenerateIdealBounds();
2567 AnimateToIdealBounds();
2570 bool TabStrip::NeedsTouchLayout() const {
2571 if (!stacked_layout_
)
2574 int mini_tab_count
= GetMiniTabCount();
2575 int normal_count
= tab_count() - mini_tab_count
;
2576 if (normal_count
<= 1 || normal_count
== mini_tab_count
)
2578 int x
= GetStartXForNormalTabs();
2579 int available_width
= tab_area_width() - x
;
2580 return (Tab::GetTouchWidth() * normal_count
+
2581 kTabHorizontalOffset
* (normal_count
- 1)) > available_width
;
2584 void TabStrip::SetResetToShrinkOnExit(bool value
) {
2585 if (!adjust_layout_
)
2588 if (value
&& !stacked_layout_
)
2589 value
= false; // We're already using shrink (not stacked) layout.
2591 if (value
== reset_to_shrink_on_exit_
)
2594 reset_to_shrink_on_exit_
= value
;
2595 // Add an observer so we know when the mouse moves out of the tabstrip.
2596 if (reset_to_shrink_on_exit_
)
2597 AddMessageLoopObserver();
2599 RemoveMessageLoopObserver();
2602 void TabStrip::ButtonPressed(views::Button
* sender
, const ui::Event
& event
) {
2603 if (sender
== newtab_button_
) {
2604 content::RecordAction(UserMetricsAction("NewTab_Button"));
2605 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON
,
2606 TabStripModel::NEW_TAB_ENUM_COUNT
);
2607 if (event
.IsMouseEvent()) {
2608 const ui::MouseEvent
& mouse
= static_cast<const ui::MouseEvent
&>(event
);
2609 if (mouse
.IsOnlyMiddleMouseButton()) {
2610 base::string16 clipboard_text
= GetClipboardText();
2611 if (!clipboard_text
.empty())
2612 controller()->CreateNewTabWithLocation(clipboard_text
);
2617 controller()->CreateNewTab();
2618 if (event
.type() == ui::ET_GESTURE_TAP
)
2619 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP
);
2623 // Overridden to support automation. See automation_proxy_uitest.cc.
2624 const views::View
* TabStrip::GetViewByID(int view_id
) const {
2625 if (tab_count() > 0) {
2626 if (view_id
== VIEW_ID_TAB_LAST
)
2627 return tab_at(tab_count() - 1);
2628 if ((view_id
>= VIEW_ID_TAB_0
) && (view_id
< VIEW_ID_TAB_LAST
)) {
2629 int index
= view_id
- VIEW_ID_TAB_0
;
2630 return (index
>= 0 && index
< tab_count()) ? tab_at(index
) : NULL
;
2634 return View::GetViewByID(view_id
);
2637 bool TabStrip::OnMousePressed(const ui::MouseEvent
& event
) {
2638 UpdateStackedLayoutFromMouseEvent(this, event
);
2639 // We can't return true here, else clicking in an empty area won't drag the
2644 bool TabStrip::OnMouseDragged(const ui::MouseEvent
& event
) {
2645 ContinueDrag(this, event
);
2649 void TabStrip::OnMouseReleased(const ui::MouseEvent
& event
) {
2650 EndDrag(END_DRAG_COMPLETE
);
2651 UpdateStackedLayoutFromMouseEvent(this, event
);
2654 void TabStrip::OnMouseCaptureLost() {
2655 EndDrag(END_DRAG_CAPTURE_LOST
);
2658 void TabStrip::OnMouseMoved(const ui::MouseEvent
& event
) {
2659 UpdateStackedLayoutFromMouseEvent(this, event
);
2662 void TabStrip::OnMouseEntered(const ui::MouseEvent
& event
) {
2663 SetResetToShrinkOnExit(true);
2666 void TabStrip::OnGestureEvent(ui::GestureEvent
* event
) {
2667 SetResetToShrinkOnExit(false);
2668 switch (event
->type()) {
2669 case ui::ET_GESTURE_SCROLL_END
:
2670 case ui::ET_SCROLL_FLING_START
:
2671 case ui::ET_GESTURE_END
:
2672 EndDrag(END_DRAG_COMPLETE
);
2673 if (adjust_layout_
) {
2674 SetStackedLayout(true);
2675 controller_
->StackedLayoutMaybeChanged();
2679 case ui::ET_GESTURE_LONG_PRESS
:
2680 if (drag_controller_
.get())
2681 drag_controller_
->SetMoveBehavior(TabDragController::REORDER
);
2684 case ui::ET_GESTURE_LONG_TAP
: {
2685 EndDrag(END_DRAG_CANCEL
);
2686 gfx::Point local_point
= event
->location();
2687 Tab
* tab
= FindTabForEvent(local_point
);
2689 ConvertPointToScreen(this, &local_point
);
2690 ShowContextMenuForTab(tab
, local_point
, ui::MENU_SOURCE_TOUCH
);
2695 case ui::ET_GESTURE_SCROLL_UPDATE
:
2696 ContinueDrag(this, *event
);
2699 case ui::ET_GESTURE_TAP_DOWN
:
2700 EndDrag(END_DRAG_CANCEL
);
2703 case ui::ET_GESTURE_TAP
: {
2704 const int active_index
= controller_
->GetActiveIndex();
2705 DCHECK_NE(-1, active_index
);
2706 Tab
* active_tab
= tab_at(active_index
);
2707 TouchUMA::GestureActionType action
= TouchUMA::GESTURE_TABNOSWITCH_TAP
;
2708 if (active_tab
->tab_activated_with_last_tap_down())
2709 action
= TouchUMA::GESTURE_TABSWITCH_TAP
;
2710 TouchUMA::RecordGestureAction(action
);
2717 event
->SetHandled();
2720 views::View
* TabStrip::TargetForRect(views::View
* root
, const gfx::Rect
& rect
) {
2721 CHECK_EQ(root
, this);
2723 if (!views::UsePointBasedTargeting(rect
))
2724 return views::ViewTargeterDelegate::TargetForRect(root
, rect
);
2725 const gfx::Point
point(rect
.CenterPoint());
2727 if (!touch_layout_
) {
2728 // Return any view that isn't a Tab or this TabStrip immediately. We don't
2729 // want to interfere.
2730 views::View
* v
= views::ViewTargeterDelegate::TargetForRect(root
, rect
);
2731 if (v
&& v
!= this && strcmp(v
->GetClassName(), Tab::kViewClassName
))
2734 views::View
* tab
= FindTabHitByPoint(point
);
2738 if (newtab_button_
->visible()) {
2740 ConvertPointToViewAndGetEventHandler(this, newtab_button_
, point
);
2744 Tab
* tab
= FindTabForEvent(point
);
2746 return ConvertPointToViewAndGetEventHandler(this, tab
, point
);