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/image/image_skia.h"
44 #include "ui/gfx/image/image_skia_operations.h"
45 #include "ui/gfx/path.h"
46 #include "ui/gfx/rect_conversions.h"
47 #include "ui/gfx/screen.h"
48 #include "ui/gfx/size.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/hwnd_util.h"
62 #include "ui/views/widget/monitor_win.h"
63 #include "ui/views/win/hwnd_util.h"
66 using base::UserMetricsAction;
67 using ui::DropTargetEvent;
71 static const int kTabStripAnimationVSlop = 40;
72 // Inactive tabs in a native frame are slightly transparent.
73 static const int kGlassFrameInactiveTabAlpha = 200;
74 // If there are multiple tabs selected then make non-selected inactive tabs
75 // even more transparent.
76 static const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
78 // Alpha applied to all elements save the selected tabs.
79 static const int kInactiveTabAndNewTabButtonAlphaAsh = 230;
80 static const int kInactiveTabAndNewTabButtonAlpha = 255;
82 // Inverse ratio of the width of a tab edge to the width of the tab. When
83 // hovering over the left or right edge of a tab, the drop indicator will
84 // point between tabs.
85 static const int kTabEdgeRatioInverse = 4;
87 // Size of the drop indicator.
88 static int drop_indicator_width;
89 static int drop_indicator_height;
91 static inline int Round(double x) {
92 // Why oh why is this not in a standard header?
93 return static_cast<int>(floor(x + 0.5));
96 // Max number of stacked tabs.
97 static const int kMaxStackedCount = 4;
99 // Padding between stacked tabs.
100 static const int kStackedPadding = 6;
102 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
103 #if !defined(USE_ASH)
104 const int kMouseMoveTimeMS = 200;
105 const int kMouseMoveCountBeforeConsiderReal = 3;
108 // Amount of time we delay before resizing after a close from a touch.
109 const int kTouchResizeLayoutTimeMS = 2000;
111 // Amount the left edge of a tab is offset from the rectangle of the tab's
112 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
113 // Affects the size of the "V" between adjacent tabs.
114 const int kTabHorizontalOffset = -26;
116 // Amount to adjust the clip by when the tab is stacked before the active index.
117 const int kStackedTabLeftClip = 20;
119 // Amount to adjust the clip by when the tab is stacked after the active index.
120 const int kStackedTabRightClip = 20;
122 base::string16 GetClipboardText() {
123 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
124 return base::string16();
125 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
127 base::string16 clipboard_text;
128 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
129 return clipboard_text;
132 // Animation delegate used for any automatic tab movement. Hides the tab if it
133 // is not fully visible within the tabstrip area, to prevent overflow clipping.
134 class TabAnimationDelegate : public gfx::AnimationDelegate {
136 TabAnimationDelegate(TabStrip* tab_strip, Tab* tab);
137 ~TabAnimationDelegate() override;
139 void AnimationProgressed(const gfx::Animation* animation) override;
142 TabStrip* tab_strip() { return tab_strip_; }
143 Tab* tab() { return tab_; }
146 TabStrip* const tab_strip_;
149 DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate);
152 TabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab)
153 : tab_strip_(tab_strip),
157 TabAnimationDelegate::~TabAnimationDelegate() {
160 void TabAnimationDelegate::AnimationProgressed(
161 const gfx::Animation* animation) {
162 tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_));
165 // Animation delegate used when a dragged tab is released. When done sets the
166 // dragging state to false.
167 class ResetDraggingStateDelegate : public TabAnimationDelegate {
169 ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab);
170 ~ResetDraggingStateDelegate() override;
172 void AnimationEnded(const gfx::Animation* animation) override;
173 void AnimationCanceled(const gfx::Animation* animation) override;
176 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
179 ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip,
181 : TabAnimationDelegate(tab_strip, tab) {
184 ResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
187 void ResetDraggingStateDelegate::AnimationEnded(
188 const gfx::Animation* animation) {
189 tab()->set_dragging(false);
190 AnimationProgressed(animation); // Forces tab visibility to update.
193 void ResetDraggingStateDelegate::AnimationCanceled(
194 const gfx::Animation* animation) {
195 AnimationEnded(animation);
198 // If |dest| contains the point |point_in_source| the event handler from |dest|
199 // is returned. Otherwise NULL is returned.
200 views::View* ConvertPointToViewAndGetEventHandler(
203 const gfx::Point& point_in_source) {
204 gfx::Point dest_point(point_in_source);
205 views::View::ConvertPointToTarget(source, dest, &dest_point);
206 return dest->HitTestPoint(dest_point) ?
207 dest->GetEventHandlerForPoint(dest_point) : NULL;
210 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
211 // should return NULL if it does not contain the point.
212 views::View* ConvertPointToViewAndGetTooltipHandler(
215 const gfx::Point& point_in_source) {
216 gfx::Point dest_point(point_in_source);
217 views::View::ConvertPointToTarget(source, dest, &dest_point);
218 return dest->GetTooltipHandlerForPoint(dest_point);
221 TabDragController::EventSource EventSourceFromEvent(
222 const ui::LocatedEvent& event) {
223 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
224 TabDragController::EVENT_SOURCE_MOUSE;
229 ///////////////////////////////////////////////////////////////////////////////
232 // A subclass of button that hit-tests to the shape of the new tab button and
233 // does custom drawing.
235 class NewTabButton : public views::ImageButton,
236 public views::MaskedTargeterDelegate {
238 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
239 ~NewTabButton() override;
241 // Set the background offset used to match the background image to the frame
243 void set_background_offset(const gfx::Point& offset) {
244 background_offset_ = offset;
250 virtual void OnMouseReleased(const ui::MouseEvent& event) override;
252 void OnPaint(gfx::Canvas* canvas) override;
255 void OnGestureEvent(ui::GestureEvent* event) override;
258 // views::MaskedTargeterDelegate:
259 bool GetHitTestMask(gfx::Path* mask) const override;
261 bool ShouldWindowContentsBeTransparent() const;
262 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
264 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
266 gfx::ImageSkia GetImageForScale(float scale) const;
268 // Tab strip that contains this button.
269 TabStrip* tab_strip_;
271 // The offset used to paint the background image.
272 gfx::Point background_offset_;
274 // were we destroyed?
277 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
280 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
281 : views::ImageButton(listener),
282 tab_strip_(tab_strip),
284 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
285 set_triggerable_event_flags(triggerable_event_flags() |
286 ui::EF_MIDDLE_MOUSE_BUTTON);
290 NewTabButton::~NewTabButton() {
296 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
297 if (event.IsOnlyRightMouseButton()) {
298 gfx::Point point = event.location();
299 views::View::ConvertPointToScreen(this, &point);
300 bool destroyed = false;
301 destroyed_ = &destroyed;
302 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
307 SetState(views::CustomButton::STATE_NORMAL);
310 views::ImageButton::OnMouseReleased(event);
314 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
315 gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
316 canvas->DrawImageInt(image, 0, height() - image.height());
319 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
320 // Consume all gesture events here so that the parent (Tab) does not
321 // start consuming gestures.
322 views::ImageButton::OnGestureEvent(event);
326 bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
329 // When the button is sized to the top of the tab strip, we want the hit
330 // test mask to be defined as the complete (rectangular) bounds of the
332 if (tab_strip_->SizeTabButtonToTopOfTabStrip()) {
333 gfx::Rect button_bounds(GetContentsBounds());
334 button_bounds.set_x(GetMirroredXForRect(button_bounds));
335 mask->addRect(RectToSkRect(button_bounds));
339 SkScalar w = SkIntToScalar(width());
340 SkScalar v_offset = SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset);
342 // These values are defined by the shape of the new tab image. Should that
343 // image ever change, these values will need to be updated. They're so
344 // custom it's not really worth defining constants for.
345 // These values are correct for regular and USE_ASH versions of the image.
346 mask->moveTo(0, v_offset + 1);
347 mask->lineTo(w - 7, v_offset + 1);
348 mask->lineTo(w - 4, v_offset + 4);
349 mask->lineTo(w, v_offset + 16);
350 mask->lineTo(w - 1, v_offset + 17);
351 mask->lineTo(7, v_offset + 17);
352 mask->lineTo(4, v_offset + 13);
353 mask->lineTo(0, v_offset + 1);
359 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
360 return GetWidget() &&
361 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
364 gfx::ImageSkia NewTabButton::GetBackgroundImage(
365 views::CustomButton::ButtonState state,
367 int background_id = 0;
368 if (ShouldWindowContentsBeTransparent()) {
369 background_id = IDR_THEME_TAB_BACKGROUND_V;
370 } else if (tab_strip_->controller()->IsIncognito()) {
371 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
373 background_id = IDR_THEME_TAB_BACKGROUND;
378 case views::CustomButton::STATE_NORMAL:
379 case views::CustomButton::STATE_HOVERED:
380 alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
383 case views::CustomButton::STATE_PRESSED:
391 gfx::ImageSkia* mask =
392 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
393 int height = mask->height();
394 int width = mask->width();
395 // The canvas and mask has to use the same scale factor.
396 if (!mask->HasRepresentation(scale))
397 scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
399 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
401 // For custom images the background starts at the top of the tab strip.
402 // Otherwise the background starts at the top of the frame.
403 gfx::ImageSkia* background =
404 GetThemeProvider()->GetImageSkiaNamed(background_id);
405 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
406 0 : background_offset_.y();
408 // The new tab background is mirrored in RTL mode, but the theme background
409 // should never be mirrored. Mirror it here to compensate.
410 float x_scale = 1.0f;
411 int x = GetMirroredX() + background_offset_.x();
412 if (base::i18n::IsRTL()) {
414 // Offset by |width| such that the same region is painted as if there was no
418 canvas.TileImageInt(*background, x,
419 TabStrip::kNewTabButtonVerticalOffset + offset_y,
420 x_scale, 1.0f, 0, 0, width, height);
424 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
425 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
426 paint.setStyle(SkPaint::kFill_Style);
427 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
430 // White highlight on hover.
431 if (state == views::CustomButton::STATE_HOVERED)
432 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
434 return gfx::ImageSkiaOperations::CreateMaskedImage(
435 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
438 gfx::ImageSkia NewTabButton::GetImageForState(
439 views::CustomButton::ButtonState state,
441 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
442 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
443 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
446 gfx::Size(overlay->width(), overlay->height()),
449 canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
451 // Draw the button border with a slight alpha.
452 const int kGlassFrameOverlayAlpha = 178;
453 const int kOpaqueFrameOverlayAlpha = 230;
454 uint8 alpha = ShouldWindowContentsBeTransparent() ?
455 kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha;
456 canvas.DrawImageInt(*overlay, 0, 0, alpha);
458 return gfx::ImageSkia(canvas.ExtractImageRep());
461 gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
462 if (!hover_animation_->is_animating())
463 return GetImageForState(state(), scale);
464 return gfx::ImageSkiaOperations::CreateBlendedImage(
465 GetImageForState(views::CustomButton::STATE_NORMAL, scale),
466 GetImageForState(views::CustomButton::STATE_HOVERED, scale),
467 hover_animation_->GetCurrentValue());
470 ///////////////////////////////////////////////////////////////////////////////
471 // TabStrip::RemoveTabDelegate
473 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
475 class TabStrip::RemoveTabDelegate : public TabAnimationDelegate {
477 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
479 void AnimationEnded(const gfx::Animation* animation) override;
480 void AnimationCanceled(const gfx::Animation* animation) override;
483 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
486 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
488 : TabAnimationDelegate(tab_strip, tab) {
491 void TabStrip::RemoveTabDelegate::AnimationEnded(
492 const gfx::Animation* animation) {
493 DCHECK(tab()->closing());
494 tab_strip()->RemoveAndDeleteTab(tab());
496 // Send the Container a message to simulate a mouse moved event at the current
497 // mouse position. This tickles the Tab the mouse is currently over to show
498 // the "hot" state of the close button. Note that this is not required (and
499 // indeed may crash!) for removes spawned by non-mouse closes and
501 if (!tab_strip()->IsDragSessionActive() &&
502 tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
503 // The widget can apparently be null during shutdown.
504 views::Widget* widget = tab_strip()->GetWidget();
506 widget->SynthesizeMouseMoveEvent();
510 void TabStrip::RemoveTabDelegate::AnimationCanceled(
511 const gfx::Animation* animation) {
512 AnimationEnded(animation);
515 ///////////////////////////////////////////////////////////////////////////////
519 const char TabStrip::kViewClassName[] = "TabStrip";
520 const int TabStrip::kNewTabButtonHorizontalOffset = -11;
521 const int TabStrip::kNewTabButtonVerticalOffset = 7;
522 const int TabStrip::kMiniToNonMiniGap = 3;
523 const int TabStrip::kNewTabButtonAssetWidth = 34;
524 const int TabStrip::kNewTabButtonAssetHeight = 18;
526 TabStrip::TabStrip(TabStripController* controller)
527 : controller_(controller),
528 newtab_button_(NULL),
529 current_unselected_width_(Tab::GetStandardSize().width()),
530 current_selected_width_(Tab::GetStandardSize().width()),
531 available_width_for_tabs_(-1),
532 in_tab_close_(false),
533 animation_container_(new gfx::AnimationContainer()),
534 bounds_animator_(this),
535 stacked_layout_(false),
536 adjust_layout_(false),
537 reset_to_shrink_on_exit_(false),
538 mouse_move_count_(0),
539 immersive_style_(false) {
542 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
545 TabStrip::~TabStrip() {
546 FOR_EACH_OBSERVER(TabStripObserver, observers_,
547 TabStripDeleted(this));
549 // The animations may reference the tabs. Shut down the animation before we
551 StopAnimating(false);
553 DestroyDragController();
555 // Make sure we unhook ourselves as a message loop observer so that we don't
556 // crash in the case where the user closes the window after closing a tab
557 // but before moving the mouse.
558 RemoveMessageLoopObserver();
560 // The children (tabs) may callback to us from their destructor. Delete them
561 // so that if they call back we aren't in a weird state.
562 RemoveAllChildViews(true);
565 void TabStrip::AddObserver(TabStripObserver* observer) {
566 observers_.AddObserver(observer);
569 void TabStrip::RemoveObserver(TabStripObserver* observer) {
570 observers_.RemoveObserver(observer);
573 void TabStrip::SetStackedLayout(bool stacked_layout) {
574 if (stacked_layout == stacked_layout_)
577 const int active_index = controller_->GetActiveIndex();
578 int active_center = 0;
579 if (active_index != -1) {
580 active_center = ideal_bounds(active_index).x() +
581 ideal_bounds(active_index).width() / 2;
583 stacked_layout_ = stacked_layout;
584 SetResetToShrinkOnExit(false);
585 SwapLayoutIfNecessary();
586 // When transitioning to stacked try to keep the active tab centered.
587 if (touch_layout_ && active_index != -1) {
588 touch_layout_->SetActiveTabLocation(
589 active_center - ideal_bounds(active_index).width() / 2);
590 AnimateToIdealBounds();
594 gfx::Rect TabStrip::GetNewTabButtonBounds() {
595 return newtab_button_->bounds();
598 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
599 // Extend the button to the screen edge in maximized and immersive fullscreen.
600 views::Widget* widget = GetWidget();
601 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
602 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
605 void TabStrip::StartHighlight(int model_index) {
606 tab_at(model_index)->StartPulse();
609 void TabStrip::StopAllHighlighting() {
610 for (int i = 0; i < tab_count(); ++i)
611 tab_at(i)->StopPulse();
614 void TabStrip::AddTabAt(int model_index,
615 const TabRendererData& data,
617 // Stop dragging when a new tab is added and dragging a window. Doing
618 // otherwise results in a confusing state if the user attempts to reattach. We
619 // could allow this and make TabDragController update itself during the add,
620 // but this comes up infrequently enough that it's not work the complexity.
621 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
622 drag_controller_->is_dragging_window()) {
623 EndDrag(END_DRAG_COMPLETE);
625 Tab* tab = CreateTab();
627 UpdateTabsClosingMap(model_index, 1);
628 tabs_.Add(tab, model_index);
632 GenerateIdealBoundsForMiniTabs(NULL);
635 add_types |= StackedTabStripLayout::kAddTypeMini;
637 add_types |= StackedTabStripLayout::kAddTypeActive;
638 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
641 // Don't animate the first tab, it looks weird, and don't animate anything
642 // if the containing window isn't visible yet.
643 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
644 StartInsertTabAnimation(model_index);
648 SwapLayoutIfNecessary();
650 FOR_EACH_OBSERVER(TabStripObserver, observers_,
651 TabStripAddedTabAt(this, model_index));
654 void TabStrip::MoveTab(int from_model_index,
656 const TabRendererData& data) {
657 DCHECK_GT(tabs_.view_size(), 0);
658 const Tab* last_tab = GetLastVisibleTab();
659 tab_at(from_model_index)->SetData(data);
661 tabs_.MoveViewOnly(from_model_index, to_model_index);
663 GenerateIdealBoundsForMiniTabs(&mini_count);
664 touch_layout_->MoveTab(
665 from_model_index, to_model_index, controller_->GetActiveIndex(),
666 GetStartXForNormalTabs(), mini_count);
668 tabs_.Move(from_model_index, to_model_index);
670 StartMoveTabAnimation();
671 if (TabDragController::IsAttachedTo(this) &&
672 (last_tab != GetLastVisibleTab() || last_tab->dragging())) {
673 newtab_button_->SetVisible(false);
675 SwapLayoutIfNecessary();
677 FOR_EACH_OBSERVER(TabStripObserver, observers_,
678 TabStripMovedTab(this, from_model_index, to_model_index));
681 void TabStrip::RemoveTabAt(int model_index) {
683 Tab* tab = tab_at(model_index);
684 tab->set_closing(true);
685 int old_x = tabs_.ideal_bounds(model_index).x();
686 // We still need to paint the tab until we actually remove it. Put it in
687 // tabs_closing_map_ so we can find it.
688 RemoveTabFromViewModel(model_index);
689 touch_layout_->RemoveTab(model_index, GenerateIdealBoundsForMiniTabs(NULL),
691 ScheduleRemoveTabAnimation(tab);
692 } else if (in_tab_close_ && model_index != GetModelCount()) {
693 StartMouseInitiatedRemoveTabAnimation(model_index);
695 StartRemoveTabAnimation(model_index);
697 SwapLayoutIfNecessary();
699 FOR_EACH_OBSERVER(TabStripObserver, observers_,
700 TabStripRemovedTabAt(this, model_index));
703 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
704 Tab* tab = tab_at(model_index);
705 bool mini_state_changed = tab->data().mini != data.mini;
708 if (mini_state_changed) {
710 int mini_tab_count = 0;
711 int start_x = GenerateIdealBoundsForMiniTabs(&mini_tab_count);
712 touch_layout_->SetXAndMiniCount(start_x, mini_tab_count);
714 if (GetWidget() && GetWidget()->IsVisible())
715 StartMiniTabAnimation();
719 SwapLayoutIfNecessary();
722 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
723 // Detached tabs should always be invisible (as they close).
727 // When stacking tabs, all tabs should always be visible.
731 // If the tab is currently clipped, it shouldn't be visible. Note that we
732 // allow dragged tabs to draw over the "New Tab button" region as well,
733 // because either the New Tab button will be hidden, or the dragged tabs will
734 // be animating back to their normal positions and we don't want to hide them
735 // in the New Tab button region in case they re-appear after leaving it.
736 // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab
737 // button area, even when the button is invisible, so that they don't appear
738 // to "pop in" when the button disappears.
739 // TODO: Probably doesn't work for RTL
740 int right_edge = tab->bounds().right();
741 const int visible_width = tab->dragging() ? width() : tab_area_width();
742 if (right_edge > visible_width)
745 // Non-clipped dragging tabs should always be visible.
749 // Let all non-clipped closing tabs be visible. These will probably finish
750 // closing before the user changes the active tab, so there's little reason to
751 // try and make the more complex logic below apply.
755 // Now we need to check whether the tab isn't currently clipped, but could
756 // become clipped if we changed the active tab, widening either this tab or
757 // the tabstrip portion before it.
759 // Mini tabs don't change size when activated, so any tab in the mini tab
761 if (tab->data().mini)
764 // If the active tab is on or before this tab, we're safe.
765 if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab))
768 // We need to check what would happen if the active tab were to move to this
770 return (right_edge + current_selected_width_ - current_unselected_width_) <=
774 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
775 if (!in_tab_close_ && IsAnimating()) {
776 // Cancel any current animations. We do this as remove uses the current
777 // ideal bounds and we need to know ideal bounds is in a good state.
784 int model_count = GetModelCount();
785 if (model_count > 1 && model_index != model_count - 1) {
786 // The user is about to close a tab other than the last tab. Set
787 // available_width_for_tabs_ so that if we do a layout we don't position a
788 // tab past the end of the second to last tab. We do this so that as the
789 // user closes tabs with the mouse a tab continues to fall under the mouse.
790 Tab* last_tab = tab_at(model_count - 1);
791 Tab* tab_being_removed = tab_at(model_index);
792 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
793 tab_being_removed->width() - kTabHorizontalOffset;
794 if (model_index == 0 && tab_being_removed->data().mini &&
795 !tab_at(1)->data().mini) {
796 available_width_for_tabs_ -= kMiniToNonMiniGap;
800 in_tab_close_ = true;
801 resize_layout_timer_.Stop();
802 if (source == CLOSE_TAB_FROM_TOUCH) {
803 StartResizeLayoutTabsFromTouchTimer();
805 AddMessageLoopObserver();
809 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
810 const ui::ListSelectionModel& new_selection) {
812 touch_layout_->SetActiveIndex(new_selection.active());
813 // Only start an animation if we need to. Otherwise clicking on an
814 // unselected tab and dragging won't work because dragging is only allowed
816 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
817 AnimateToIdealBounds();
820 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
821 // a different size to the selected ones.
822 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
823 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
830 // Use STLSetDifference to get the indices of elements newly selected
831 // and no longer selected, since selected_indices() is always sorted.
832 ui::ListSelectionModel::SelectedIndices no_longer_selected =
833 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
834 old_selection.selected_indices(),
835 new_selection.selected_indices());
836 ui::ListSelectionModel::SelectedIndices newly_selected =
837 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
838 new_selection.selected_indices(),
839 old_selection.selected_indices());
841 // Fire accessibility events that reflect the changes to selection, and
842 // stop the mini tab title animation on tabs no longer selected.
843 for (size_t i = 0; i < no_longer_selected.size(); ++i) {
844 tab_at(no_longer_selected[i])->StopMiniTabTitleAnimation();
845 tab_at(no_longer_selected[i])->NotifyAccessibilityEvent(
846 ui::AX_EVENT_SELECTION_REMOVE, true);
848 for (size_t i = 0; i < newly_selected.size(); ++i) {
849 tab_at(newly_selected[i])->NotifyAccessibilityEvent(
850 ui::AX_EVENT_SELECTION_ADD, true);
852 tab_at(new_selection.active())->NotifyAccessibilityEvent(
853 ui::AX_EVENT_SELECTION, true);
856 void TabStrip::TabTitleChangedNotLoading(int model_index) {
857 Tab* tab = tab_at(model_index);
858 if (tab->data().mini && !tab->IsActive())
859 tab->StartMiniTabTitleAnimation();
862 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
863 return tabs_.GetIndexOfView(tab);
866 int TabStrip::GetModelCount() const {
867 return controller_->GetCount();
870 bool TabStrip::IsValidModelIndex(int model_index) const {
871 return controller_->IsValidIndex(model_index);
874 bool TabStrip::IsDragSessionActive() const {
875 return drag_controller_.get() != NULL;
878 bool TabStrip::IsActiveDropTarget() const {
879 for (int i = 0; i < tab_count(); ++i) {
880 Tab* tab = tab_at(i);
887 bool TabStrip::IsTabStripEditable() const {
888 return !IsDragSessionActive() && !IsActiveDropTarget();
891 bool TabStrip::IsTabStripCloseable() const {
892 return !IsDragSessionActive();
895 void TabStrip::UpdateLoadingAnimations() {
896 controller_->UpdateLoadingAnimations();
899 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
900 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
903 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
904 views::View* v = GetEventHandlerForRect(rect);
906 // If there is no control at this location, claim the hit was in the title
907 // bar to get a move action.
911 // Check to see if the rect intersects the non-button parts of the new tab
912 // button. The button has a non-rectangular shape, so if it's not in the
913 // visual portions of the button we treat it as a click to the caption.
914 gfx::RectF rect_in_newtab_coords_f(rect);
915 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
916 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
917 rect_in_newtab_coords_f);
918 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
919 !newtab_button_->HitTestRect(rect_in_newtab_coords))
922 // All other regions, including the new Tab button, should be considered part
923 // of the containing Window's client area so that regular events can be
924 // processed for them.
928 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
929 for (int i = 0; i < tab_count(); ++i)
930 tab_at(i)->set_background_offset(offset);
931 newtab_button_->set_background_offset(offset);
934 void TabStrip::SetImmersiveStyle(bool enable) {
935 if (immersive_style_ == enable)
937 immersive_style_ = enable;
940 bool TabStrip::IsAnimating() const {
941 return bounds_animator_.IsAnimating();
944 void TabStrip::StopAnimating(bool layout) {
948 bounds_animator_.Cancel();
954 void TabStrip::FileSupported(const GURL& url, bool supported) {
955 if (drop_info_.get() && drop_info_->url == url)
956 drop_info_->file_supported = supported;
959 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
960 return controller_->GetSelectionModel();
963 bool TabStrip::SupportsMultipleSelection() {
964 // TODO: currently only allow single selection in touch layout mode.
965 return touch_layout_ == NULL;
968 void TabStrip::SelectTab(Tab* tab) {
969 int model_index = GetModelIndexOfTab(tab);
970 if (IsValidModelIndex(model_index))
971 controller_->SelectTab(model_index);
974 void TabStrip::ExtendSelectionTo(Tab* tab) {
975 int model_index = GetModelIndexOfTab(tab);
976 if (IsValidModelIndex(model_index))
977 controller_->ExtendSelectionTo(model_index);
980 void TabStrip::ToggleSelected(Tab* tab) {
981 int model_index = GetModelIndexOfTab(tab);
982 if (IsValidModelIndex(model_index))
983 controller_->ToggleSelected(model_index);
986 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
987 int model_index = GetModelIndexOfTab(tab);
988 if (IsValidModelIndex(model_index))
989 controller_->AddSelectionFromAnchorTo(model_index);
992 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
993 if (tab->closing()) {
994 // If the tab is already closing, close the next tab. We do this so that the
995 // user can rapidly close tabs by clicking the close button and not have
996 // the animations interfere with that.
997 const int closed_tab_index = FindClosingTab(tab).first->first;
998 if (closed_tab_index < GetModelCount())
999 controller_->CloseTab(closed_tab_index, source);
1002 int model_index = GetModelIndexOfTab(tab);
1003 if (IsValidModelIndex(model_index))
1004 controller_->CloseTab(model_index, source);
1007 void TabStrip::ToggleTabAudioMute(Tab* tab) {
1008 int model_index = GetModelIndexOfTab(tab);
1009 if (IsValidModelIndex(model_index))
1010 controller_->ToggleTabAudioMute(model_index);
1013 void TabStrip::ShowContextMenuForTab(Tab* tab,
1014 const gfx::Point& p,
1015 ui::MenuSourceType source_type) {
1016 controller_->ShowContextMenuForTab(tab, p, source_type);
1019 bool TabStrip::IsActiveTab(const Tab* tab) const {
1020 int model_index = GetModelIndexOfTab(tab);
1021 return IsValidModelIndex(model_index) &&
1022 controller_->IsActiveTab(model_index);
1025 bool TabStrip::IsTabSelected(const Tab* tab) const {
1026 int model_index = GetModelIndexOfTab(tab);
1027 return IsValidModelIndex(model_index) &&
1028 controller_->IsTabSelected(model_index);
1031 bool TabStrip::IsTabPinned(const Tab* tab) const {
1035 int model_index = GetModelIndexOfTab(tab);
1036 return IsValidModelIndex(model_index) &&
1037 controller_->IsTabPinned(model_index);
1040 void TabStrip::MaybeStartDrag(
1042 const ui::LocatedEvent& event,
1043 const ui::ListSelectionModel& original_selection) {
1044 // Don't accidentally start any drag operations during animations if the
1045 // mouse is down... during an animation tabs are being resized automatically,
1046 // so the View system can misinterpret this easily if the mouse is down that
1047 // the user is dragging.
1048 if (IsAnimating() || tab->closing() ||
1049 controller_->HasAvailableDragActions() == 0) {
1053 // Do not do any dragging of tabs when using the super short immersive style.
1054 if (IsImmersiveStyle())
1057 int model_index = GetModelIndexOfTab(tab);
1058 if (!IsValidModelIndex(model_index)) {
1063 int size_to_selected = 0;
1064 int x = tab->GetMirroredXInView(event.x());
1066 // Build the set of selected tabs to drag and calculate the offset from the
1067 // first selected tab.
1068 for (int i = 0; i < tab_count(); ++i) {
1069 Tab* other_tab = tab_at(i);
1070 if (IsTabSelected(other_tab)) {
1071 tabs.push_back(other_tab);
1072 if (other_tab == tab) {
1073 size_to_selected = GetSizeNeededForTabs(tabs);
1074 x = size_to_selected - tab->width() + x;
1078 DCHECK(!tabs.empty());
1079 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1080 ui::ListSelectionModel selection_model;
1081 if (!original_selection.IsSelected(model_index))
1082 selection_model.Copy(original_selection);
1083 // Delete the existing DragController before creating a new one. We do this as
1084 // creating the DragController remembers the WebContents delegates and we need
1085 // to make sure the existing DragController isn't still a delegate.
1086 drag_controller_.reset();
1087 TabDragController::MoveBehavior move_behavior =
1088 TabDragController::REORDER;
1089 // Use MOVE_VISIBILE_TABS in the following conditions:
1090 // . Mouse event generated from touch and the left button is down (the right
1091 // button corresponds to a long press, which we want to reorder).
1092 // . Gesture tap down and control key isn't down.
1093 // . Real mouse event and control is down. This is mostly for testing.
1094 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1095 event.type() == ui::ET_GESTURE_TAP_DOWN);
1096 if (touch_layout_ &&
1097 ((event.type() == ui::ET_MOUSE_PRESSED &&
1098 (((event.flags() & ui::EF_FROM_TOUCH) &&
1099 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1100 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1101 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1102 (event.type() == ui::ET_GESTURE_TAP_DOWN && !event.IsControlDown()))) {
1103 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1106 drag_controller_.reset(new TabDragController);
1107 drag_controller_->Init(
1108 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1109 move_behavior, EventSourceFromEvent(event));
1112 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1113 if (drag_controller_.get() &&
1114 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1115 gfx::Point screen_location(event.location());
1116 views::View::ConvertPointToScreen(view, &screen_location);
1117 drag_controller_->Drag(screen_location);
1121 bool TabStrip::EndDrag(EndDragReason reason) {
1122 if (!drag_controller_.get())
1124 bool started_drag = drag_controller_->started_drag();
1125 drag_controller_->EndDrag(reason);
1126 return started_drag;
1129 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1130 gfx::Point local_point = tab_in_tab_coordinates;
1131 ConvertPointToTarget(tab, this, &local_point);
1133 views::View* view = GetEventHandlerForPoint(local_point);
1135 return NULL; // No tab contains the point.
1137 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1138 while (view && view != this && view->id() != VIEW_ID_TAB)
1139 view = view->parent();
1141 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1144 void TabStrip::OnMouseEventInTab(views::View* source,
1145 const ui::MouseEvent& event) {
1146 UpdateStackedLayoutFromMouseEvent(source, event);
1149 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1150 // Only touch layout needs to restrict the clip.
1151 if (!touch_layout_ && !IsStackingDraggedTabs())
1154 int index = GetModelIndexOfTab(tab);
1156 return true; // Tab is closing, paint it all.
1158 int active_index = IsStackingDraggedTabs() ?
1159 controller_->GetActiveIndex() : touch_layout_->active_index();
1160 if (active_index == tab_count())
1163 if (index < active_index) {
1164 if (tab_at(index)->x() == tab_at(index + 1)->x())
1167 if (tab_at(index)->x() > tab_at(index + 1)->x())
1168 return true; // Can happen during dragging.
1171 0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + kStackedTabLeftClip,
1172 tab_at(index)->height());
1173 } else if (index > active_index && index > 0) {
1174 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1175 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1176 if (tab_bounds.x() == previous_tab_bounds.x())
1179 if (tab_bounds.x() < previous_tab_bounds.x())
1180 return true; // Can happen during dragging.
1182 if (previous_tab_bounds.right() + kTabHorizontalOffset != tab_bounds.x()) {
1183 int x = previous_tab_bounds.right() - tab_bounds.x() -
1184 kStackedTabRightClip;
1185 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1191 bool TabStrip::IsImmersiveStyle() const {
1192 return immersive_style_;
1195 void TabStrip::UpdateTabAccessibilityState(const Tab* tab,
1196 ui::AXViewState* state) {
1197 state->count = tab_count();
1198 state->index = GetModelIndexOfTab(tab);
1201 void TabStrip::MouseMovedOutOfHost() {
1203 if (reset_to_shrink_on_exit_) {
1204 reset_to_shrink_on_exit_ = false;
1205 SetStackedLayout(false);
1206 controller_->StackedLayoutMaybeChanged();
1210 ///////////////////////////////////////////////////////////////////////////////
1211 // TabStrip, views::View overrides:
1213 void TabStrip::Layout() {
1214 // Only do a layout if our size changed.
1215 if (last_layout_size_ == size())
1217 if (IsDragSessionActive())
1222 void TabStrip::PaintChildren(gfx::Canvas* canvas,
1223 const views::CullSet& cull_set) {
1224 // The view order doesn't match the paint order (tabs_ contains the tab
1225 // ordering). Additionally we need to paint the tabs that are closing in
1226 // |tabs_closing_map_|.
1227 Tab* active_tab = NULL;
1230 int selected_tab_count = 0;
1231 bool is_dragging = false;
1232 int active_tab_index = -1;
1234 const chrome::HostDesktopType host_desktop_type =
1235 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1236 const int inactive_tab_alpha =
1237 (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) ?
1238 kInactiveTabAndNewTabButtonAlphaAsh : kInactiveTabAndNewTabButtonAlpha;
1240 if (inactive_tab_alpha < 255)
1241 canvas->SaveLayerAlpha(inactive_tab_alpha);
1243 PaintClosingTabs(canvas, tab_count(), cull_set);
1245 for (int i = tab_count() - 1; i >= 0; --i) {
1246 Tab* tab = tab_at(i);
1247 if (tab->IsSelected())
1248 selected_tab_count++;
1249 if (tab->dragging() && !stacked_layout_) {
1251 if (tab->IsActive()) {
1253 active_tab_index = i;
1255 tabs_dragging.push_back(tab);
1257 } else if (!tab->IsActive()) {
1258 if (!tab->IsSelected()) {
1259 if (!stacked_layout_)
1260 tab->Paint(canvas, cull_set);
1262 selected_tabs.push_back(tab);
1266 active_tab_index = i;
1268 PaintClosingTabs(canvas, i, cull_set);
1271 // Draw from the left and then the right if we're in touch mode.
1272 if (stacked_layout_ && active_tab_index >= 0) {
1273 for (int i = 0; i < active_tab_index; ++i) {
1274 Tab* tab = tab_at(i);
1275 tab->Paint(canvas, cull_set);
1278 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1279 Tab* tab = tab_at(i);
1280 tab->Paint(canvas, cull_set);
1283 if (inactive_tab_alpha < 255)
1286 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1287 // Make sure non-active tabs are somewhat transparent.
1289 // If there are multiple tabs selected, fade non-selected tabs more to make
1290 // the selected tabs more noticable.
1291 int alpha = selected_tab_count > 1 ?
1292 kGlassFrameInactiveTabAlphaMultiSelection :
1293 kGlassFrameInactiveTabAlpha;
1294 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1295 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1296 paint.setStyle(SkPaint::kFill_Style);
1297 // The tabstrip area overlaps the toolbar area by 2 px.
1298 canvas->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint);
1301 // Now selected but not active. We don't want these dimmed if using native
1302 // frame, so they're painted after initial pass.
1303 for (size_t i = 0; i < selected_tabs.size(); ++i)
1304 selected_tabs[i]->Paint(canvas, cull_set);
1306 // Next comes the active tab.
1307 if (active_tab && !is_dragging)
1308 active_tab->Paint(canvas, cull_set);
1310 // Paint the New Tab button.
1311 if (inactive_tab_alpha < 255)
1312 canvas->SaveLayerAlpha(inactive_tab_alpha);
1313 newtab_button_->Paint(canvas, cull_set);
1314 if (inactive_tab_alpha < 255)
1317 // And the dragged tabs.
1318 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1319 tabs_dragging[i]->Paint(canvas, cull_set);
1321 // If the active tab is being dragged, it goes last.
1322 if (active_tab && is_dragging)
1323 active_tab->Paint(canvas, cull_set);
1326 const char* TabStrip::GetClassName() const {
1327 return kViewClassName;
1330 gfx::Size TabStrip::GetPreferredSize() const {
1331 int needed_tab_width;
1332 if (touch_layout_ || adjust_layout_) {
1333 // For stacked tabs the minimum size is calculated as the size needed to
1334 // handle showing any number of tabs.
1336 Tab::GetTouchWidth() + (2 * kStackedPadding * kMaxStackedCount);
1338 // Otherwise the minimum width is based on the actual number of tabs.
1339 const int mini_tab_count = GetMiniTabCount();
1340 needed_tab_width = mini_tab_count * Tab::GetMiniWidth();
1341 const int remaining_tab_count = tab_count() - mini_tab_count;
1342 const int min_selected_width = Tab::GetMinimumSelectedSize().width();
1343 const int min_unselected_width = Tab::GetMinimumUnselectedSize().width();
1344 if (remaining_tab_count > 0) {
1345 needed_tab_width += kMiniToNonMiniGap + min_selected_width +
1346 ((remaining_tab_count - 1) * min_unselected_width);
1348 if (tab_count() > 1)
1349 needed_tab_width += (tab_count() - 1) * kTabHorizontalOffset;
1351 // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1352 // and don't force it to be larger than is necessary to show 20 tabs.
1353 const int largest_min_tab_width =
1354 min_selected_width + 19 * (min_unselected_width + kTabHorizontalOffset);
1355 needed_tab_width = std::min(
1356 std::max(needed_tab_width, min_selected_width), largest_min_tab_width);
1359 needed_tab_width + new_tab_button_width(),
1361 Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1364 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1365 // Force animations to stop, otherwise it makes the index calculation tricky.
1366 StopAnimating(true);
1368 UpdateDropIndex(event);
1371 base::string16 title;
1373 // Check whether the event data includes supported drop data.
1374 if (event.data().GetURLAndTitle(
1375 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1377 drop_info_->url = url;
1379 // For file:// URLs, kick off a MIME type request in case they're dropped.
1380 if (url.SchemeIsFile())
1381 controller()->CheckFileSupported(url);
1385 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1386 // Update the drop index even if the file is unsupported, to allow
1387 // dragging a file to the contents of another tab.
1388 UpdateDropIndex(event);
1390 if (!drop_info_->file_supported)
1391 return ui::DragDropTypes::DRAG_NONE;
1393 return GetDropEffect(event);
1396 void TabStrip::OnDragExited() {
1397 SetDropIndex(-1, false);
1400 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1401 if (!drop_info_.get())
1402 return ui::DragDropTypes::DRAG_NONE;
1404 const int drop_index = drop_info_->drop_index;
1405 const bool drop_before = drop_info_->drop_before;
1406 const bool file_supported = drop_info_->file_supported;
1408 // Hide the drop indicator.
1409 SetDropIndex(-1, false);
1411 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1412 // have been changed after |drop_info_| was created.
1414 base::string16 title;
1415 if (!file_supported ||
1416 !event.data().GetURLAndTitle(
1417 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1419 return ui::DragDropTypes::DRAG_NONE;
1421 controller()->PerformDrop(drop_before, drop_index, url);
1423 return GetDropEffect(event);
1426 void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1427 state->role = ui::AX_ROLE_TAB_LIST;
1430 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1431 if (!HitTestPoint(point))
1434 if (!touch_layout_) {
1435 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1436 // want to interfere.
1437 views::View* v = View::GetTooltipHandlerForPoint(point);
1438 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1441 views::View* tab = FindTabHitByPoint(point);
1445 if (newtab_button_->visible()) {
1447 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1451 Tab* tab = FindTabForEvent(point);
1453 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1459 int TabStrip::GetImmersiveHeight() {
1460 return Tab::GetImmersiveHeight();
1463 ///////////////////////////////////////////////////////////////////////////////
1464 // TabStrip, private:
1466 void TabStrip::Init() {
1467 set_id(VIEW_ID_TAB_STRIP);
1468 // So we get enter/exit on children to switch stacked layout on and off.
1469 set_notify_enter_exit_on_child(true);
1470 newtab_button_bounds_.SetRect(0,
1472 kNewTabButtonAssetWidth,
1473 kNewTabButtonAssetHeight +
1474 kNewTabButtonVerticalOffset);
1475 newtab_button_ = new NewTabButton(this, this);
1476 newtab_button_->SetTooltipText(
1477 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1478 newtab_button_->SetAccessibleName(
1479 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1480 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1481 views::ImageButton::ALIGN_BOTTOM);
1482 newtab_button_->SetEventTargeter(
1483 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(newtab_button_)));
1484 AddChildView(newtab_button_);
1486 if (drop_indicator_width == 0) {
1487 // Direction doesn't matter, both images are the same size.
1488 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1489 drop_indicator_width = drop_image->width();
1490 drop_indicator_height = drop_image->height();
1494 Tab* TabStrip::CreateTab() {
1495 Tab* tab = new Tab(this);
1496 tab->set_animation_container(animation_container_.get());
1500 void TabStrip::StartInsertTabAnimation(int model_index) {
1501 PrepareForAnimation();
1503 // The TabStrip can now use its entire width to lay out Tabs.
1504 in_tab_close_ = false;
1505 available_width_for_tabs_ = -1;
1507 GenerateIdealBounds();
1509 Tab* tab = tab_at(model_index);
1510 if (model_index == 0) {
1511 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1512 ideal_bounds(model_index).height());
1514 Tab* last_tab = tab_at(model_index - 1);
1515 tab->SetBounds(last_tab->bounds().right() + kTabHorizontalOffset,
1516 ideal_bounds(model_index).y(), 0,
1517 ideal_bounds(model_index).height());
1520 AnimateToIdealBounds();
1523 void TabStrip::StartMoveTabAnimation() {
1524 PrepareForAnimation();
1525 GenerateIdealBounds();
1526 AnimateToIdealBounds();
1529 void TabStrip::StartRemoveTabAnimation(int model_index) {
1530 PrepareForAnimation();
1532 // Mark the tab as closing.
1533 Tab* tab = tab_at(model_index);
1534 tab->set_closing(true);
1536 RemoveTabFromViewModel(model_index);
1538 ScheduleRemoveTabAnimation(tab);
1541 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1542 // Start an animation for the tabs.
1543 GenerateIdealBounds();
1544 AnimateToIdealBounds();
1546 // Animate the tab being closed to zero width.
1547 gfx::Rect tab_bounds = tab->bounds();
1548 tab_bounds.set_width(0);
1549 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1550 bounds_animator_.SetAnimationDelegate(
1552 scoped_ptr<gfx::AnimationDelegate>(new RemoveTabDelegate(this, tab)));
1554 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1555 // like the new tab button magically appears from beyond the end of the tab
1557 if (TabDragController::IsAttachedTo(this)) {
1558 bounds_animator_.StopAnimatingView(newtab_button_);
1559 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1563 void TabStrip::AnimateToIdealBounds() {
1564 for (int i = 0; i < tab_count(); ++i) {
1565 Tab* tab = tab_at(i);
1566 if (!tab->dragging()) {
1567 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1568 bounds_animator_.SetAnimationDelegate(
1570 scoped_ptr<gfx::AnimationDelegate>(
1571 new TabAnimationDelegate(this, tab)));
1575 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1578 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1579 return in_tab_close_;
1582 void TabStrip::DoLayout() {
1583 last_layout_size_ = size();
1585 StopAnimating(false);
1587 SwapLayoutIfNecessary();
1590 touch_layout_->SetWidth(tab_area_width());
1592 GenerateIdealBounds();
1594 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1599 bounds_animator_.StopAnimatingView(newtab_button_);
1600 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1603 void TabStrip::SetTabVisibility() {
1604 // We could probably be more efficient here by making use of the fact that the
1605 // tabstrip will always have any visible tabs, and then any invisible tabs, so
1606 // we could e.g. binary-search for the changeover point. But since we have to
1607 // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1609 for (int i = 0; i < tab_count(); ++i) {
1610 Tab* tab = tab_at(i);
1611 tab->SetVisible(ShouldTabBeVisible(tab));
1613 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1614 i != tabs_closing_map_.end(); ++i) {
1615 for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) {
1617 tab->SetVisible(ShouldTabBeVisible(tab));
1622 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1624 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1625 if (!touch_layout_) {
1626 StackDraggedTabs(delta);
1629 SetIdealBoundsFromPositions(initial_positions);
1630 touch_layout_->DragActiveTab(delta);
1634 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1635 if (static_cast<size_t>(tab_count()) != positions.size())
1638 for (int i = 0; i < tab_count(); ++i) {
1639 gfx::Rect bounds(ideal_bounds(i));
1640 bounds.set_x(positions[i]);
1641 tabs_.set_ideal_bounds(i, bounds);
1645 void TabStrip::StackDraggedTabs(int delta) {
1646 DCHECK(!touch_layout_);
1647 GenerateIdealBounds();
1648 const int active_index = controller_->GetActiveIndex();
1649 DCHECK_NE(-1, active_index);
1651 // Drag the tabs to the left, stacking tabs before the active tab.
1652 const int adjusted_delta =
1653 std::min(ideal_bounds(active_index).x() -
1654 kStackedPadding * std::min(active_index, kMaxStackedCount),
1656 for (int i = 0; i <= active_index; ++i) {
1657 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1658 gfx::Rect new_bounds(ideal_bounds(i));
1659 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1660 tabs_.set_ideal_bounds(i, new_bounds);
1662 const bool is_active_mini = tab_at(active_index)->data().mini;
1663 const int active_width = ideal_bounds(active_index).width();
1664 for (int i = active_index + 1; i < tab_count(); ++i) {
1665 const int max_x = ideal_bounds(active_index).x() +
1666 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1667 gfx::Rect new_bounds(ideal_bounds(i));
1668 int new_x = std::max(new_bounds.x() + delta, max_x);
1669 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini &&
1670 new_bounds.width() != active_width)
1671 new_x += (active_width - new_bounds.width());
1672 new_bounds.set_x(new_x);
1673 tabs_.set_ideal_bounds(i, new_bounds);
1676 // Drag the tabs to the right, stacking tabs after the active tab.
1677 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1678 const int last_tab_x = tab_area_width() - last_tab_width;
1679 if (active_index == tab_count() - 1 &&
1680 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1682 const int adjusted_delta =
1683 std::min(last_tab_x -
1684 kStackedPadding * std::min(tab_count() - active_index - 1,
1686 ideal_bounds(active_index).x(),
1688 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1690 const int max_x = last_tab_x -
1691 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1692 gfx::Rect new_bounds(ideal_bounds(i));
1693 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1694 // Because of rounding not all tabs are the same width. Adjust the
1695 // position to accommodate this, otherwise the stacking is off.
1696 if (new_x == max_x && !tab_at(i)->data().mini &&
1697 new_bounds.width() != last_tab_width)
1698 new_x += (last_tab_width - new_bounds.width());
1699 new_bounds.set_x(new_x);
1700 tabs_.set_ideal_bounds(i, new_bounds);
1702 for (int i = active_index - 1; i >= 0; --i) {
1703 const int min_x = ideal_bounds(active_index).x() -
1704 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1705 gfx::Rect new_bounds(ideal_bounds(i));
1706 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1707 tabs_.set_ideal_bounds(i, new_bounds);
1709 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1710 newtab_button_->SetVisible(false);
1712 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1716 bool TabStrip::IsStackingDraggedTabs() const {
1717 return drag_controller_.get() && drag_controller_->started_drag() &&
1718 (drag_controller_->move_behavior() ==
1719 TabDragController::MOVE_VISIBILE_TABS);
1722 void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs,
1724 const gfx::Point& location,
1725 bool initial_drag) {
1726 // Immediately hide the new tab button if the last tab is being dragged.
1727 const Tab* last_visible_tab = GetLastVisibleTab();
1728 if (last_visible_tab && last_visible_tab->dragging())
1729 newtab_button_->SetVisible(false);
1730 std::vector<gfx::Rect> bounds;
1731 CalculateBoundsForDraggedTabs(tabs, &bounds);
1732 DCHECK_EQ(tabs.size(), bounds.size());
1733 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1734 int active_tab_index = static_cast<int>(
1735 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1736 for (size_t i = 0; i < tabs.size(); ++i) {
1738 gfx::Rect new_bounds = bounds[i];
1739 new_bounds.Offset(location.x(), location.y());
1740 int consecutive_index =
1741 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1742 // If this is the initial layout during a drag and the tabs aren't
1743 // consecutive animate the view into position. Do the same if the tab is
1744 // already animating (which means we previously caused it to animate).
1745 if ((initial_drag &&
1746 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1747 bounds_animator_.IsAnimating(tabs[i])) {
1748 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1750 tab->SetBoundsRect(new_bounds);
1756 void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs,
1757 std::vector<gfx::Rect>* bounds) {
1759 for (size_t i = 0; i < tabs.size(); ++i) {
1761 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1762 x += kMiniToNonMiniGap;
1763 gfx::Rect new_bounds = tab->bounds();
1764 new_bounds.set_origin(gfx::Point(x, 0));
1765 bounds->push_back(new_bounds);
1766 x += tab->width() + kTabHorizontalOffset;
1770 int TabStrip::GetSizeNeededForTabs(const Tabs& tabs) {
1772 for (size_t i = 0; i < tabs.size(); ++i) {
1774 width += tab->width();
1775 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1776 width += kMiniToNonMiniGap;
1778 if (tabs.size() > 0)
1779 width += kTabHorizontalOffset * static_cast<int>(tabs.size() - 1);
1783 int TabStrip::GetMiniTabCount() const {
1785 while (mini_count < tab_count() && tab_at(mini_count)->data().mini)
1790 const Tab* TabStrip::GetLastVisibleTab() const {
1791 for (int i = tab_count() - 1; i >= 0; --i) {
1792 const Tab* tab = tab_at(i);
1796 // While in normal use the tabstrip should always be wide enough to have at
1797 // least one visible tab, it can be zero-width in tests, meaning we get here.
1801 void TabStrip::RemoveTabFromViewModel(int index) {
1802 // We still need to paint the tab until we actually remove it. Put it
1803 // in tabs_closing_map_ so we can find it.
1804 tabs_closing_map_[index].push_back(tab_at(index));
1805 UpdateTabsClosingMap(index + 1, -1);
1806 tabs_.Remove(index);
1809 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1810 scoped_ptr<Tab> deleter(tab);
1811 FindClosingTabResult res(FindClosingTab(tab));
1812 res.first->second.erase(res.second);
1813 if (res.first->second.empty())
1814 tabs_closing_map_.erase(res.first);
1817 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1818 if (tabs_closing_map_.empty())
1822 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1823 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1824 const Tabs& tabs(tabs_closing_map_[index]);
1825 tabs_closing_map_[index - 1].insert(
1826 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1828 TabsClosingMap updated_map;
1829 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1830 i != tabs_closing_map_.end(); ++i) {
1831 if (i->first > index)
1832 updated_map[i->first + delta] = i->second;
1833 else if (i->first < index)
1834 updated_map[i->first] = i->second;
1836 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1837 updated_map[index + delta] = tabs_closing_map_[index];
1838 tabs_closing_map_.swap(updated_map);
1841 void TabStrip::StartedDraggingTabs(const Tabs& tabs) {
1842 // Let the controller know that the user started dragging tabs.
1843 controller()->OnStartedDraggingTabs();
1845 // Hide the new tab button immediately if we didn't originate the drag.
1846 if (!drag_controller_.get())
1847 newtab_button_->SetVisible(false);
1849 PrepareForAnimation();
1851 // Reset dragging state of existing tabs.
1852 for (int i = 0; i < tab_count(); ++i)
1853 tab_at(i)->set_dragging(false);
1855 for (size_t i = 0; i < tabs.size(); ++i) {
1856 tabs[i]->set_dragging(true);
1857 bounds_animator_.StopAnimatingView(tabs[i]);
1860 // Move the dragged tabs to their ideal bounds.
1861 GenerateIdealBounds();
1863 // Sets the bounds of the dragged tabs.
1864 for (size_t i = 0; i < tabs.size(); ++i) {
1865 int tab_data_index = GetModelIndexOfTab(tabs[i]);
1866 DCHECK_NE(-1, tab_data_index);
1867 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1873 void TabStrip::DraggedTabsDetached() {
1874 // Let the controller know that the user is not dragging this tabstrip's tabs
1876 controller()->OnStoppedDraggingTabs();
1877 newtab_button_->SetVisible(true);
1880 void TabStrip::StoppedDraggingTabs(const Tabs& tabs,
1881 const std::vector<int>& initial_positions,
1884 // Let the controller know that the user stopped dragging tabs.
1885 controller()->OnStoppedDraggingTabs();
1887 newtab_button_->SetVisible(true);
1888 if (move_only && touch_layout_) {
1890 touch_layout_->SizeToFit();
1892 SetIdealBoundsFromPositions(initial_positions);
1894 bool is_first_tab = true;
1895 for (size_t i = 0; i < tabs.size(); ++i)
1896 StoppedDraggingTab(tabs[i], &is_first_tab);
1899 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1900 int tab_data_index = GetModelIndexOfTab(tab);
1901 if (tab_data_index == -1) {
1902 // The tab was removed before the drag completed. Don't do anything.
1906 if (*is_first_tab) {
1907 *is_first_tab = false;
1908 PrepareForAnimation();
1910 // Animate the view back to its correct position.
1911 GenerateIdealBounds();
1912 AnimateToIdealBounds();
1914 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1915 // Install a delegate to reset the dragging state when done. We have to leave
1916 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1917 bounds_animator_.SetAnimationDelegate(
1919 scoped_ptr<gfx::AnimationDelegate>(
1920 new ResetDraggingStateDelegate(this, tab)));
1923 void TabStrip::OwnDragController(TabDragController* controller) {
1924 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1925 // via corresponding calls to TabDragController::Detach() and
1926 // TabDragController::Attach(). There is one exception to that rule: when a
1927 // drag might start, we create a TabDragController that is owned by the
1928 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1929 // we then call Attach() on the source tabstrip, but since the source tabstrip
1930 // already owns the TabDragController, so we don't need to do anything.
1931 if (controller != drag_controller_.get())
1932 drag_controller_.reset(controller);
1935 void TabStrip::DestroyDragController() {
1936 newtab_button_->SetVisible(true);
1937 drag_controller_.reset();
1940 TabDragController* TabStrip::ReleaseDragController() {
1941 return drag_controller_.release();
1944 TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
1945 DCHECK(tab->closing());
1946 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1947 i != tabs_closing_map_.end(); ++i) {
1948 Tabs::iterator j = std::find(i->second.begin(), i->second.end(), tab);
1949 if (j != i->second.end())
1950 return FindClosingTabResult(i, j);
1953 return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
1956 void TabStrip::PaintClosingTabs(gfx::Canvas* canvas,
1958 const views::CullSet& cull_set) {
1959 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
1962 const Tabs& tabs = tabs_closing_map_[index];
1963 for (Tabs::const_reverse_iterator i(tabs.rbegin()); i != tabs.rend(); ++i)
1964 (*i)->Paint(canvas, cull_set);
1967 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
1968 const ui::MouseEvent& event) {
1969 if (!adjust_layout_)
1972 // The following code attempts to switch to shrink (not stacked) layout when
1973 // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
1974 // to stacked layout when a touch device is used. This is made problematic by
1975 // windows generating mouse move events that do not clearly indicate the move
1976 // is the result of a touch device. This assumes a real mouse is used if
1977 // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
1978 // the time window |kMouseMoveTimeMS|. At the time we get a mouse press we
1979 // know whether its from a touch device or not, but we don't layout then else
1980 // everything shifts. Instead we wait for the release.
1982 // TODO(sky): revisit this when touch events are really plumbed through.
1984 switch (event.type()) {
1985 case ui::ET_MOUSE_PRESSED:
1986 mouse_move_count_ = 0;
1987 last_mouse_move_time_ = base::TimeTicks();
1988 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
1989 if (reset_to_shrink_on_exit_ && touch_layout_) {
1990 gfx::Point tab_strip_point(event.location());
1991 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
1992 Tab* tab = FindTabForEvent(tab_strip_point);
1993 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
1994 SetStackedLayout(false);
1995 controller_->StackedLayoutMaybeChanged();
2000 case ui::ET_MOUSE_MOVED: {
2001 #if defined(USE_ASH)
2002 // Ash does not synthesize mouse events from touch events.
2003 SetResetToShrinkOnExit(true);
2005 gfx::Point location(event.location());
2006 ConvertPointToTarget(source, this, &location);
2007 if (location == last_mouse_move_location_)
2008 return; // Ignore spurious moves.
2009 last_mouse_move_location_ = location;
2010 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2011 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2012 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2014 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2015 SetResetToShrinkOnExit(true);
2017 mouse_move_count_ = 1;
2018 last_mouse_move_time_ = base::TimeTicks::Now();
2021 last_mouse_move_time_ = base::TimeTicks();
2027 case ui::ET_MOUSE_RELEASED: {
2028 gfx::Point location(event.location());
2029 ConvertPointToTarget(source, this, &location);
2030 last_mouse_move_location_ = location;
2031 mouse_move_count_ = 0;
2032 last_mouse_move_time_ = base::TimeTicks();
2033 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2034 SetStackedLayout(true);
2035 controller_->StackedLayoutMaybeChanged();
2045 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2046 double* selected_width) const {
2047 *unselected_width = current_unselected_width_;
2048 *selected_width = current_selected_width_;
2051 void TabStrip::GetDesiredTabWidths(int tab_count,
2053 double* unselected_width,
2054 double* selected_width) const {
2055 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
2056 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2057 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2059 *unselected_width = min_unselected_width;
2060 *selected_width = min_selected_width;
2062 if (tab_count == 0) {
2063 // Return immediately to avoid divide-by-zero below.
2067 // Determine how much space we can actually allocate to tabs.
2068 int available_width = (available_width_for_tabs_ < 0) ?
2069 tab_area_width() : available_width_for_tabs_;
2070 if (mini_tab_count > 0) {
2072 mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset);
2073 tab_count -= mini_tab_count;
2074 if (tab_count == 0) {
2075 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2078 // Account for gap between the last mini-tab and first non-mini-tab.
2079 available_width -= kMiniToNonMiniGap;
2082 // Calculate the desired tab widths by dividing the available space into equal
2083 // portions. Don't let tabs get larger than the "standard width" or smaller
2084 // than the minimum width for each type, respectively.
2085 const int total_offset = kTabHorizontalOffset * (tab_count - 1);
2086 const double desired_tab_width = std::min((static_cast<double>(
2087 available_width - total_offset) / static_cast<double>(tab_count)),
2088 static_cast<double>(Tab::GetStandardSize().width()));
2089 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2090 *selected_width = std::max(desired_tab_width, min_selected_width);
2092 // When there are multiple tabs, we'll have one selected and some unselected
2093 // tabs. If the desired width was between the minimum sizes of these types,
2094 // try to shrink the tabs with the smaller minimum. For example, if we have
2095 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2096 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2097 // width of 1, the above code would set *unselected_width = 2.5,
2098 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2099 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2101 if (tab_count > 1) {
2102 if (desired_tab_width < min_selected_width) {
2103 // Unselected width = (total width - selected width) / (num_tabs - 1)
2104 *unselected_width = std::max(static_cast<double>(
2105 available_width - total_offset - min_selected_width) /
2106 static_cast<double>(tab_count - 1), min_unselected_width);
2111 void TabStrip::ResizeLayoutTabs() {
2112 // We've been called back after the TabStrip has been emptied out (probably
2113 // just prior to the window being destroyed). We need to do nothing here or
2114 // else GetTabAt below will crash.
2115 if (tab_count() == 0)
2118 // It is critically important that this is unhooked here, otherwise we will
2119 // keep spying on messages forever.
2120 RemoveMessageLoopObserver();
2122 in_tab_close_ = false;
2123 available_width_for_tabs_ = -1;
2124 int mini_tab_count = GetMiniTabCount();
2125 if (mini_tab_count == tab_count()) {
2126 // Only mini-tabs, we know the tab widths won't have changed (all
2127 // mini-tabs have the same width), so there is nothing to do.
2130 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2131 // then the width of the active tab may not change, but other widths may
2132 // have. This is particularly important if we've overflowed (all tabs are at
2134 StartResizeLayoutAnimation();
2137 void TabStrip::ResizeLayoutTabsFromTouch() {
2138 // Don't resize if the user is interacting with the tabstrip.
2139 if (!drag_controller_.get())
2142 StartResizeLayoutTabsFromTouchTimer();
2145 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2146 resize_layout_timer_.Stop();
2147 resize_layout_timer_.Start(
2148 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2149 this, &TabStrip::ResizeLayoutTabsFromTouch);
2152 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2153 StopAnimating(false);
2154 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2155 for (int i = 0; i < tab_count(); ++i)
2156 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2157 // Reset the layout size as we've effectively layed out a different size.
2158 // This ensures a layout happens after the drag is done.
2159 last_layout_size_ = gfx::Size();
2162 void TabStrip::AddMessageLoopObserver() {
2163 if (!mouse_watcher_.get()) {
2164 mouse_watcher_.reset(
2165 new views::MouseWatcher(
2166 new views::MouseWatcherViewHost(
2167 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2170 mouse_watcher_->Start();
2173 void TabStrip::RemoveMessageLoopObserver() {
2174 mouse_watcher_.reset(NULL);
2177 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2180 DCHECK_NE(drop_index, -1);
2182 if (drop_index < tab_count()) {
2183 Tab* tab = tab_at(drop_index);
2185 center_x = tab->x() - (kTabHorizontalOffset / 2);
2187 center_x = tab->x() + (tab->width() / 2);
2189 Tab* last_tab = tab_at(drop_index - 1);
2190 center_x = last_tab->x() + last_tab->width() + (kTabHorizontalOffset / 2);
2193 // Mirror the center point if necessary.
2194 center_x = GetMirroredXInView(center_x);
2196 // Determine the screen bounds.
2197 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2198 -drop_indicator_height);
2199 ConvertPointToScreen(this, &drop_loc);
2200 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2201 drop_indicator_height);
2203 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2204 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2205 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2206 *is_beneath = !display.bounds().Contains(drop_bounds);
2208 drop_bounds.Offset(0, drop_bounds.height() + height());
2213 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2214 // If the UI layout is right-to-left, we need to mirror the mouse
2215 // coordinates since we calculate the drop index based on the
2216 // original (and therefore non-mirrored) positions of the tabs.
2217 const int x = GetMirroredXInView(event.x());
2218 // We don't allow replacing the urls of mini-tabs.
2219 for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
2220 Tab* tab = tab_at(i);
2221 const int tab_max_x = tab->x() + tab->width();
2222 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2223 if (x < tab_max_x) {
2224 if (x < tab->x() + hot_width)
2225 SetDropIndex(i, true);
2226 else if (x >= tab_max_x - hot_width)
2227 SetDropIndex(i + 1, true);
2229 SetDropIndex(i, false);
2234 // The drop isn't over a tab, add it to the end.
2235 SetDropIndex(tab_count(), true);
2238 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2239 // Let the controller know of the index update.
2240 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2242 if (tab_data_index == -1) {
2243 if (drop_info_.get())
2244 drop_info_.reset(NULL);
2248 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2249 drop_info_->drop_before == drop_before) {
2254 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2257 if (!drop_info_.get()) {
2259 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2261 drop_info_->drop_index = tab_data_index;
2262 drop_info_->drop_before = drop_before;
2263 if (is_beneath == drop_info_->point_down) {
2264 drop_info_->point_down = !is_beneath;
2265 drop_info_->arrow_view->SetImage(
2266 GetDropArrowImage(drop_info_->point_down));
2270 // Reposition the window. Need to show it too as the window is initially
2272 drop_info_->arrow_window->SetBounds(drop_bounds);
2273 drop_info_->arrow_window->Show();
2276 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2277 const int source_ops = event.source_operations();
2278 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2279 return ui::DragDropTypes::DRAG_COPY;
2280 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2281 return ui::DragDropTypes::DRAG_LINK;
2282 return ui::DragDropTypes::DRAG_MOVE;
2286 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2287 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2288 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2291 // TabStrip::DropInfo ----------------------------------------------------------
2293 TabStrip::DropInfo::DropInfo(int drop_index,
2296 views::Widget* context)
2297 : drop_index(drop_index),
2298 drop_before(drop_before),
2299 point_down(point_down),
2300 file_supported(true) {
2301 arrow_view = new views::ImageView;
2302 arrow_view->SetImage(GetDropArrowImage(point_down));
2304 arrow_window = new views::Widget;
2305 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2306 params.keep_on_top = true;
2307 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2308 params.accept_events = false;
2309 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2310 params.context = context->GetNativeWindow();
2311 arrow_window->Init(params);
2312 arrow_window->SetContentsView(arrow_view);
2315 TabStrip::DropInfo::~DropInfo() {
2316 // Close eventually deletes the window, which deletes arrow_view too.
2317 arrow_window->Close();
2320 ///////////////////////////////////////////////////////////////////////////////
2322 void TabStrip::PrepareForAnimation() {
2323 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2324 for (int i = 0; i < tab_count(); ++i)
2325 tab_at(i)->set_dragging(false);
2329 void TabStrip::GenerateIdealBounds() {
2332 if (touch_layout_) {
2333 if (tabs_.view_size() == 0)
2336 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2337 kNewTabButtonHorizontalOffset;
2338 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2342 GetDesiredTabWidths(tab_count(), GetMiniTabCount(),
2343 ¤t_unselected_width_, ¤t_selected_width_);
2345 // NOTE: This currently assumes a tab's height doesn't differ based on
2346 // selected state or the number of tabs in the strip!
2347 int tab_height = Tab::GetStandardSize().height();
2348 int first_non_mini_index = 0;
2349 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index);
2350 for (int i = first_non_mini_index; i < tab_count(); ++i) {
2351 Tab* tab = tab_at(i);
2352 DCHECK(!tab->data().mini);
2354 tab->IsActive() ? current_selected_width_ : current_unselected_width_;
2355 double end_of_tab = tab_x + tab_width;
2356 int rounded_tab_x = Round(tab_x);
2357 tabs_.set_ideal_bounds(
2359 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2361 tab_x = end_of_tab + kTabHorizontalOffset;
2364 // Update bounds of new tab button.
2366 if ((Tab::GetStandardSize().width() - Round(current_unselected_width_)) > 1 &&
2368 // We're shrinking tabs, so we need to anchor the New Tab button to the
2369 // right edge of the TabStrip's bounds, rather than the right edge of the
2370 // right-most Tab, otherwise it'll bounce when animating.
2371 new_tab_x = width() - newtab_button_bounds_.width();
2373 new_tab_x = Round(tab_x - kTabHorizontalOffset) +
2374 kNewTabButtonHorizontalOffset;
2376 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2379 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) {
2381 int mini_width = Tab::GetMiniWidth();
2382 int tab_height = Tab::GetStandardSize().height();
2384 for (; index < tab_count() && tab_at(index)->data().mini; ++index) {
2385 tabs_.set_ideal_bounds(index, gfx::Rect(next_x, 0, mini_width, tab_height));
2386 next_x += mini_width + kTabHorizontalOffset;
2388 if (index > 0 && index < tab_count())
2389 next_x += kMiniToNonMiniGap;
2390 if (first_non_mini_index)
2391 *first_non_mini_index = index;
2395 void TabStrip::StartResizeLayoutAnimation() {
2396 PrepareForAnimation();
2397 GenerateIdealBounds();
2398 AnimateToIdealBounds();
2401 void TabStrip::StartMiniTabAnimation() {
2402 in_tab_close_ = false;
2403 available_width_for_tabs_ = -1;
2405 PrepareForAnimation();
2407 GenerateIdealBounds();
2408 AnimateToIdealBounds();
2411 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2412 // The user initiated the close. We want to persist the bounds of all the
2413 // existing tabs, so we manually shift ideal_bounds then animate.
2414 Tab* tab_closing = tab_at(model_index);
2415 int delta = tab_closing->width() + kTabHorizontalOffset;
2416 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2417 // add the extra padding.
2418 DCHECK_LT(model_index, tab_count() - 1);
2419 if (tab_closing->data().mini && !tab_at(model_index + 1)->data().mini)
2420 delta += kMiniToNonMiniGap;
2422 for (int i = model_index + 1; i < tab_count(); ++i) {
2423 gfx::Rect bounds = ideal_bounds(i);
2424 bounds.set_x(bounds.x() - delta);
2425 tabs_.set_ideal_bounds(i, bounds);
2428 // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2429 // overflow tabs that will be able to animate into the strip, in which case
2430 // the new tab button should stay where it is.
2431 newtab_button_bounds_.set_x(std::min(
2432 width() - newtab_button_bounds_.width(),
2433 ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset));
2435 PrepareForAnimation();
2437 tab_closing->set_closing(true);
2439 // We still need to paint the tab until we actually remove it. Put it in
2440 // tabs_closing_map_ so we can find it.
2441 RemoveTabFromViewModel(model_index);
2443 AnimateToIdealBounds();
2445 gfx::Rect tab_bounds = tab_closing->bounds();
2446 tab_bounds.set_width(0);
2447 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2449 // Register delegate to do cleanup when done, BoundsAnimator takes
2450 // ownership of RemoveTabDelegate.
2451 bounds_animator_.SetAnimationDelegate(
2453 scoped_ptr<gfx::AnimationDelegate>(
2454 new RemoveTabDelegate(this, tab_closing)));
2457 bool TabStrip::IsPointInTab(Tab* tab,
2458 const gfx::Point& point_in_tabstrip_coords) {
2459 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2460 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2461 return tab->HitTestPoint(point_in_tab_coords);
2464 int TabStrip::GetStartXForNormalTabs() const {
2465 int mini_tab_count = GetMiniTabCount();
2466 if (mini_tab_count == 0)
2468 return mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset) +
2472 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2473 if (touch_layout_) {
2474 int active_tab_index = touch_layout_->active_index();
2475 if (active_tab_index != -1) {
2476 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2478 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2482 return FindTabForEventFrom(point, 0, 1);
2484 for (int i = 0; i < tab_count(); ++i) {
2485 if (IsPointInTab(tab_at(i), point))
2492 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2495 // |start| equals tab_count() when there are only pinned tabs.
2496 if (start == tab_count())
2498 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2499 if (IsPointInTab(tab_at(i), point))
2505 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2506 // The display order doesn't necessarily match the child list order, so we
2507 // walk the display list hit-testing Tabs. Since the active tab always
2508 // renders on top of adjacent tabs, it needs to be hit-tested before any
2509 // left-adjacent Tab, so we look ahead for it as we walk.
2510 for (int i = 0; i < tab_count(); ++i) {
2511 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2512 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2514 if (IsPointInTab(tab_at(i), point))
2521 std::vector<int> TabStrip::GetTabXCoordinates() {
2522 std::vector<int> results;
2523 for (int i = 0; i < tab_count(); ++i)
2524 results.push_back(ideal_bounds(i).x());
2528 void TabStrip::SwapLayoutIfNecessary() {
2529 bool needs_touch = NeedsTouchLayout();
2530 bool using_touch = touch_layout_ != NULL;
2531 if (needs_touch == using_touch)
2535 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2536 tab_size.set_width(Tab::GetTouchWidth());
2537 touch_layout_.reset(new StackedTabStripLayout(
2539 kTabHorizontalOffset,
2543 touch_layout_->SetWidth(tab_area_width());
2544 // This has to be after SetWidth() as SetWidth() is going to reset the
2545 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2546 // many mini-tabs there are).
2547 GenerateIdealBoundsForMiniTabs(NULL);
2548 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(),
2550 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2552 touch_layout_.reset();
2554 PrepareForAnimation();
2555 GenerateIdealBounds();
2557 AnimateToIdealBounds();
2560 bool TabStrip::NeedsTouchLayout() const {
2561 if (!stacked_layout_)
2564 int mini_tab_count = GetMiniTabCount();
2565 int normal_count = tab_count() - mini_tab_count;
2566 if (normal_count <= 1 || normal_count == mini_tab_count)
2568 int x = GetStartXForNormalTabs();
2569 int available_width = tab_area_width() - x;
2570 return (Tab::GetTouchWidth() * normal_count +
2571 kTabHorizontalOffset * (normal_count - 1)) > available_width;
2574 void TabStrip::SetResetToShrinkOnExit(bool value) {
2575 if (!adjust_layout_)
2578 if (value && !stacked_layout_)
2579 value = false; // We're already using shrink (not stacked) layout.
2581 if (value == reset_to_shrink_on_exit_)
2584 reset_to_shrink_on_exit_ = value;
2585 // Add an observer so we know when the mouse moves out of the tabstrip.
2586 if (reset_to_shrink_on_exit_)
2587 AddMessageLoopObserver();
2589 RemoveMessageLoopObserver();
2592 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
2593 if (sender == newtab_button_) {
2594 content::RecordAction(UserMetricsAction("NewTab_Button"));
2595 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
2596 TabStripModel::NEW_TAB_ENUM_COUNT);
2597 if (event.IsMouseEvent()) {
2598 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
2599 if (mouse.IsOnlyMiddleMouseButton()) {
2600 base::string16 clipboard_text = GetClipboardText();
2601 if (!clipboard_text.empty())
2602 controller()->CreateNewTabWithLocation(clipboard_text);
2607 controller()->CreateNewTab();
2608 if (event.type() == ui::ET_GESTURE_TAP)
2609 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
2613 // Overridden to support automation. See automation_proxy_uitest.cc.
2614 const views::View* TabStrip::GetViewByID(int view_id) const {
2615 if (tab_count() > 0) {
2616 if (view_id == VIEW_ID_TAB_LAST)
2617 return tab_at(tab_count() - 1);
2618 if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
2619 int index = view_id - VIEW_ID_TAB_0;
2620 return (index >= 0 && index < tab_count()) ? tab_at(index) : NULL;
2624 return View::GetViewByID(view_id);
2627 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
2628 UpdateStackedLayoutFromMouseEvent(this, event);
2629 // We can't return true here, else clicking in an empty area won't drag the
2634 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
2635 ContinueDrag(this, event);
2639 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
2640 EndDrag(END_DRAG_COMPLETE);
2641 UpdateStackedLayoutFromMouseEvent(this, event);
2644 void TabStrip::OnMouseCaptureLost() {
2645 EndDrag(END_DRAG_CAPTURE_LOST);
2648 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
2649 UpdateStackedLayoutFromMouseEvent(this, event);
2652 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
2653 SetResetToShrinkOnExit(true);
2656 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
2657 SetResetToShrinkOnExit(false);
2658 switch (event->type()) {
2659 case ui::ET_GESTURE_SCROLL_END:
2660 case ui::ET_SCROLL_FLING_START:
2661 case ui::ET_GESTURE_END:
2662 EndDrag(END_DRAG_COMPLETE);
2663 if (adjust_layout_) {
2664 SetStackedLayout(true);
2665 controller_->StackedLayoutMaybeChanged();
2669 case ui::ET_GESTURE_LONG_PRESS:
2670 if (drag_controller_.get())
2671 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
2674 case ui::ET_GESTURE_LONG_TAP: {
2675 EndDrag(END_DRAG_CANCEL);
2676 gfx::Point local_point = event->location();
2677 Tab* tab = FindTabForEvent(local_point);
2679 ConvertPointToScreen(this, &local_point);
2680 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
2685 case ui::ET_GESTURE_SCROLL_UPDATE:
2686 ContinueDrag(this, *event);
2689 case ui::ET_GESTURE_TAP_DOWN:
2690 EndDrag(END_DRAG_CANCEL);
2693 case ui::ET_GESTURE_TAP: {
2694 const int active_index = controller_->GetActiveIndex();
2695 DCHECK_NE(-1, active_index);
2696 Tab* active_tab = tab_at(active_index);
2697 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
2698 if (active_tab->tab_activated_with_last_tap_down())
2699 action = TouchUMA::GESTURE_TABSWITCH_TAP;
2700 TouchUMA::RecordGestureAction(action);
2707 event->SetHandled();
2710 views::View* TabStrip::TargetForRect(views::View* root, const gfx::Rect& rect) {
2711 CHECK_EQ(root, this);
2713 if (!views::UsePointBasedTargeting(rect))
2714 return views::ViewTargeterDelegate::TargetForRect(root, rect);
2715 const gfx::Point point(rect.CenterPoint());
2717 if (!touch_layout_) {
2718 // Return any view that isn't a Tab or this TabStrip immediately. We don't
2719 // want to interfere.
2720 views::View* v = views::ViewTargeterDelegate::TargetForRect(root, rect);
2721 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
2724 views::View* tab = FindTabHitByPoint(point);
2728 if (newtab_button_->visible()) {
2730 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
2734 Tab* tab = FindTabForEvent(point);
2736 return ConvertPointToViewAndGetEventHandler(this, tab, point);