[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip.cc
blob5375cdf77e94e123f61aabca1d6bb7699f5b785f
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"
7 #if defined(OS_WIN)
8 #include <windowsx.h>
9 #endif
11 #include <algorithm>
12 #include <iterator>
13 #include <string>
14 #include <vector>
16 #include "base/compiler_specific.h"
17 #include "base/metrics/histogram.h"
18 #include "base/stl_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/defaults.h"
21 #include "chrome/browser/ui/host_desktop.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/view_ids.h"
24 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
25 #include "chrome/browser/ui/views/tabs/tab.h"
26 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
27 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
29 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
30 #include "content/public/browser/user_metrics.h"
31 #include "grit/generated_resources.h"
32 #include "grit/theme_resources.h"
33 #include "ui/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/views/controls/image_view.h"
50 #include "ui/views/mouse_watcher_view_host.h"
51 #include "ui/views/rect_based_targeting_utils.h"
52 #include "ui/views/view_model_utils.h"
53 #include "ui/views/widget/root_view.h"
54 #include "ui/views/widget/widget.h"
55 #include "ui/views/window/non_client_view.h"
57 #if defined(OS_WIN)
58 #include "ui/gfx/win/hwnd_util.h"
59 #include "ui/views/widget/monitor_win.h"
60 #include "ui/views/win/hwnd_util.h"
61 #endif
63 using base::UserMetricsAction;
64 using ui::DropTargetEvent;
66 namespace {
68 static const int kTabStripAnimationVSlop = 40;
69 // Inactive tabs in a native frame are slightly transparent.
70 static const int kGlassFrameInactiveTabAlpha = 200;
71 // If there are multiple tabs selected then make non-selected inactive tabs
72 // even more transparent.
73 static const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
75 // Alpha applied to all elements save the selected tabs.
76 static const int kInactiveTabAndNewTabButtonAlphaAsh = 230;
77 static const int kInactiveTabAndNewTabButtonAlpha = 255;
79 // Inverse ratio of the width of a tab edge to the width of the tab. When
80 // hovering over the left or right edge of a tab, the drop indicator will
81 // point between tabs.
82 static const int kTabEdgeRatioInverse = 4;
84 // Size of the drop indicator.
85 static int drop_indicator_width;
86 static int drop_indicator_height;
88 static inline int Round(double x) {
89 // Why oh why is this not in a standard header?
90 return static_cast<int>(floor(x + 0.5));
93 // Max number of stacked tabs.
94 static const int kMaxStackedCount = 4;
96 // Padding between stacked tabs.
97 static const int kStackedPadding = 6;
99 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
100 #if !defined(USE_ASH)
101 const int kMouseMoveTimeMS = 200;
102 const int kMouseMoveCountBeforeConsiderReal = 3;
103 #endif
105 // Amount of time we delay before resizing after a close from a touch.
106 const int kTouchResizeLayoutTimeMS = 2000;
108 // Horizontal offset for the new tab button to bring it closer to the
109 // rightmost tab.
110 const int kNewTabButtonHorizontalOffset = -11;
112 // Vertical offset for the new tab button to bring it closer to the
113 // rightmost tab.
114 const int kNewTabButtonVerticalOffset = 7;
116 // Amount the left edge of a tab is offset from the rectangle of the tab's
117 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
118 // Affects the size of the "V" between adjacent tabs.
119 const int kTabHorizontalOffset = -26;
121 // The size of the new tab button must be hardcoded because we need to be
122 // able to lay it out before we are able to get its image from the
123 // ui::ThemeProvider. It also makes sense to do this, because the size of the
124 // new tab button should not need to be calculated dynamically.
125 const int kNewTabButtonAssetWidth = 34;
126 const int kNewTabButtonAssetHeight = 18;
128 // Amount to adjust the clip by when the tab is stacked before the active index.
129 const int kStackedTabLeftClip = 20;
131 // Amount to adjust the clip by when the tab is stacked after the active index.
132 const int kStackedTabRightClip = 20;
134 base::string16 GetClipboardText() {
135 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
136 return base::string16();
137 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
138 CHECK(clipboard);
139 base::string16 clipboard_text;
140 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
141 return clipboard_text;
144 // Animation delegate used when a dragged tab is released. When done sets the
145 // dragging state to false.
146 class ResetDraggingStateDelegate
147 : public views::BoundsAnimator::OwnedAnimationDelegate {
148 public:
149 explicit ResetDraggingStateDelegate(Tab* tab) : tab_(tab) {
152 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
153 tab_->set_dragging(false);
156 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
157 tab_->set_dragging(false);
160 private:
161 Tab* tab_;
163 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
166 // If |dest| contains the point |point_in_source| the event handler from |dest|
167 // is returned. Otherwise NULL is returned.
168 views::View* ConvertPointToViewAndGetEventHandler(
169 views::View* source,
170 views::View* dest,
171 const gfx::Point& point_in_source) {
172 gfx::Point dest_point(point_in_source);
173 views::View::ConvertPointToTarget(source, dest, &dest_point);
174 return dest->HitTestPoint(dest_point) ?
175 dest->GetEventHandlerForPoint(dest_point) : NULL;
178 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
179 // should return NULL if it does not contain the point.
180 views::View* ConvertPointToViewAndGetTooltipHandler(
181 views::View* source,
182 views::View* dest,
183 const gfx::Point& point_in_source) {
184 gfx::Point dest_point(point_in_source);
185 views::View::ConvertPointToTarget(source, dest, &dest_point);
186 return dest->GetTooltipHandlerForPoint(dest_point);
189 TabDragController::EventSource EventSourceFromEvent(
190 const ui::LocatedEvent& event) {
191 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
192 TabDragController::EVENT_SOURCE_MOUSE;
195 } // namespace
197 ///////////////////////////////////////////////////////////////////////////////
198 // NewTabButton
200 // A subclass of button that hit-tests to the shape of the new tab button and
201 // does custom drawing.
203 class NewTabButton : public views::ImageButton {
204 public:
205 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
206 virtual ~NewTabButton();
208 // Set the background offset used to match the background image to the frame
209 // image.
210 void set_background_offset(const gfx::Point& offset) {
211 background_offset_ = offset;
214 protected:
215 // Overridden from views::View:
216 virtual bool HasHitTestMask() const OVERRIDE;
217 virtual void GetHitTestMask(HitTestSource source,
218 gfx::Path* path) const OVERRIDE;
219 #if defined(OS_WIN)
220 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
221 #endif
222 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
224 // Overridden from ui::EventHandler:
225 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
227 private:
228 bool ShouldWindowContentsBeTransparent() const;
229 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
230 float scale) const;
231 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
232 float scale) const;
233 gfx::ImageSkia GetImageForScale(float scale) const;
235 // Tab strip that contains this button.
236 TabStrip* tab_strip_;
238 // The offset used to paint the background image.
239 gfx::Point background_offset_;
241 // were we destroyed?
242 bool* destroyed_;
244 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
247 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
248 : views::ImageButton(listener),
249 tab_strip_(tab_strip),
250 destroyed_(NULL) {
251 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
252 set_triggerable_event_flags(triggerable_event_flags() |
253 ui::EF_MIDDLE_MOUSE_BUTTON);
254 #endif
257 NewTabButton::~NewTabButton() {
258 if (destroyed_)
259 *destroyed_ = true;
262 bool NewTabButton::HasHitTestMask() const {
263 // When the button is sized to the top of the tab strip we want the user to
264 // be able to click on complete bounds, and so don't return a custom hit
265 // mask.
266 return !tab_strip_->SizeTabButtonToTopOfTabStrip();
269 void NewTabButton::GetHitTestMask(HitTestSource source, gfx::Path* path) const {
270 DCHECK(path);
272 SkScalar w = SkIntToScalar(width());
273 SkScalar v_offset = SkIntToScalar(kNewTabButtonVerticalOffset);
275 // These values are defined by the shape of the new tab image. Should that
276 // image ever change, these values will need to be updated. They're so
277 // custom it's not really worth defining constants for.
278 // These values are correct for regular and USE_ASH versions of the image.
279 path->moveTo(0, v_offset + 1);
280 path->lineTo(w - 7, v_offset + 1);
281 path->lineTo(w - 4, v_offset + 4);
282 path->lineTo(w, v_offset + 16);
283 path->lineTo(w - 1, v_offset + 17);
284 path->lineTo(7, v_offset + 17);
285 path->lineTo(4, v_offset + 13);
286 path->lineTo(0, v_offset + 1);
287 path->close();
290 #if defined(OS_WIN)
291 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
292 if (event.IsOnlyRightMouseButton()) {
293 gfx::Point point = event.location();
294 views::View::ConvertPointToScreen(this, &point);
295 bool destroyed = false;
296 destroyed_ = &destroyed;
297 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
298 if (destroyed)
299 return;
301 destroyed_ = NULL;
302 SetState(views::CustomButton::STATE_NORMAL);
303 return;
305 views::ImageButton::OnMouseReleased(event);
307 #endif
309 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
310 gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
311 canvas->DrawImageInt(image, 0, height() - image.height());
314 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
315 // Consume all gesture events here so that the parent (Tab) does not
316 // start consuming gestures.
317 views::ImageButton::OnGestureEvent(event);
318 event->SetHandled();
321 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
322 return GetWidget() &&
323 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
326 gfx::ImageSkia NewTabButton::GetBackgroundImage(
327 views::CustomButton::ButtonState state,
328 float scale) const {
329 int background_id = 0;
330 if (ShouldWindowContentsBeTransparent()) {
331 background_id = IDR_THEME_TAB_BACKGROUND_V;
332 } else if (tab_strip_->controller()->IsIncognito()) {
333 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
334 } else {
335 background_id = IDR_THEME_TAB_BACKGROUND;
338 int alpha = 0;
339 switch (state) {
340 case views::CustomButton::STATE_NORMAL:
341 case views::CustomButton::STATE_HOVERED:
342 alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
343 : 255;
344 break;
345 case views::CustomButton::STATE_PRESSED:
346 alpha = 145;
347 break;
348 default:
349 NOTREACHED();
350 break;
353 gfx::ImageSkia* mask =
354 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
355 int height = mask->height();
356 int width = mask->width();
357 // The canvas and mask has to use the same scale factor.
358 if (!mask->HasRepresentation(scale))
359 scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
361 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
363 // For custom images the background starts at the top of the tab strip.
364 // Otherwise the background starts at the top of the frame.
365 gfx::ImageSkia* background =
366 GetThemeProvider()->GetImageSkiaNamed(background_id);
367 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
368 0 : background_offset_.y();
370 // The new tab background is mirrored in RTL mode, but the theme background
371 // should never be mirrored. Mirror it here to compensate.
372 float x_scale = 1.0f;
373 int x = GetMirroredX() + background_offset_.x();
374 if (base::i18n::IsRTL()) {
375 x_scale = -1.0f;
376 // Offset by |width| such that the same region is painted as if there was no
377 // flip.
378 x += width;
380 canvas.TileImageInt(*background, x, kNewTabButtonVerticalOffset + offset_y,
381 x_scale, 1.0f, 0, 0, width, height);
383 if (alpha != 255) {
384 SkPaint paint;
385 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
386 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
387 paint.setStyle(SkPaint::kFill_Style);
388 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
391 // White highlight on hover.
392 if (state == views::CustomButton::STATE_HOVERED)
393 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
395 return gfx::ImageSkiaOperations::CreateMaskedImage(
396 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
399 gfx::ImageSkia NewTabButton::GetImageForState(
400 views::CustomButton::ButtonState state,
401 float scale) const {
402 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
403 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
404 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
406 gfx::Canvas canvas(
407 gfx::Size(overlay->width(), overlay->height()),
408 scale,
409 false);
410 canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
412 // Draw the button border with a slight alpha.
413 const int kGlassFrameOverlayAlpha = 178;
414 const int kOpaqueFrameOverlayAlpha = 230;
415 uint8 alpha = ShouldWindowContentsBeTransparent() ?
416 kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha;
417 canvas.DrawImageInt(*overlay, 0, 0, alpha);
419 return gfx::ImageSkia(canvas.ExtractImageRep());
422 gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
423 if (!hover_animation_->is_animating())
424 return GetImageForState(state(), scale);
425 return gfx::ImageSkiaOperations::CreateBlendedImage(
426 GetImageForState(views::CustomButton::STATE_NORMAL, scale),
427 GetImageForState(views::CustomButton::STATE_HOVERED, scale),
428 hover_animation_->GetCurrentValue());
431 ///////////////////////////////////////////////////////////////////////////////
432 // TabStrip::RemoveTabDelegate
434 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
435 // done.
436 class TabStrip::RemoveTabDelegate
437 : public views::BoundsAnimator::OwnedAnimationDelegate {
438 public:
439 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
441 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
442 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE;
444 private:
445 void CompleteRemove();
447 // When the animation completes, we send the Container a message to simulate
448 // a mouse moved event at the current mouse position. This tickles the Tab
449 // the mouse is currently over to show the "hot" state of the close button.
450 void HighlightCloseButton();
452 TabStrip* tabstrip_;
453 Tab* tab_;
455 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
458 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
459 Tab* tab)
460 : tabstrip_(tab_strip),
461 tab_(tab) {
464 void TabStrip::RemoveTabDelegate::AnimationEnded(
465 const gfx::Animation* animation) {
466 CompleteRemove();
469 void TabStrip::RemoveTabDelegate::AnimationCanceled(
470 const gfx::Animation* animation) {
471 CompleteRemove();
474 void TabStrip::RemoveTabDelegate::CompleteRemove() {
475 DCHECK(tab_->closing());
476 tabstrip_->RemoveAndDeleteTab(tab_);
477 HighlightCloseButton();
480 void TabStrip::RemoveTabDelegate::HighlightCloseButton() {
481 if (tabstrip_->IsDragSessionActive() ||
482 !tabstrip_->ShouldHighlightCloseButtonAfterRemove()) {
483 // This function is not required (and indeed may crash!) for removes
484 // spawned by non-mouse closes and drag-detaches.
485 return;
488 views::Widget* widget = tabstrip_->GetWidget();
489 // This can be null during shutdown. See http://crbug.com/42737.
490 if (!widget)
491 return;
493 widget->SynthesizeMouseMoveEvent();
496 ///////////////////////////////////////////////////////////////////////////////
497 // TabStrip, public:
499 // static
500 const char TabStrip::kViewClassName[] = "TabStrip";
502 // static
503 const int TabStrip::kMiniToNonMiniGap = 3;
505 TabStrip::TabStrip(TabStripController* controller)
506 : controller_(controller),
507 newtab_button_(NULL),
508 current_unselected_width_(Tab::GetStandardSize().width()),
509 current_selected_width_(Tab::GetStandardSize().width()),
510 available_width_for_tabs_(-1),
511 in_tab_close_(false),
512 animation_container_(new gfx::AnimationContainer()),
513 bounds_animator_(this),
514 layout_type_(TAB_STRIP_LAYOUT_SHRINK),
515 adjust_layout_(false),
516 reset_to_shrink_on_exit_(false),
517 mouse_move_count_(0),
518 immersive_style_(false) {
519 Init();
522 TabStrip::~TabStrip() {
523 FOR_EACH_OBSERVER(TabStripObserver, observers_,
524 TabStripDeleted(this));
526 // The animations may reference the tabs. Shut down the animation before we
527 // delete the tabs.
528 StopAnimating(false);
530 DestroyDragController();
532 // Make sure we unhook ourselves as a message loop observer so that we don't
533 // crash in the case where the user closes the window after closing a tab
534 // but before moving the mouse.
535 RemoveMessageLoopObserver();
537 // The children (tabs) may callback to us from their destructor. Delete them
538 // so that if they call back we aren't in a weird state.
539 RemoveAllChildViews(true);
542 void TabStrip::AddObserver(TabStripObserver* observer) {
543 observers_.AddObserver(observer);
546 void TabStrip::RemoveObserver(TabStripObserver* observer) {
547 observers_.RemoveObserver(observer);
550 void TabStrip::SetLayoutType(TabStripLayoutType layout_type,
551 bool adjust_layout) {
552 adjust_layout_ = adjust_layout;
553 if (layout_type == layout_type_)
554 return;
556 const int active_index = controller_->GetActiveIndex();
557 int active_center = 0;
558 if (active_index != -1) {
559 active_center = ideal_bounds(active_index).x() +
560 ideal_bounds(active_index).width() / 2;
562 layout_type_ = layout_type;
563 SetResetToShrinkOnExit(false);
564 SwapLayoutIfNecessary();
565 // When transitioning to stacked try to keep the active tab centered.
566 if (touch_layout_.get() && active_index != -1) {
567 touch_layout_->SetActiveTabLocation(
568 active_center - ideal_bounds(active_index).width() / 2);
569 AnimateToIdealBounds();
573 gfx::Rect TabStrip::GetNewTabButtonBounds() {
574 return newtab_button_->bounds();
577 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
578 // Extend the button to the screen edge in maximized and immersive fullscreen.
579 views::Widget* widget = GetWidget();
580 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
581 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
584 void TabStrip::StartHighlight(int model_index) {
585 tab_at(model_index)->StartPulse();
588 void TabStrip::StopAllHighlighting() {
589 for (int i = 0; i < tab_count(); ++i)
590 tab_at(i)->StopPulse();
593 void TabStrip::AddTabAt(int model_index,
594 const TabRendererData& data,
595 bool is_active) {
596 // Stop dragging when a new tab is added and dragging a window. Doing
597 // otherwise results in a confusing state if the user attempts to reattach. We
598 // could allow this and make TabDragController update itself during the add,
599 // but this comes up infrequently enough that it's not work the complexity.
600 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
601 drag_controller_->is_dragging_window()) {
602 EndDrag(END_DRAG_COMPLETE);
604 Tab* tab = CreateTab();
605 tab->SetData(data);
606 UpdateTabsClosingMap(model_index, 1);
607 tabs_.Add(tab, model_index);
608 AddChildView(tab);
610 if (touch_layout_.get()) {
611 GenerateIdealBoundsForMiniTabs(NULL);
612 int add_types = 0;
613 if (data.mini)
614 add_types |= StackedTabStripLayout::kAddTypeMini;
615 if (is_active)
616 add_types |= StackedTabStripLayout::kAddTypeActive;
617 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
620 // Don't animate the first tab, it looks weird, and don't animate anything
621 // if the containing window isn't visible yet.
622 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
623 StartInsertTabAnimation(model_index);
624 else
625 DoLayout();
627 SwapLayoutIfNecessary();
629 FOR_EACH_OBSERVER(TabStripObserver, observers_,
630 TabStripAddedTabAt(this, model_index));
633 void TabStrip::MoveTab(int from_model_index,
634 int to_model_index,
635 const TabRendererData& data) {
636 DCHECK_GT(tabs_.view_size(), 0);
637 Tab* last_tab = tab_at(tab_count() - 1);
638 tab_at(from_model_index)->SetData(data);
639 if (touch_layout_.get()) {
640 tabs_.MoveViewOnly(from_model_index, to_model_index);
641 int mini_count = 0;
642 GenerateIdealBoundsForMiniTabs(&mini_count);
643 touch_layout_->MoveTab(
644 from_model_index, to_model_index, controller_->GetActiveIndex(),
645 GetStartXForNormalTabs(), mini_count);
646 } else {
647 tabs_.Move(from_model_index, to_model_index);
649 StartMoveTabAnimation();
650 if (TabDragController::IsAttachedTo(this) &&
651 (last_tab != tab_at(tab_count() - 1) || last_tab->dragging())) {
652 newtab_button_->SetVisible(false);
654 SwapLayoutIfNecessary();
656 FOR_EACH_OBSERVER(TabStripObserver, observers_,
657 TabStripMovedTab(this, from_model_index, to_model_index));
660 void TabStrip::RemoveTabAt(int model_index) {
661 if (touch_layout_.get()) {
662 Tab* tab = tab_at(model_index);
663 tab->set_closing(true);
664 int old_x = tabs_.ideal_bounds(model_index).x();
665 // We still need to paint the tab until we actually remove it. Put it in
666 // tabs_closing_map_ so we can find it.
667 RemoveTabFromViewModel(model_index);
668 touch_layout_->RemoveTab(model_index, GenerateIdealBoundsForMiniTabs(NULL),
669 old_x);
670 ScheduleRemoveTabAnimation(tab);
671 } else if (in_tab_close_ && model_index != GetModelCount()) {
672 StartMouseInitiatedRemoveTabAnimation(model_index);
673 } else {
674 StartRemoveTabAnimation(model_index);
676 SwapLayoutIfNecessary();
678 FOR_EACH_OBSERVER(TabStripObserver, observers_,
679 TabStripRemovedTabAt(this, model_index));
682 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
683 Tab* tab = tab_at(model_index);
684 bool mini_state_changed = tab->data().mini != data.mini;
685 tab->SetData(data);
687 if (mini_state_changed) {
688 if (touch_layout_.get()) {
689 int mini_tab_count = 0;
690 int start_x = GenerateIdealBoundsForMiniTabs(&mini_tab_count);
691 touch_layout_->SetXAndMiniCount(start_x, mini_tab_count);
693 if (GetWidget() && GetWidget()->IsVisible())
694 StartMiniTabAnimation();
695 else
696 DoLayout();
698 SwapLayoutIfNecessary();
701 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
702 if (!in_tab_close_ && IsAnimating()) {
703 // Cancel any current animations. We do this as remove uses the current
704 // ideal bounds and we need to know ideal bounds is in a good state.
705 StopAnimating(true);
708 if (!GetWidget())
709 return;
711 int model_count = GetModelCount();
712 if (model_index + 1 != model_count && model_count > 1) {
713 // The user is about to close a tab other than the last tab. Set
714 // available_width_for_tabs_ so that if we do a layout we don't position a
715 // tab past the end of the second to last tab. We do this so that as the
716 // user closes tabs with the mouse a tab continues to fall under the mouse.
717 Tab* last_tab = tab_at(model_count - 1);
718 Tab* tab_being_removed = tab_at(model_index);
719 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
720 tab_being_removed->width() - kTabHorizontalOffset;
721 if (model_index == 0 && tab_being_removed->data().mini &&
722 !tab_at(1)->data().mini) {
723 available_width_for_tabs_ -= kMiniToNonMiniGap;
727 in_tab_close_ = true;
728 resize_layout_timer_.Stop();
729 if (source == CLOSE_TAB_FROM_TOUCH) {
730 StartResizeLayoutTabsFromTouchTimer();
731 } else {
732 AddMessageLoopObserver();
736 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
737 const ui::ListSelectionModel& new_selection) {
738 if (touch_layout_.get()) {
739 touch_layout_->SetActiveIndex(new_selection.active());
740 // Only start an animation if we need to. Otherwise clicking on an
741 // unselected tab and dragging won't work because dragging is only allowed
742 // if not animating.
743 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
744 AnimateToIdealBounds();
745 SchedulePaint();
746 } else {
747 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
748 // a different size to the selected ones.
749 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
750 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
751 DoLayout();
752 } else {
753 SchedulePaint();
757 ui::ListSelectionModel::SelectedIndices no_longer_selected =
758 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
759 old_selection.selected_indices(),
760 new_selection.selected_indices());
761 for (size_t i = 0; i < no_longer_selected.size(); ++i)
762 tab_at(no_longer_selected[i])->StopMiniTabTitleAnimation();
765 void TabStrip::TabTitleChangedNotLoading(int model_index) {
766 Tab* tab = tab_at(model_index);
767 if (tab->data().mini && !tab->IsActive())
768 tab->StartMiniTabTitleAnimation();
771 Tab* TabStrip::tab_at(int index) const {
772 return static_cast<Tab*>(tabs_.view_at(index));
775 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
776 return tabs_.GetIndexOfView(tab);
779 int TabStrip::GetModelCount() const {
780 return controller_->GetCount();
783 bool TabStrip::IsValidModelIndex(int model_index) const {
784 return controller_->IsValidIndex(model_index);
787 bool TabStrip::IsDragSessionActive() const {
788 return drag_controller_.get() != NULL;
791 bool TabStrip::IsActiveDropTarget() const {
792 for (int i = 0; i < tab_count(); ++i) {
793 Tab* tab = tab_at(i);
794 if (tab->dragging())
795 return true;
797 return false;
800 bool TabStrip::IsTabStripEditable() const {
801 return !IsDragSessionActive() && !IsActiveDropTarget();
804 bool TabStrip::IsTabStripCloseable() const {
805 return !IsDragSessionActive();
808 void TabStrip::UpdateLoadingAnimations() {
809 controller_->UpdateLoadingAnimations();
812 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
813 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
816 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
817 views::View* v = GetEventHandlerForRect(rect);
819 // If there is no control at this location, claim the hit was in the title
820 // bar to get a move action.
821 if (v == this)
822 return true;
824 // Check to see if the rect intersects the non-button parts of the new tab
825 // button. The button has a non-rectangular shape, so if it's not in the
826 // visual portions of the button we treat it as a click to the caption.
827 gfx::RectF rect_in_newtab_coords_f(rect);
828 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
829 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
830 rect_in_newtab_coords_f);
831 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
832 !newtab_button_->HitTestRect(rect_in_newtab_coords))
833 return true;
835 // All other regions, including the new Tab button, should be considered part
836 // of the containing Window's client area so that regular events can be
837 // processed for them.
838 return false;
841 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
842 for (int i = 0; i < tab_count(); ++i)
843 tab_at(i)->set_background_offset(offset);
844 newtab_button_->set_background_offset(offset);
847 views::View* TabStrip::newtab_button() {
848 return newtab_button_;
851 void TabStrip::SetImmersiveStyle(bool enable) {
852 if (immersive_style_ == enable)
853 return;
854 immersive_style_ = enable;
857 bool TabStrip::IsAnimating() const {
858 return bounds_animator_.IsAnimating();
861 void TabStrip::StopAnimating(bool layout) {
862 if (!IsAnimating())
863 return;
865 bounds_animator_.Cancel();
867 if (layout)
868 DoLayout();
871 void TabStrip::FileSupported(const GURL& url, bool supported) {
872 if (drop_info_.get() && drop_info_->url == url)
873 drop_info_->file_supported = supported;
876 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
877 return controller_->GetSelectionModel();
880 bool TabStrip::SupportsMultipleSelection() {
881 // TODO: currently only allow single selection in touch layout mode.
882 return touch_layout_.get() == NULL;
885 void TabStrip::SelectTab(Tab* tab) {
886 int model_index = GetModelIndexOfTab(tab);
887 if (IsValidModelIndex(model_index))
888 controller_->SelectTab(model_index);
891 void TabStrip::ExtendSelectionTo(Tab* tab) {
892 int model_index = GetModelIndexOfTab(tab);
893 if (IsValidModelIndex(model_index))
894 controller_->ExtendSelectionTo(model_index);
897 void TabStrip::ToggleSelected(Tab* tab) {
898 int model_index = GetModelIndexOfTab(tab);
899 if (IsValidModelIndex(model_index))
900 controller_->ToggleSelected(model_index);
903 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
904 int model_index = GetModelIndexOfTab(tab);
905 if (IsValidModelIndex(model_index))
906 controller_->AddSelectionFromAnchorTo(model_index);
909 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
910 if (tab->closing()) {
911 // If the tab is already closing, close the next tab. We do this so that the
912 // user can rapidly close tabs by clicking the close button and not have
913 // the animations interfere with that.
914 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
915 i != tabs_closing_map_.end(); ++i) {
916 std::vector<Tab*>::const_iterator j =
917 std::find(i->second.begin(), i->second.end(), tab);
918 if (j != i->second.end()) {
919 if (i->first + 1 < GetModelCount())
920 controller_->CloseTab(i->first + 1, source);
921 return;
924 // If we get here, it means a tab has been marked as closing but isn't in
925 // the set of known closing tabs.
926 NOTREACHED();
927 return;
929 int model_index = GetModelIndexOfTab(tab);
930 if (IsValidModelIndex(model_index))
931 controller_->CloseTab(model_index, source);
934 void TabStrip::ShowContextMenuForTab(Tab* tab,
935 const gfx::Point& p,
936 ui::MenuSourceType source_type) {
937 controller_->ShowContextMenuForTab(tab, p, source_type);
940 bool TabStrip::IsActiveTab(const Tab* tab) const {
941 int model_index = GetModelIndexOfTab(tab);
942 return IsValidModelIndex(model_index) &&
943 controller_->IsActiveTab(model_index);
946 bool TabStrip::IsTabSelected(const Tab* tab) const {
947 int model_index = GetModelIndexOfTab(tab);
948 return IsValidModelIndex(model_index) &&
949 controller_->IsTabSelected(model_index);
952 bool TabStrip::IsTabPinned(const Tab* tab) const {
953 if (tab->closing())
954 return false;
956 int model_index = GetModelIndexOfTab(tab);
957 return IsValidModelIndex(model_index) &&
958 controller_->IsTabPinned(model_index);
961 void TabStrip::MaybeStartDrag(
962 Tab* tab,
963 const ui::LocatedEvent& event,
964 const ui::ListSelectionModel& original_selection) {
965 // Don't accidentally start any drag operations during animations if the
966 // mouse is down... during an animation tabs are being resized automatically,
967 // so the View system can misinterpret this easily if the mouse is down that
968 // the user is dragging.
969 if (IsAnimating() || tab->closing() ||
970 controller_->HasAvailableDragActions() == 0) {
971 return;
974 // Do not do any dragging of tabs when using the super short immersive style.
975 if (IsImmersiveStyle())
976 return;
978 int model_index = GetModelIndexOfTab(tab);
979 if (!IsValidModelIndex(model_index)) {
980 CHECK(false);
981 return;
983 std::vector<Tab*> tabs;
984 int size_to_selected = 0;
985 int x = tab->GetMirroredXInView(event.x());
986 int y = event.y();
987 // Build the set of selected tabs to drag and calculate the offset from the
988 // first selected tab.
989 for (int i = 0; i < tab_count(); ++i) {
990 Tab* other_tab = tab_at(i);
991 if (IsTabSelected(other_tab)) {
992 tabs.push_back(other_tab);
993 if (other_tab == tab) {
994 size_to_selected = GetSizeNeededForTabs(tabs);
995 x = size_to_selected - tab->width() + x;
999 DCHECK(!tabs.empty());
1000 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1001 ui::ListSelectionModel selection_model;
1002 if (!original_selection.IsSelected(model_index))
1003 selection_model.Copy(original_selection);
1004 // Delete the existing DragController before creating a new one. We do this as
1005 // creating the DragController remembers the WebContents delegates and we need
1006 // to make sure the existing DragController isn't still a delegate.
1007 drag_controller_.reset();
1008 TabDragController::DetachBehavior detach_behavior =
1009 TabDragController::DETACHABLE;
1010 TabDragController::MoveBehavior move_behavior =
1011 TabDragController::REORDER;
1012 // Use MOVE_VISIBILE_TABS in the following conditions:
1013 // . Mouse event generated from touch and the left button is down (the right
1014 // button corresponds to a long press, which we want to reorder).
1015 // . Gesture begin and control key isn't down.
1016 // . Real mouse event and control is down. This is mostly for testing.
1017 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1018 event.type() == ui::ET_GESTURE_BEGIN);
1019 if (touch_layout_.get() &&
1020 ((event.type() == ui::ET_MOUSE_PRESSED &&
1021 (((event.flags() & ui::EF_FROM_TOUCH) &&
1022 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1023 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1024 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1025 (event.type() == ui::ET_GESTURE_BEGIN && !event.IsControlDown()))) {
1026 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1029 drag_controller_.reset(new TabDragController);
1030 drag_controller_->Init(
1031 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1032 detach_behavior, move_behavior, EventSourceFromEvent(event));
1035 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1036 if (drag_controller_.get() &&
1037 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1038 gfx::Point screen_location(event.location());
1039 views::View::ConvertPointToScreen(view, &screen_location);
1040 drag_controller_->Drag(screen_location);
1044 bool TabStrip::EndDrag(EndDragReason reason) {
1045 if (!drag_controller_.get())
1046 return false;
1047 bool started_drag = drag_controller_->started_drag();
1048 drag_controller_->EndDrag(reason);
1049 return started_drag;
1052 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1053 gfx::Point local_point = tab_in_tab_coordinates;
1054 ConvertPointToTarget(tab, this, &local_point);
1056 views::View* view = GetEventHandlerForPoint(local_point);
1057 if (!view)
1058 return NULL; // No tab contains the point.
1060 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1061 while (view && view != this && view->id() != VIEW_ID_TAB)
1062 view = view->parent();
1064 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1067 void TabStrip::OnMouseEventInTab(views::View* source,
1068 const ui::MouseEvent& event) {
1069 UpdateLayoutTypeFromMouseEvent(source, event);
1072 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1073 // Only touch layout needs to restrict the clip.
1074 if (!(touch_layout_.get() || IsStackingDraggedTabs()))
1075 return true;
1077 int index = GetModelIndexOfTab(tab);
1078 if (index == -1)
1079 return true; // Tab is closing, paint it all.
1081 int active_index = IsStackingDraggedTabs() ?
1082 controller_->GetActiveIndex() : touch_layout_->active_index();
1083 if (active_index == tab_count())
1084 active_index--;
1086 if (index < active_index) {
1087 if (tab_at(index)->x() == tab_at(index + 1)->x())
1088 return false;
1090 if (tab_at(index)->x() > tab_at(index + 1)->x())
1091 return true; // Can happen during dragging.
1093 clip->SetRect(0, 0, tab_at(index + 1)->x() - tab_at(index)->x() +
1094 kStackedTabLeftClip,
1095 tab_at(index)->height());
1096 } else if (index > active_index && index > 0) {
1097 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1098 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1099 if (tab_bounds.x() == previous_tab_bounds.x())
1100 return false;
1102 if (tab_bounds.x() < previous_tab_bounds.x())
1103 return true; // Can happen during dragging.
1105 if (previous_tab_bounds.right() + kTabHorizontalOffset != tab_bounds.x()) {
1106 int x = previous_tab_bounds.right() - tab_bounds.x() -
1107 kStackedTabRightClip;
1108 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1111 return true;
1114 bool TabStrip::IsImmersiveStyle() const {
1115 return immersive_style_;
1118 void TabStrip::MouseMovedOutOfHost() {
1119 ResizeLayoutTabs();
1120 if (reset_to_shrink_on_exit_) {
1121 reset_to_shrink_on_exit_ = false;
1122 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true);
1123 controller_->LayoutTypeMaybeChanged();
1127 ///////////////////////////////////////////////////////////////////////////////
1128 // TabStrip, views::View overrides:
1130 void TabStrip::Layout() {
1131 // Only do a layout if our size changed.
1132 if (last_layout_size_ == size())
1133 return;
1134 if (IsDragSessionActive())
1135 return;
1136 DoLayout();
1139 void TabStrip::PaintChildren(gfx::Canvas* canvas,
1140 const views::CullSet& cull_set) {
1141 // The view order doesn't match the paint order (tabs_ contains the tab
1142 // ordering). Additionally we need to paint the tabs that are closing in
1143 // |tabs_closing_map_|.
1144 Tab* active_tab = NULL;
1145 std::vector<Tab*> tabs_dragging;
1146 std::vector<Tab*> selected_tabs;
1147 int selected_tab_count = 0;
1148 bool is_dragging = false;
1149 int active_tab_index = -1;
1150 // Since |touch_layout_| is created based on number of tabs and width we use
1151 // the ideal state to determine if we should paint stacked. This minimizes
1152 // painting changes as we switch between the two.
1153 const bool stacking = (layout_type_ == TAB_STRIP_LAYOUT_STACKED) ||
1154 IsStackingDraggedTabs();
1156 const chrome::HostDesktopType host_desktop_type =
1157 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1158 const int inactive_tab_alpha =
1159 host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH ?
1160 kInactiveTabAndNewTabButtonAlphaAsh :
1161 kInactiveTabAndNewTabButtonAlpha;
1163 if (inactive_tab_alpha < 255)
1164 canvas->SaveLayerAlpha(inactive_tab_alpha);
1166 PaintClosingTabs(canvas, tab_count(), cull_set);
1168 for (int i = tab_count() - 1; i >= 0; --i) {
1169 Tab* tab = tab_at(i);
1170 if (tab->IsSelected())
1171 selected_tab_count++;
1172 if (tab->dragging() && !stacking) {
1173 is_dragging = true;
1174 if (tab->IsActive()) {
1175 active_tab = tab;
1176 active_tab_index = i;
1177 } else {
1178 tabs_dragging.push_back(tab);
1180 } else if (!tab->IsActive()) {
1181 if (!tab->IsSelected()) {
1182 if (!stacking)
1183 tab->Paint(canvas, cull_set);
1184 } else {
1185 selected_tabs.push_back(tab);
1187 } else {
1188 active_tab = tab;
1189 active_tab_index = i;
1191 PaintClosingTabs(canvas, i, cull_set);
1194 // Draw from the left and then the right if we're in touch mode.
1195 if (stacking && active_tab_index >= 0) {
1196 for (int i = 0; i < active_tab_index; ++i) {
1197 Tab* tab = tab_at(i);
1198 tab->Paint(canvas, cull_set);
1201 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1202 Tab* tab = tab_at(i);
1203 tab->Paint(canvas, cull_set);
1206 if (inactive_tab_alpha < 255)
1207 canvas->Restore();
1209 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1210 // Make sure non-active tabs are somewhat transparent.
1211 SkPaint paint;
1212 // If there are multiple tabs selected, fade non-selected tabs more to make
1213 // the selected tabs more noticable.
1214 int alpha = selected_tab_count > 1 ?
1215 kGlassFrameInactiveTabAlphaMultiSelection :
1216 kGlassFrameInactiveTabAlpha;
1217 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1218 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1219 paint.setStyle(SkPaint::kFill_Style);
1220 // The tabstrip area overlaps the toolbar area by 2 px.
1221 canvas->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint);
1224 // Now selected but not active. We don't want these dimmed if using native
1225 // frame, so they're painted after initial pass.
1226 for (size_t i = 0; i < selected_tabs.size(); ++i)
1227 selected_tabs[i]->Paint(canvas, cull_set);
1229 // Next comes the active tab.
1230 if (active_tab && !is_dragging)
1231 active_tab->Paint(canvas, cull_set);
1233 // Paint the New Tab button.
1234 if (inactive_tab_alpha < 255)
1235 canvas->SaveLayerAlpha(inactive_tab_alpha);
1236 newtab_button_->Paint(canvas, cull_set);
1237 if (inactive_tab_alpha < 255)
1238 canvas->Restore();
1240 // And the dragged tabs.
1241 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1242 tabs_dragging[i]->Paint(canvas, cull_set);
1244 // If the active tab is being dragged, it goes last.
1245 if (active_tab && is_dragging)
1246 active_tab->Paint(canvas, cull_set);
1249 const char* TabStrip::GetClassName() const {
1250 return kViewClassName;
1253 gfx::Size TabStrip::GetPreferredSize() const {
1254 // For stacked tabs the minimum size is calculated as the size needed to
1255 // handle showing any number of tabs. Otherwise report the minimum width as
1256 // the size required for a single selected tab plus the new tab button. Don't
1257 // base it on the actual number of tabs because it's undesirable to have the
1258 // minimum window size change when a new tab is opened.
1259 int needed_width;
1260 if (touch_layout_.get() || adjust_layout_) {
1261 needed_width = Tab::GetTouchWidth() +
1262 (2 * kStackedPadding * kMaxStackedCount);
1263 } else {
1264 needed_width = Tab::GetMinimumSelectedSize().width();
1266 needed_width += new_tab_button_width();
1267 if (immersive_style_)
1268 return gfx::Size(needed_width, Tab::GetImmersiveHeight());
1269 return gfx::Size(needed_width, Tab::GetMinimumUnselectedSize().height());
1272 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1273 // Force animations to stop, otherwise it makes the index calculation tricky.
1274 StopAnimating(true);
1276 UpdateDropIndex(event);
1278 GURL url;
1279 base::string16 title;
1281 // Check whether the event data includes supported drop data.
1282 if (event.data().GetURLAndTitle(
1283 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1284 url.is_valid()) {
1285 drop_info_->url = url;
1287 // For file:// URLs, kick off a MIME type request in case they're dropped.
1288 if (url.SchemeIsFile())
1289 controller()->CheckFileSupported(url);
1293 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1294 // Update the drop index even if the file is unsupported, to allow
1295 // dragging a file to the contents of another tab.
1296 UpdateDropIndex(event);
1298 if (!drop_info_->file_supported)
1299 return ui::DragDropTypes::DRAG_NONE;
1301 return GetDropEffect(event);
1304 void TabStrip::OnDragExited() {
1305 SetDropIndex(-1, false);
1308 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1309 if (!drop_info_.get())
1310 return ui::DragDropTypes::DRAG_NONE;
1312 const int drop_index = drop_info_->drop_index;
1313 const bool drop_before = drop_info_->drop_before;
1314 const bool file_supported = drop_info_->file_supported;
1316 // Hide the drop indicator.
1317 SetDropIndex(-1, false);
1319 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1320 // have been changed after |drop_info_| was created.
1321 GURL url;
1322 base::string16 title;
1323 if (!file_supported ||
1324 !event.data().GetURLAndTitle(
1325 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1326 !url.is_valid())
1327 return ui::DragDropTypes::DRAG_NONE;
1329 controller()->PerformDrop(drop_before, drop_index, url);
1331 return GetDropEffect(event);
1334 void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1335 state->role = ui::AX_ROLE_TAB_LIST;
1338 views::View* TabStrip::GetEventHandlerForRect(const gfx::Rect& rect) {
1339 if (!views::UsePointBasedTargeting(rect))
1340 return View::GetEventHandlerForRect(rect);
1341 const gfx::Point point(rect.CenterPoint());
1343 if (!touch_layout_.get()) {
1344 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1345 // want to interfere.
1346 views::View* v = View::GetEventHandlerForRect(rect);
1347 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1348 return v;
1350 views::View* tab = FindTabHitByPoint(point);
1351 if (tab)
1352 return tab;
1353 } else {
1354 if (newtab_button_->visible()) {
1355 views::View* view =
1356 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
1357 if (view)
1358 return view;
1360 Tab* tab = FindTabForEvent(point);
1361 if (tab)
1362 return ConvertPointToViewAndGetEventHandler(this, tab, point);
1364 return this;
1367 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1368 if (!HitTestPoint(point))
1369 return NULL;
1371 if (!touch_layout_.get()) {
1372 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1373 // want to interfere.
1374 views::View* v = View::GetTooltipHandlerForPoint(point);
1375 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1376 return v;
1378 views::View* tab = FindTabHitByPoint(point);
1379 if (tab)
1380 return tab;
1381 } else {
1382 if (newtab_button_->visible()) {
1383 views::View* view =
1384 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1385 if (view)
1386 return view;
1388 Tab* tab = FindTabForEvent(point);
1389 if (tab)
1390 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1392 return this;
1395 // static
1396 int TabStrip::GetImmersiveHeight() {
1397 return Tab::GetImmersiveHeight();
1400 int TabStrip::GetMiniTabCount() const {
1401 int mini_count = 0;
1402 while (mini_count < tab_count() && tab_at(mini_count)->data().mini)
1403 mini_count++;
1404 return mini_count;
1407 ///////////////////////////////////////////////////////////////////////////////
1408 // TabStrip, views::ButtonListener implementation:
1410 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
1411 if (sender == newtab_button_) {
1412 content::RecordAction(UserMetricsAction("NewTab_Button"));
1413 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
1414 TabStripModel::NEW_TAB_ENUM_COUNT);
1415 if (event.IsMouseEvent()) {
1416 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
1417 if (mouse.IsOnlyMiddleMouseButton()) {
1418 base::string16 clipboard_text = GetClipboardText();
1419 if (!clipboard_text.empty())
1420 controller()->CreateNewTabWithLocation(clipboard_text);
1421 return;
1425 controller()->CreateNewTab();
1426 if (event.type() == ui::ET_GESTURE_TAP)
1427 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
1431 ///////////////////////////////////////////////////////////////////////////////
1432 // TabStrip, protected:
1434 // Overridden to support automation. See automation_proxy_uitest.cc.
1435 const views::View* TabStrip::GetViewByID(int view_id) const {
1436 if (tab_count() > 0) {
1437 if (view_id == VIEW_ID_TAB_LAST) {
1438 return tab_at(tab_count() - 1);
1439 } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
1440 int index = view_id - VIEW_ID_TAB_0;
1441 if (index >= 0 && index < tab_count()) {
1442 return tab_at(index);
1443 } else {
1444 return NULL;
1449 return View::GetViewByID(view_id);
1452 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
1453 UpdateLayoutTypeFromMouseEvent(this, event);
1454 // We can't return true here, else clicking in an empty area won't drag the
1455 // window.
1456 return false;
1459 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
1460 ContinueDrag(this, event);
1461 return true;
1464 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
1465 EndDrag(END_DRAG_COMPLETE);
1466 UpdateLayoutTypeFromMouseEvent(this, event);
1469 void TabStrip::OnMouseCaptureLost() {
1470 EndDrag(END_DRAG_CAPTURE_LOST);
1473 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
1474 UpdateLayoutTypeFromMouseEvent(this, event);
1477 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
1478 SetResetToShrinkOnExit(true);
1481 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
1482 SetResetToShrinkOnExit(false);
1483 switch (event->type()) {
1484 case ui::ET_GESTURE_SCROLL_END:
1485 case ui::ET_SCROLL_FLING_START:
1486 case ui::ET_GESTURE_END:
1487 EndDrag(END_DRAG_COMPLETE);
1488 if (adjust_layout_) {
1489 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true);
1490 controller_->LayoutTypeMaybeChanged();
1492 break;
1494 case ui::ET_GESTURE_LONG_PRESS:
1495 if (drag_controller_.get())
1496 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
1497 break;
1499 case ui::ET_GESTURE_LONG_TAP: {
1500 EndDrag(END_DRAG_CANCEL);
1501 gfx::Point local_point = event->location();
1502 Tab* tab = FindTabForEvent(local_point);
1503 if (tab) {
1504 ConvertPointToScreen(this, &local_point);
1505 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
1507 break;
1510 case ui::ET_GESTURE_SCROLL_UPDATE:
1511 ContinueDrag(this, *event);
1512 break;
1514 case ui::ET_GESTURE_BEGIN:
1515 EndDrag(END_DRAG_CANCEL);
1516 break;
1518 case ui::ET_GESTURE_TAP: {
1519 const int active_index = controller_->GetActiveIndex();
1520 DCHECK_NE(-1, active_index);
1521 Tab* active_tab = tab_at(active_index);
1522 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
1523 if (active_tab->tab_activated_with_last_gesture_begin())
1524 action = TouchUMA::GESTURE_TABSWITCH_TAP;
1525 TouchUMA::RecordGestureAction(action);
1526 break;
1529 default:
1530 break;
1532 event->SetHandled();
1535 ///////////////////////////////////////////////////////////////////////////////
1536 // TabStrip, private:
1538 void TabStrip::Init() {
1539 set_id(VIEW_ID_TAB_STRIP);
1540 // So we get enter/exit on children to switch layout type.
1541 set_notify_enter_exit_on_child(true);
1542 newtab_button_bounds_.SetRect(0,
1544 kNewTabButtonAssetWidth,
1545 kNewTabButtonAssetHeight +
1546 kNewTabButtonVerticalOffset);
1547 newtab_button_ = new NewTabButton(this, this);
1548 newtab_button_->SetTooltipText(
1549 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1550 newtab_button_->SetAccessibleName(
1551 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1552 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1553 views::ImageButton::ALIGN_BOTTOM);
1554 AddChildView(newtab_button_);
1555 if (drop_indicator_width == 0) {
1556 // Direction doesn't matter, both images are the same size.
1557 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1558 drop_indicator_width = drop_image->width();
1559 drop_indicator_height = drop_image->height();
1563 Tab* TabStrip::CreateTab() {
1564 Tab* tab = new Tab(this);
1565 tab->set_animation_container(animation_container_.get());
1566 return tab;
1569 void TabStrip::StartInsertTabAnimation(int model_index) {
1570 PrepareForAnimation();
1572 // The TabStrip can now use its entire width to lay out Tabs.
1573 in_tab_close_ = false;
1574 available_width_for_tabs_ = -1;
1576 GenerateIdealBounds();
1578 Tab* tab = tab_at(model_index);
1579 if (model_index == 0) {
1580 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1581 ideal_bounds(model_index).height());
1582 } else {
1583 Tab* last_tab = tab_at(model_index - 1);
1584 tab->SetBounds(last_tab->bounds().right() + kTabHorizontalOffset,
1585 ideal_bounds(model_index).y(), 0,
1586 ideal_bounds(model_index).height());
1589 AnimateToIdealBounds();
1592 void TabStrip::StartMoveTabAnimation() {
1593 PrepareForAnimation();
1594 GenerateIdealBounds();
1595 AnimateToIdealBounds();
1598 void TabStrip::StartRemoveTabAnimation(int model_index) {
1599 PrepareForAnimation();
1601 // Mark the tab as closing.
1602 Tab* tab = tab_at(model_index);
1603 tab->set_closing(true);
1605 RemoveTabFromViewModel(model_index);
1607 ScheduleRemoveTabAnimation(tab);
1610 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1611 // Start an animation for the tabs.
1612 GenerateIdealBounds();
1613 AnimateToIdealBounds();
1615 // Animate the tab being closed to 0x0.
1616 gfx::Rect tab_bounds = tab->bounds();
1617 tab_bounds.set_width(0);
1618 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1620 // Register delegate to do cleanup when done, BoundsAnimator takes
1621 // ownership of RemoveTabDelegate.
1622 bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab),
1623 true);
1625 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1626 // like the new tab button magically appears from beyond the end of the tab
1627 // strip.
1628 if (TabDragController::IsAttachedTo(this)) {
1629 bounds_animator_.StopAnimatingView(newtab_button_);
1630 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1634 void TabStrip::AnimateToIdealBounds() {
1635 for (int i = 0; i < tab_count(); ++i) {
1636 Tab* tab = tab_at(i);
1637 if (!tab->dragging())
1638 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1641 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1644 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1645 return in_tab_close_;
1648 void TabStrip::DoLayout() {
1649 last_layout_size_ = size();
1651 StopAnimating(false);
1653 SwapLayoutIfNecessary();
1655 if (touch_layout_.get())
1656 touch_layout_->SetWidth(size().width() - new_tab_button_width());
1658 GenerateIdealBounds();
1660 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1662 SchedulePaint();
1664 bounds_animator_.StopAnimatingView(newtab_button_);
1665 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1668 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1669 int delta) {
1670 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1671 if (!touch_layout_.get()) {
1672 StackDraggedTabs(delta);
1673 return;
1675 SetIdealBoundsFromPositions(initial_positions);
1676 touch_layout_->DragActiveTab(delta);
1677 DoLayout();
1680 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1681 if (static_cast<size_t>(tab_count()) != positions.size())
1682 return;
1684 for (int i = 0; i < tab_count(); ++i) {
1685 gfx::Rect bounds(ideal_bounds(i));
1686 bounds.set_x(positions[i]);
1687 set_ideal_bounds(i, bounds);
1691 void TabStrip::StackDraggedTabs(int delta) {
1692 DCHECK(!touch_layout_.get());
1693 GenerateIdealBounds();
1694 const int active_index = controller_->GetActiveIndex();
1695 DCHECK_NE(-1, active_index);
1696 if (delta < 0) {
1697 // Drag the tabs to the left, stacking tabs before the active tab.
1698 const int adjusted_delta =
1699 std::min(ideal_bounds(active_index).x() -
1700 kStackedPadding * std::min(active_index, kMaxStackedCount),
1701 -delta);
1702 for (int i = 0; i <= active_index; ++i) {
1703 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1704 gfx::Rect new_bounds(ideal_bounds(i));
1705 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1706 set_ideal_bounds(i, new_bounds);
1708 const bool is_active_mini = tab_at(active_index)->data().mini;
1709 const int active_width = ideal_bounds(active_index).width();
1710 for (int i = active_index + 1; i < tab_count(); ++i) {
1711 const int max_x = ideal_bounds(active_index).x() +
1712 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1713 gfx::Rect new_bounds(ideal_bounds(i));
1714 int new_x = std::max(new_bounds.x() + delta, max_x);
1715 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini &&
1716 new_bounds.width() != active_width)
1717 new_x += (active_width - new_bounds.width());
1718 new_bounds.set_x(new_x);
1719 set_ideal_bounds(i, new_bounds);
1721 } else {
1722 // Drag the tabs to the right, stacking tabs after the active tab.
1723 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1724 const int last_tab_x = width() - new_tab_button_width() - last_tab_width;
1725 if (active_index == tab_count() - 1 &&
1726 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1727 return;
1728 const int adjusted_delta =
1729 std::min(last_tab_x -
1730 kStackedPadding * std::min(tab_count() - active_index - 1,
1731 kMaxStackedCount) -
1732 ideal_bounds(active_index).x(),
1733 delta);
1734 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1735 --i) {
1736 const int max_x = last_tab_x -
1737 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1738 gfx::Rect new_bounds(ideal_bounds(i));
1739 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1740 // Because of rounding not all tabs are the same width. Adjust the
1741 // position to accommodate this, otherwise the stacking is off.
1742 if (new_x == max_x && !tab_at(i)->data().mini &&
1743 new_bounds.width() != last_tab_width)
1744 new_x += (last_tab_width - new_bounds.width());
1745 new_bounds.set_x(new_x);
1746 set_ideal_bounds(i, new_bounds);
1748 for (int i = active_index - 1; i >= 0; --i) {
1749 const int min_x = ideal_bounds(active_index).x() -
1750 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1751 gfx::Rect new_bounds(ideal_bounds(i));
1752 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1753 set_ideal_bounds(i, new_bounds);
1755 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1756 newtab_button_->SetVisible(false);
1758 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1759 SchedulePaint();
1762 bool TabStrip::IsStackingDraggedTabs() const {
1763 return drag_controller_.get() && drag_controller_->started_drag() &&
1764 (drag_controller_->move_behavior() ==
1765 TabDragController::MOVE_VISIBILE_TABS);
1768 void TabStrip::LayoutDraggedTabsAt(const std::vector<Tab*>& tabs,
1769 Tab* active_tab,
1770 const gfx::Point& location,
1771 bool initial_drag) {
1772 // Immediately hide the new tab button if the last tab is being dragged.
1773 if (tab_at(tab_count() - 1)->dragging())
1774 newtab_button_->SetVisible(false);
1775 std::vector<gfx::Rect> bounds;
1776 CalculateBoundsForDraggedTabs(tabs, &bounds);
1777 DCHECK_EQ(tabs.size(), bounds.size());
1778 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1779 int active_tab_index = static_cast<int>(
1780 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1781 for (size_t i = 0; i < tabs.size(); ++i) {
1782 Tab* tab = tabs[i];
1783 gfx::Rect new_bounds = bounds[i];
1784 new_bounds.Offset(location.x(), location.y());
1785 int consecutive_index =
1786 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1787 // If this is the initial layout during a drag and the tabs aren't
1788 // consecutive animate the view into position. Do the same if the tab is
1789 // already animating (which means we previously caused it to animate).
1790 if ((initial_drag &&
1791 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1792 bounds_animator_.IsAnimating(tabs[i])) {
1793 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1794 } else {
1795 tab->SetBoundsRect(new_bounds);
1800 void TabStrip::CalculateBoundsForDraggedTabs(const std::vector<Tab*>& tabs,
1801 std::vector<gfx::Rect>* bounds) {
1802 int x = 0;
1803 for (size_t i = 0; i < tabs.size(); ++i) {
1804 Tab* tab = tabs[i];
1805 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1806 x += kMiniToNonMiniGap;
1807 gfx::Rect new_bounds = tab->bounds();
1808 new_bounds.set_origin(gfx::Point(x, 0));
1809 bounds->push_back(new_bounds);
1810 x += tab->width() + kTabHorizontalOffset;
1814 int TabStrip::GetSizeNeededForTabs(const std::vector<Tab*>& tabs) {
1815 int width = 0;
1816 for (size_t i = 0; i < tabs.size(); ++i) {
1817 Tab* tab = tabs[i];
1818 width += tab->width();
1819 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1820 width += kMiniToNonMiniGap;
1822 if (tabs.size() > 0)
1823 width += kTabHorizontalOffset * static_cast<int>(tabs.size() - 1);
1824 return width;
1827 void TabStrip::RemoveTabFromViewModel(int index) {
1828 // We still need to paint the tab until we actually remove it. Put it
1829 // in tabs_closing_map_ so we can find it.
1830 tabs_closing_map_[index].push_back(tab_at(index));
1831 UpdateTabsClosingMap(index + 1, -1);
1832 tabs_.Remove(index);
1835 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1836 scoped_ptr<Tab> deleter(tab);
1837 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1838 i != tabs_closing_map_.end(); ++i) {
1839 std::vector<Tab*>::iterator j =
1840 std::find(i->second.begin(), i->second.end(), tab);
1841 if (j != i->second.end()) {
1842 i->second.erase(j);
1843 if (i->second.empty())
1844 tabs_closing_map_.erase(i);
1845 return;
1848 NOTREACHED();
1851 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1852 if (tabs_closing_map_.empty())
1853 return;
1855 if (delta == -1 &&
1856 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1857 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1858 const std::vector<Tab*>& tabs(tabs_closing_map_[index]);
1859 tabs_closing_map_[index - 1].insert(
1860 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1862 TabsClosingMap updated_map;
1863 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1864 i != tabs_closing_map_.end(); ++i) {
1865 if (i->first > index)
1866 updated_map[i->first + delta] = i->second;
1867 else if (i->first < index)
1868 updated_map[i->first] = i->second;
1870 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1871 updated_map[index + delta] = tabs_closing_map_[index];
1872 tabs_closing_map_.swap(updated_map);
1875 void TabStrip::StartedDraggingTabs(const std::vector<Tab*>& tabs) {
1876 // Let the controller know that the user started dragging tabs.
1877 controller()->OnStartedDraggingTabs();
1879 // Hide the new tab button immediately if we didn't originate the drag.
1880 if (!drag_controller_.get())
1881 newtab_button_->SetVisible(false);
1883 PrepareForAnimation();
1885 // Reset dragging state of existing tabs.
1886 for (int i = 0; i < tab_count(); ++i)
1887 tab_at(i)->set_dragging(false);
1889 for (size_t i = 0; i < tabs.size(); ++i) {
1890 tabs[i]->set_dragging(true);
1891 bounds_animator_.StopAnimatingView(tabs[i]);
1894 // Move the dragged tabs to their ideal bounds.
1895 GenerateIdealBounds();
1897 // Sets the bounds of the dragged tabs.
1898 for (size_t i = 0; i < tabs.size(); ++i) {
1899 int tab_data_index = GetModelIndexOfTab(tabs[i]);
1900 DCHECK_NE(-1, tab_data_index);
1901 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1903 SchedulePaint();
1906 void TabStrip::DraggedTabsDetached() {
1907 // Let the controller know that the user is not dragging this tabstrip's tabs
1908 // anymore.
1909 controller()->OnStoppedDraggingTabs();
1910 newtab_button_->SetVisible(true);
1913 void TabStrip::StoppedDraggingTabs(const std::vector<Tab*>& tabs,
1914 const std::vector<int>& initial_positions,
1915 bool move_only,
1916 bool completed) {
1917 // Let the controller know that the user stopped dragging tabs.
1918 controller()->OnStoppedDraggingTabs();
1920 newtab_button_->SetVisible(true);
1921 if (move_only && touch_layout_.get()) {
1922 if (completed) {
1923 touch_layout_->SizeToFit();
1924 } else {
1925 SetIdealBoundsFromPositions(initial_positions);
1928 bool is_first_tab = true;
1929 for (size_t i = 0; i < tabs.size(); ++i)
1930 StoppedDraggingTab(tabs[i], &is_first_tab);
1933 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1934 int tab_data_index = GetModelIndexOfTab(tab);
1935 if (tab_data_index == -1) {
1936 // The tab was removed before the drag completed. Don't do anything.
1937 return;
1940 if (*is_first_tab) {
1941 *is_first_tab = false;
1942 PrepareForAnimation();
1944 // Animate the view back to its correct position.
1945 GenerateIdealBounds();
1946 AnimateToIdealBounds();
1948 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1949 // Install a delegate to reset the dragging state when done. We have to leave
1950 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1951 bounds_animator_.SetAnimationDelegate(
1952 tab, new ResetDraggingStateDelegate(tab), true);
1955 void TabStrip::OwnDragController(TabDragController* controller) {
1956 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1957 // via corresponding calls to TabDragController::Detach() and
1958 // TabDragController::Attach(). There is one exception to that rule: when a
1959 // drag might start, we create a TabDragController that is owned by the
1960 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1961 // we then call Attach() on the source tabstrip, but since the source tabstrip
1962 // already owns the TabDragController, so we don't need to do anything.
1963 if (controller != drag_controller_.get())
1964 drag_controller_.reset(controller);
1967 void TabStrip::DestroyDragController() {
1968 newtab_button_->SetVisible(true);
1969 drag_controller_.reset();
1972 TabDragController* TabStrip::ReleaseDragController() {
1973 return drag_controller_.release();
1976 void TabStrip::PaintClosingTabs(gfx::Canvas* canvas,
1977 int index,
1978 const views::CullSet& cull_set) {
1979 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
1980 return;
1982 const std::vector<Tab*>& tabs = tabs_closing_map_[index];
1983 for (std::vector<Tab*>::const_reverse_iterator i(tabs.rbegin());
1984 i != tabs.rend(); ++i) {
1985 (*i)->Paint(canvas, cull_set);
1989 void TabStrip::UpdateLayoutTypeFromMouseEvent(views::View* source,
1990 const ui::MouseEvent& event) {
1991 if (!GetAdjustLayout())
1992 return;
1994 // The following code attempts to switch to TAB_STRIP_LAYOUT_SHRINK when the
1995 // mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
1996 // TAB_STRIP_LAYOUT_STACKED when a touch device is used. This is made
1997 // problematic by windows generating mouse move events that do not clearly
1998 // indicate the move is the result of a touch device. This assumes a real
1999 // mouse is used if |kMouseMoveCountBeforeConsiderReal| mouse move events are
2000 // received within the time window |kMouseMoveTimeMS|. At the time we get a
2001 // mouse press we know whether its from a touch device or not, but we don't
2002 // layout then else everything shifts. Instead we wait for the release.
2004 // TODO(sky): revisit this when touch events are really plumbed through.
2006 switch (event.type()) {
2007 case ui::ET_MOUSE_PRESSED:
2008 mouse_move_count_ = 0;
2009 last_mouse_move_time_ = base::TimeTicks();
2010 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
2011 if (reset_to_shrink_on_exit_ && touch_layout_.get()) {
2012 gfx::Point tab_strip_point(event.location());
2013 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
2014 Tab* tab = FindTabForEvent(tab_strip_point);
2015 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
2016 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true);
2017 controller_->LayoutTypeMaybeChanged();
2020 break;
2022 case ui::ET_MOUSE_MOVED: {
2023 #if defined(USE_ASH)
2024 // Ash does not synthesize mouse events from touch events.
2025 SetResetToShrinkOnExit(true);
2026 #else
2027 gfx::Point location(event.location());
2028 ConvertPointToTarget(source, this, &location);
2029 if (location == last_mouse_move_location_)
2030 return; // Ignore spurious moves.
2031 last_mouse_move_location_ = location;
2032 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2033 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2034 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2035 kMouseMoveTimeMS) {
2036 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2037 SetResetToShrinkOnExit(true);
2038 } else {
2039 mouse_move_count_ = 1;
2040 last_mouse_move_time_ = base::TimeTicks::Now();
2042 } else {
2043 last_mouse_move_time_ = base::TimeTicks();
2045 #endif
2046 break;
2049 case ui::ET_MOUSE_RELEASED: {
2050 gfx::Point location(event.location());
2051 ConvertPointToTarget(source, this, &location);
2052 last_mouse_move_location_ = location;
2053 mouse_move_count_ = 0;
2054 last_mouse_move_time_ = base::TimeTicks();
2055 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2056 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true);
2057 controller_->LayoutTypeMaybeChanged();
2059 break;
2062 default:
2063 break;
2067 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2068 double* selected_width) const {
2069 *unselected_width = current_unselected_width_;
2070 *selected_width = current_selected_width_;
2073 void TabStrip::GetDesiredTabWidths(int tab_count,
2074 int mini_tab_count,
2075 double* unselected_width,
2076 double* selected_width) const {
2077 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
2078 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2079 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2081 *unselected_width = min_unselected_width;
2082 *selected_width = min_selected_width;
2084 if (tab_count == 0) {
2085 // Return immediately to avoid divide-by-zero below.
2086 return;
2089 // Determine how much space we can actually allocate to tabs.
2090 int available_width;
2091 if (available_width_for_tabs_ < 0) {
2092 available_width = width() - new_tab_button_width();
2093 } else {
2094 // Interesting corner case: if |available_width_for_tabs_| > the result
2095 // of the calculation in the conditional arm above, the strip is in
2096 // overflow. We can either use the specified width or the true available
2097 // width here; the first preserves the consistent "leave the last tab under
2098 // the user's mouse so they can close many tabs" behavior at the cost of
2099 // prolonging the glitchy appearance of the overflow state, while the second
2100 // gets us out of overflow as soon as possible but forces the user to move
2101 // their mouse for a few tabs' worth of closing. We choose visual
2102 // imperfection over behavioral imperfection and select the first option.
2103 available_width = available_width_for_tabs_;
2106 if (mini_tab_count > 0) {
2107 available_width -=
2108 mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset);
2109 tab_count -= mini_tab_count;
2110 if (tab_count == 0) {
2111 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2112 return;
2114 // Account for gap between the last mini-tab and first non-mini-tab.
2115 available_width -= kMiniToNonMiniGap;
2118 // Calculate the desired tab widths by dividing the available space into equal
2119 // portions. Don't let tabs get larger than the "standard width" or smaller
2120 // than the minimum width for each type, respectively.
2121 const int total_offset = kTabHorizontalOffset * (tab_count - 1);
2122 const double desired_tab_width = std::min((static_cast<double>(
2123 available_width - total_offset) / static_cast<double>(tab_count)),
2124 static_cast<double>(Tab::GetStandardSize().width()));
2125 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2126 *selected_width = std::max(desired_tab_width, min_selected_width);
2128 // When there are multiple tabs, we'll have one selected and some unselected
2129 // tabs. If the desired width was between the minimum sizes of these types,
2130 // try to shrink the tabs with the smaller minimum. For example, if we have
2131 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2132 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2133 // width of 1, the above code would set *unselected_width = 2.5,
2134 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2135 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2136 // of 10.
2137 if (tab_count > 1) {
2138 if ((min_unselected_width < min_selected_width) &&
2139 (desired_tab_width < min_selected_width)) {
2140 // Unselected width = (total width - selected width) / (num_tabs - 1)
2141 *unselected_width = std::max(static_cast<double>(
2142 available_width - total_offset - min_selected_width) /
2143 static_cast<double>(tab_count - 1), min_unselected_width);
2144 } else if ((min_unselected_width > min_selected_width) &&
2145 (desired_tab_width < min_unselected_width)) {
2146 // Selected width = (total width - (unselected width * (num_tabs - 1)))
2147 *selected_width = std::max(available_width - total_offset -
2148 (min_unselected_width * (tab_count - 1)), min_selected_width);
2153 void TabStrip::ResizeLayoutTabs() {
2154 // We've been called back after the TabStrip has been emptied out (probably
2155 // just prior to the window being destroyed). We need to do nothing here or
2156 // else GetTabAt below will crash.
2157 if (tab_count() == 0)
2158 return;
2160 // It is critically important that this is unhooked here, otherwise we will
2161 // keep spying on messages forever.
2162 RemoveMessageLoopObserver();
2164 in_tab_close_ = false;
2165 available_width_for_tabs_ = -1;
2166 int mini_tab_count = GetMiniTabCount();
2167 if (mini_tab_count == tab_count()) {
2168 // Only mini-tabs, we know the tab widths won't have changed (all
2169 // mini-tabs have the same width), so there is nothing to do.
2170 return;
2172 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2173 // then the width of the active tab may not change, but other widths may
2174 // have. This is particularly important if we've overflowed (all tabs are at
2175 // the min).
2176 StartResizeLayoutAnimation();
2179 void TabStrip::ResizeLayoutTabsFromTouch() {
2180 // Don't resize if the user is interacting with the tabstrip.
2181 if (!drag_controller_.get())
2182 ResizeLayoutTabs();
2183 else
2184 StartResizeLayoutTabsFromTouchTimer();
2187 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2188 resize_layout_timer_.Stop();
2189 resize_layout_timer_.Start(
2190 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2191 this, &TabStrip::ResizeLayoutTabsFromTouch);
2194 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2195 StopAnimating(false);
2196 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2197 for (int i = 0; i < tab_count(); ++i)
2198 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2199 // Reset the layout size as we've effectively layed out a different size.
2200 // This ensures a layout happens after the drag is done.
2201 last_layout_size_ = gfx::Size();
2204 void TabStrip::AddMessageLoopObserver() {
2205 if (!mouse_watcher_.get()) {
2206 mouse_watcher_.reset(
2207 new views::MouseWatcher(
2208 new views::MouseWatcherViewHost(
2209 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2210 this));
2212 mouse_watcher_->Start();
2215 void TabStrip::RemoveMessageLoopObserver() {
2216 mouse_watcher_.reset(NULL);
2219 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2220 bool drop_before,
2221 bool* is_beneath) {
2222 DCHECK_NE(drop_index, -1);
2223 int center_x;
2224 if (drop_index < tab_count()) {
2225 Tab* tab = tab_at(drop_index);
2226 if (drop_before)
2227 center_x = tab->x() - (kTabHorizontalOffset / 2);
2228 else
2229 center_x = tab->x() + (tab->width() / 2);
2230 } else {
2231 Tab* last_tab = tab_at(drop_index - 1);
2232 center_x = last_tab->x() + last_tab->width() + (kTabHorizontalOffset / 2);
2235 // Mirror the center point if necessary.
2236 center_x = GetMirroredXInView(center_x);
2238 // Determine the screen bounds.
2239 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2240 -drop_indicator_height);
2241 ConvertPointToScreen(this, &drop_loc);
2242 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2243 drop_indicator_height);
2245 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2246 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2247 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2248 *is_beneath = !display.bounds().Contains(drop_bounds);
2249 if (*is_beneath)
2250 drop_bounds.Offset(0, drop_bounds.height() + height());
2252 return drop_bounds;
2255 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2256 // If the UI layout is right-to-left, we need to mirror the mouse
2257 // coordinates since we calculate the drop index based on the
2258 // original (and therefore non-mirrored) positions of the tabs.
2259 const int x = GetMirroredXInView(event.x());
2260 // We don't allow replacing the urls of mini-tabs.
2261 for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
2262 Tab* tab = tab_at(i);
2263 const int tab_max_x = tab->x() + tab->width();
2264 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2265 if (x < tab_max_x) {
2266 if (x < tab->x() + hot_width)
2267 SetDropIndex(i, true);
2268 else if (x >= tab_max_x - hot_width)
2269 SetDropIndex(i + 1, true);
2270 else
2271 SetDropIndex(i, false);
2272 return;
2276 // The drop isn't over a tab, add it to the end.
2277 SetDropIndex(tab_count(), true);
2280 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2281 // Let the controller know of the index update.
2282 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2284 if (tab_data_index == -1) {
2285 if (drop_info_.get())
2286 drop_info_.reset(NULL);
2287 return;
2290 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2291 drop_info_->drop_before == drop_before) {
2292 return;
2295 bool is_beneath;
2296 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2297 &is_beneath);
2299 if (!drop_info_.get()) {
2300 drop_info_.reset(
2301 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2302 } else {
2303 drop_info_->drop_index = tab_data_index;
2304 drop_info_->drop_before = drop_before;
2305 if (is_beneath == drop_info_->point_down) {
2306 drop_info_->point_down = !is_beneath;
2307 drop_info_->arrow_view->SetImage(
2308 GetDropArrowImage(drop_info_->point_down));
2312 // Reposition the window. Need to show it too as the window is initially
2313 // hidden.
2314 drop_info_->arrow_window->SetBounds(drop_bounds);
2315 drop_info_->arrow_window->Show();
2318 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2319 const int source_ops = event.source_operations();
2320 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2321 return ui::DragDropTypes::DRAG_COPY;
2322 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2323 return ui::DragDropTypes::DRAG_LINK;
2324 return ui::DragDropTypes::DRAG_MOVE;
2327 // static
2328 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2329 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2330 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2333 // TabStrip::DropInfo ----------------------------------------------------------
2335 TabStrip::DropInfo::DropInfo(int drop_index,
2336 bool drop_before,
2337 bool point_down,
2338 views::Widget* context)
2339 : drop_index(drop_index),
2340 drop_before(drop_before),
2341 point_down(point_down),
2342 file_supported(true) {
2343 arrow_view = new views::ImageView;
2344 arrow_view->SetImage(GetDropArrowImage(point_down));
2346 arrow_window = new views::Widget;
2347 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2348 params.keep_on_top = true;
2349 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2350 params.accept_events = false;
2351 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2352 params.context = context->GetNativeView();
2353 arrow_window->Init(params);
2354 arrow_window->SetContentsView(arrow_view);
2357 TabStrip::DropInfo::~DropInfo() {
2358 // Close eventually deletes the window, which deletes arrow_view too.
2359 arrow_window->Close();
2362 ///////////////////////////////////////////////////////////////////////////////
2364 void TabStrip::PrepareForAnimation() {
2365 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2366 for (int i = 0; i < tab_count(); ++i)
2367 tab_at(i)->set_dragging(false);
2371 void TabStrip::GenerateIdealBounds() {
2372 int new_tab_y = 0;
2374 if (touch_layout_.get()) {
2375 if (tabs_.view_size() == 0)
2376 return;
2378 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2379 kNewTabButtonHorizontalOffset;
2380 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2381 return;
2384 double unselected, selected;
2385 GetDesiredTabWidths(tab_count(), GetMiniTabCount(), &unselected, &selected);
2386 current_unselected_width_ = unselected;
2387 current_selected_width_ = selected;
2389 // NOTE: This currently assumes a tab's height doesn't differ based on
2390 // selected state or the number of tabs in the strip!
2391 int tab_height = Tab::GetStandardSize().height();
2392 int first_non_mini_index = 0;
2393 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index);
2394 for (int i = first_non_mini_index; i < tab_count(); ++i) {
2395 Tab* tab = tab_at(i);
2396 DCHECK(!tab->data().mini);
2397 double tab_width = tab->IsActive() ? selected : unselected;
2398 double end_of_tab = tab_x + tab_width;
2399 int rounded_tab_x = Round(tab_x);
2400 set_ideal_bounds(
2402 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2403 tab_height));
2404 tab_x = end_of_tab + kTabHorizontalOffset;
2407 // Update bounds of new tab button.
2408 int new_tab_x;
2409 if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 &&
2410 !in_tab_close_) {
2411 // We're shrinking tabs, so we need to anchor the New Tab button to the
2412 // right edge of the TabStrip's bounds, rather than the right edge of the
2413 // right-most Tab, otherwise it'll bounce when animating.
2414 new_tab_x = width() - newtab_button_bounds_.width();
2415 } else {
2416 new_tab_x = Round(tab_x - kTabHorizontalOffset) +
2417 kNewTabButtonHorizontalOffset;
2419 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2422 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) {
2423 int next_x = 0;
2424 int mini_width = Tab::GetMiniWidth();
2425 int tab_height = Tab::GetStandardSize().height();
2426 int index = 0;
2427 for (; index < tab_count() && tab_at(index)->data().mini; ++index) {
2428 set_ideal_bounds(index,
2429 gfx::Rect(next_x, 0, mini_width, tab_height));
2430 next_x += mini_width + kTabHorizontalOffset;
2432 if (index > 0 && index < tab_count())
2433 next_x += kMiniToNonMiniGap;
2434 if (first_non_mini_index)
2435 *first_non_mini_index = index;
2436 return next_x;
2439 // static
2440 int TabStrip::new_tab_button_width() {
2441 return kNewTabButtonAssetWidth + kNewTabButtonHorizontalOffset;
2444 // static
2445 int TabStrip::button_v_offset() {
2446 return kNewTabButtonVerticalOffset;
2449 int TabStrip::tab_area_width() const {
2450 return width() - new_tab_button_width();
2453 void TabStrip::StartResizeLayoutAnimation() {
2454 PrepareForAnimation();
2455 GenerateIdealBounds();
2456 AnimateToIdealBounds();
2459 void TabStrip::StartMiniTabAnimation() {
2460 in_tab_close_ = false;
2461 available_width_for_tabs_ = -1;
2463 PrepareForAnimation();
2465 GenerateIdealBounds();
2466 AnimateToIdealBounds();
2469 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2470 // The user initiated the close. We want to persist the bounds of all the
2471 // existing tabs, so we manually shift ideal_bounds then animate.
2472 Tab* tab_closing = tab_at(model_index);
2473 int delta = tab_closing->width() + kTabHorizontalOffset;
2474 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2475 // add the extra padding.
2476 DCHECK_NE(model_index + 1, tab_count());
2477 if (tab_closing->data().mini && model_index + 1 < tab_count() &&
2478 !tab_at(model_index + 1)->data().mini) {
2479 delta += kMiniToNonMiniGap;
2482 for (int i = model_index + 1; i < tab_count(); ++i) {
2483 gfx::Rect bounds = ideal_bounds(i);
2484 bounds.set_x(bounds.x() - delta);
2485 set_ideal_bounds(i, bounds);
2488 newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta);
2490 PrepareForAnimation();
2492 tab_closing->set_closing(true);
2494 // We still need to paint the tab until we actually remove it. Put it in
2495 // tabs_closing_map_ so we can find it.
2496 RemoveTabFromViewModel(model_index);
2498 AnimateToIdealBounds();
2500 gfx::Rect tab_bounds = tab_closing->bounds();
2501 tab_bounds.set_width(0);
2502 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2504 // Register delegate to do cleanup when done, BoundsAnimator takes
2505 // ownership of RemoveTabDelegate.
2506 bounds_animator_.SetAnimationDelegate(
2507 tab_closing,
2508 new RemoveTabDelegate(this, tab_closing),
2509 true);
2512 bool TabStrip::IsPointInTab(Tab* tab,
2513 const gfx::Point& point_in_tabstrip_coords) {
2514 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2515 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2516 return tab->HitTestPoint(point_in_tab_coords);
2519 int TabStrip::GetStartXForNormalTabs() const {
2520 int mini_tab_count = GetMiniTabCount();
2521 if (mini_tab_count == 0)
2522 return 0;
2523 return mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset) +
2524 kMiniToNonMiniGap;
2527 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2528 if (touch_layout_.get()) {
2529 int active_tab_index = touch_layout_->active_index();
2530 if (active_tab_index != -1) {
2531 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2532 if (!tab)
2533 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2534 return tab;
2535 } else if (tab_count()) {
2536 return FindTabForEventFrom(point, 0, 1);
2538 } else {
2539 for (int i = 0; i < tab_count(); ++i) {
2540 if (IsPointInTab(tab_at(i), point))
2541 return tab_at(i);
2544 return NULL;
2547 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2548 int start,
2549 int delta) {
2550 // |start| equals tab_count() when there are only pinned tabs.
2551 if (start == tab_count())
2552 start += delta;
2553 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2554 if (IsPointInTab(tab_at(i), point))
2555 return tab_at(i);
2557 return NULL;
2560 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2561 // The display order doesn't necessarily match the child list order, so we
2562 // walk the display list hit-testing Tabs. Since the active tab always
2563 // renders on top of adjacent tabs, it needs to be hit-tested before any
2564 // left-adjacent Tab, so we look ahead for it as we walk.
2565 for (int i = 0; i < tab_count(); ++i) {
2566 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2567 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2568 return next_tab;
2569 if (IsPointInTab(tab_at(i), point))
2570 return tab_at(i);
2573 return NULL;
2576 std::vector<int> TabStrip::GetTabXCoordinates() {
2577 std::vector<int> results;
2578 for (int i = 0; i < tab_count(); ++i)
2579 results.push_back(ideal_bounds(i).x());
2580 return results;
2583 void TabStrip::SwapLayoutIfNecessary() {
2584 bool needs_touch = NeedsTouchLayout();
2585 bool using_touch = touch_layout_.get() != NULL;
2586 if (needs_touch == using_touch)
2587 return;
2589 if (needs_touch) {
2590 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2591 tab_size.set_width(Tab::GetTouchWidth());
2592 touch_layout_.reset(new StackedTabStripLayout(
2593 tab_size,
2594 kTabHorizontalOffset,
2595 kStackedPadding,
2596 kMaxStackedCount,
2597 &tabs_));
2598 touch_layout_->SetWidth(width() - new_tab_button_width());
2599 // This has to be after SetWidth() as SetWidth() is going to reset the
2600 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2601 // many mini-tabs there are).
2602 GenerateIdealBoundsForMiniTabs(NULL);
2603 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(),
2604 GetMiniTabCount());
2605 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2606 } else {
2607 touch_layout_.reset();
2609 PrepareForAnimation();
2610 GenerateIdealBounds();
2611 AnimateToIdealBounds();
2614 bool TabStrip::NeedsTouchLayout() const {
2615 if (layout_type_ == TAB_STRIP_LAYOUT_SHRINK)
2616 return false;
2618 int mini_tab_count = GetMiniTabCount();
2619 int normal_count = tab_count() - mini_tab_count;
2620 if (normal_count <= 1 || normal_count == mini_tab_count)
2621 return false;
2622 int x = GetStartXForNormalTabs();
2623 int available_width = width() - x - new_tab_button_width();
2624 return (Tab::GetTouchWidth() * normal_count +
2625 kTabHorizontalOffset * (normal_count - 1)) > available_width;
2628 void TabStrip::SetResetToShrinkOnExit(bool value) {
2629 if (!GetAdjustLayout())
2630 return;
2632 if (value && layout_type_ == TAB_STRIP_LAYOUT_SHRINK)
2633 value = false; // We're already at TAB_STRIP_LAYOUT_SHRINK.
2635 if (value == reset_to_shrink_on_exit_)
2636 return;
2638 reset_to_shrink_on_exit_ = value;
2639 // Add an observer so we know when the mouse moves out of the tabstrip.
2640 if (reset_to_shrink_on_exit_)
2641 AddMessageLoopObserver();
2642 else
2643 RemoveMessageLoopObserver();
2646 bool TabStrip::GetAdjustLayout() const {
2647 if (!adjust_layout_)
2648 return false;
2649 return chrome::GetHostDesktopTypeForNativeView(
2650 GetWidget()->GetNativeView()) == chrome::HOST_DESKTOP_TYPE_ASH;