Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip.cc
blobf37295b7ec75b5f5bfb30996d8eeb83c9fccd47d
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/base/accessibility/accessible_view_state.h"
34 #include "ui/base/default_theme_provider.h"
35 #include "ui/base/dragdrop/drag_drop_types.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/layout.h"
38 #include "ui/base/models/list_selection_model.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/gfx/animation/animation_container.h"
41 #include "ui/gfx/animation/throb_animation.h"
42 #include "ui/gfx/canvas.h"
43 #include "ui/gfx/display.h"
44 #include "ui/gfx/image/image_skia.h"
45 #include "ui/gfx/image/image_skia_operations.h"
46 #include "ui/gfx/path.h"
47 #include "ui/gfx/rect_conversions.h"
48 #include "ui/gfx/screen.h"
49 #include "ui/gfx/size.h"
50 #include "ui/views/controls/image_view.h"
51 #include "ui/views/mouse_watcher_view_host.h"
52 #include "ui/views/rect_based_targeting_utils.h"
53 #include "ui/views/view_model_utils.h"
54 #include "ui/views/widget/root_view.h"
55 #include "ui/views/widget/widget.h"
56 #include "ui/views/window/non_client_view.h"
58 #if defined(OS_WIN)
59 #include "ui/gfx/win/hwnd_util.h"
60 #include "ui/views/widget/monitor_win.h"
61 #include "ui/views/win/hwnd_util.h"
62 #include "win8/util/win8_util.h"
63 #endif
65 using base::UserMetricsAction;
66 using ui::DropTargetEvent;
68 namespace {
70 static const int kTabStripAnimationVSlop = 40;
71 // Inactive tabs in a native frame are slightly transparent.
72 static const int kNativeFrameInactiveTabAlpha = 200;
73 // If there are multiple tabs selected then make non-selected inactive tabs
74 // even more transparent.
75 static const int kNativeFrameInactiveTabAlphaMultiSelection = 150;
77 // Alpha applied to all elements save the selected tabs.
78 static const int kInactiveTabAndNewTabButtonAlphaAsh = 230;
79 static const int kInactiveTabAndNewTabButtonAlpha = 255;
81 // Inverse ratio of the width of a tab edge to the width of the tab. When
82 // hovering over the left or right edge of a tab, the drop indicator will
83 // point between tabs.
84 static const int kTabEdgeRatioInverse = 4;
86 // Size of the drop indicator.
87 static int drop_indicator_width;
88 static int drop_indicator_height;
90 static inline int Round(double x) {
91 // Why oh why is this not in a standard header?
92 return static_cast<int>(floor(x + 0.5));
95 // Max number of stacked tabs.
96 static const int kMaxStackedCount = 4;
98 // Padding between stacked tabs.
99 static const int kStackedPadding = 6;
101 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
102 #if !defined(USE_ASH)
103 const int kMouseMoveTimeMS = 200;
104 const int kMouseMoveCountBeforeConsiderReal = 3;
105 #endif
107 // Amount of time we delay before resizing after a close from a touch.
108 const int kTouchResizeLayoutTimeMS = 2000;
110 // Horizontal offset for the new tab button to bring it closer to the
111 // rightmost tab.
112 int newtab_button_h_offset() {
113 static int value = -1;
114 if (value == -1) {
115 switch (ui::GetDisplayLayout()) {
116 case ui::LAYOUT_DESKTOP:
117 value = -11;
118 break;
119 case ui::LAYOUT_TOUCH:
120 value = -13;
121 break;
122 default:
123 NOTREACHED();
126 return value;
129 // Vertical offset for the new tab button to bring it closer to the
130 // rightmost tab.
131 int newtab_button_v_offset() {
132 static int value = -1;
133 if (value == -1) {
134 switch (ui::GetDisplayLayout()) {
135 case ui::LAYOUT_DESKTOP:
136 value = 7;
137 break;
138 case ui::LAYOUT_TOUCH:
139 value = 8;
140 break;
141 default:
142 NOTREACHED();
145 return value;
148 // Amount the left edge of a tab is offset from the rectangle of the tab's
149 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
150 // Affects the size of the "V" between adjacent tabs.
151 int tab_h_offset() {
152 static int value = -1;
153 if (value == -1) {
154 switch (ui::GetDisplayLayout()) {
155 case ui::LAYOUT_DESKTOP:
156 value = -26;
157 break;
158 case ui::LAYOUT_TOUCH:
159 value = -34;
160 break;
161 default:
162 NOTREACHED();
165 return value;
168 // The size of the new tab button must be hardcoded because we need to be
169 // able to lay it out before we are able to get its image from the
170 // ui::ThemeProvider. It also makes sense to do this, because the size of the
171 // new tab button should not need to be calculated dynamically.
172 int newtab_button_asset_width() {
173 static int value = -1;
174 if (value == -1) {
175 switch (ui::GetDisplayLayout()) {
176 case ui::LAYOUT_DESKTOP:
177 value = 34;
178 break;
179 case ui::LAYOUT_TOUCH:
180 value = 46;
181 break;
182 default:
183 NOTREACHED();
186 return value;
189 int newtab_button_asset_height() {
190 static int value = -1;
191 if (value == -1) {
192 switch (ui::GetDisplayLayout()) {
193 case ui::LAYOUT_DESKTOP:
194 value = 18;
195 break;
196 case ui::LAYOUT_TOUCH:
197 value = 24;
198 break;
199 default:
200 NOTREACHED();
203 return value;
206 // Amount to adjust the clip by when the tab is stacked before the active index.
207 int stacked_tab_left_clip() {
208 static int value = -1;
209 if (value == -1) {
210 switch (ui::GetDisplayLayout()) {
211 case ui::LAYOUT_DESKTOP:
212 value = 20;
213 break;
214 case ui::LAYOUT_TOUCH:
215 value = 26;
216 break;
217 default:
218 NOTREACHED();
221 return value;
224 // Amount to adjust the clip by when the tab is stacked after the active index.
225 int stacked_tab_right_clip() {
226 static int value = -1;
227 if (value == -1) {
228 switch (ui::GetDisplayLayout()) {
229 case ui::LAYOUT_DESKTOP:
230 value = 20;
231 break;
232 case ui::LAYOUT_TOUCH:
233 value = 26;
234 break;
235 default:
236 NOTREACHED();
239 return value;
242 base::string16 GetClipboardText() {
243 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
244 return base::string16();
245 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
246 CHECK(clipboard);
247 base::string16 clipboard_text;
248 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
249 return clipboard_text;
252 // Animation delegate used when a dragged tab is released. When done sets the
253 // dragging state to false.
254 class ResetDraggingStateDelegate
255 : public views::BoundsAnimator::OwnedAnimationDelegate {
256 public:
257 explicit ResetDraggingStateDelegate(Tab* tab) : tab_(tab) {
260 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE {
261 tab_->set_dragging(false);
264 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE {
265 tab_->set_dragging(false);
268 private:
269 Tab* tab_;
271 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
274 // If |dest| contains the point |point_in_source| the event handler from |dest|
275 // is returned. Otherwise NULL is returned.
276 views::View* ConvertPointToViewAndGetEventHandler(
277 views::View* source,
278 views::View* dest,
279 const gfx::Point& point_in_source) {
280 gfx::Point dest_point(point_in_source);
281 views::View::ConvertPointToTarget(source, dest, &dest_point);
282 return dest->HitTestPoint(dest_point) ?
283 dest->GetEventHandlerForPoint(dest_point) : NULL;
286 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
287 // should return NULL if it does not contain the point.
288 views::View* ConvertPointToViewAndGetTooltipHandler(
289 views::View* source,
290 views::View* dest,
291 const gfx::Point& point_in_source) {
292 gfx::Point dest_point(point_in_source);
293 views::View::ConvertPointToTarget(source, dest, &dest_point);
294 return dest->GetTooltipHandlerForPoint(dest_point);
297 TabDragController::EventSource EventSourceFromEvent(
298 const ui::LocatedEvent& event) {
299 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
300 TabDragController::EVENT_SOURCE_MOUSE;
303 } // namespace
305 ///////////////////////////////////////////////////////////////////////////////
306 // NewTabButton
308 // A subclass of button that hit-tests to the shape of the new tab button and
309 // does custom drawing.
311 class NewTabButton : public views::ImageButton {
312 public:
313 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
314 virtual ~NewTabButton();
316 // Set the background offset used to match the background image to the frame
317 // image.
318 void set_background_offset(const gfx::Point& offset) {
319 background_offset_ = offset;
322 protected:
323 // Overridden from views::View:
324 virtual bool HasHitTestMask() const OVERRIDE;
325 virtual void GetHitTestMask(HitTestSource source,
326 gfx::Path* path) const OVERRIDE;
327 #if defined(OS_WIN)
328 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
329 #endif
330 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
332 // Overridden from ui::EventHandler:
333 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
335 private:
336 bool ShouldUseNativeFrame() const;
337 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
338 ui::ScaleFactor scale_factor) const;
339 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
340 ui::ScaleFactor scale_factor) const;
341 gfx::ImageSkia GetImageForScale(ui::ScaleFactor scale_factor) const;
343 // Tab strip that contains this button.
344 TabStrip* tab_strip_;
346 // The offset used to paint the background image.
347 gfx::Point background_offset_;
349 // were we destroyed?
350 bool* destroyed_;
352 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
355 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
356 : views::ImageButton(listener),
357 tab_strip_(tab_strip),
358 destroyed_(NULL) {
359 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
360 set_triggerable_event_flags(triggerable_event_flags() |
361 ui::EF_MIDDLE_MOUSE_BUTTON);
362 #endif
365 NewTabButton::~NewTabButton() {
366 if (destroyed_)
367 *destroyed_ = true;
370 bool NewTabButton::HasHitTestMask() const {
371 // When the button is sized to the top of the tab strip we want the user to
372 // be able to click on complete bounds, and so don't return a custom hit
373 // mask.
374 return !tab_strip_->SizeTabButtonToTopOfTabStrip();
377 void NewTabButton::GetHitTestMask(HitTestSource source, gfx::Path* path) const {
378 DCHECK(path);
380 SkScalar w = SkIntToScalar(width());
381 SkScalar v_offset = SkIntToScalar(newtab_button_v_offset());
383 // These values are defined by the shape of the new tab image. Should that
384 // image ever change, these values will need to be updated. They're so
385 // custom it's not really worth defining constants for.
386 // These values are correct for regular and USE_ASH versions of the image.
387 path->moveTo(0, v_offset + 1);
388 path->lineTo(w - 7, v_offset + 1);
389 path->lineTo(w - 4, v_offset + 4);
390 path->lineTo(w, v_offset + 16);
391 path->lineTo(w - 1, v_offset + 17);
392 path->lineTo(7, v_offset + 17);
393 path->lineTo(4, v_offset + 13);
394 path->lineTo(0, v_offset + 1);
395 path->close();
398 #if defined(OS_WIN)
399 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
400 if (event.IsOnlyRightMouseButton()) {
401 gfx::Point point = event.location();
402 views::View::ConvertPointToScreen(this, &point);
403 bool destroyed = false;
404 destroyed_ = &destroyed;
405 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
406 if (destroyed)
407 return;
409 destroyed_ = NULL;
410 SetState(views::CustomButton::STATE_NORMAL);
411 return;
413 views::ImageButton::OnMouseReleased(event);
415 #endif
417 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
418 gfx::ImageSkia image =
419 GetImageForScale(ui::GetSupportedScaleFactor(canvas->image_scale()));
420 canvas->DrawImageInt(image, 0, height() - image.height());
423 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
424 // Consume all gesture events here so that the parent (Tab) does not
425 // start consuming gestures.
426 views::ImageButton::OnGestureEvent(event);
427 event->SetHandled();
430 bool NewTabButton::ShouldUseNativeFrame() const {
431 return GetWidget() &&
432 GetWidget()->GetTopLevelWidget()->ShouldUseNativeFrame();
435 gfx::ImageSkia NewTabButton::GetBackgroundImage(
436 views::CustomButton::ButtonState state,
437 ui::ScaleFactor scale_factor) const {
438 int background_id = 0;
439 if (ShouldUseNativeFrame()) {
440 background_id = IDR_THEME_TAB_BACKGROUND_V;
441 } else if (tab_strip_->controller()->IsIncognito()) {
442 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
443 #if defined(OS_WIN)
444 } else if (win8::IsSingleWindowMetroMode()) {
445 background_id = IDR_THEME_TAB_BACKGROUND_V;
446 #endif
447 } else {
448 background_id = IDR_THEME_TAB_BACKGROUND;
451 int alpha = 0;
452 switch (state) {
453 case views::CustomButton::STATE_NORMAL:
454 case views::CustomButton::STATE_HOVERED:
455 alpha = ShouldUseNativeFrame() ? kNativeFrameInactiveTabAlpha : 255;
456 break;
457 case views::CustomButton::STATE_PRESSED:
458 alpha = 145;
459 break;
460 default:
461 NOTREACHED();
462 break;
465 gfx::ImageSkia* mask =
466 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
467 int height = mask->height();
468 int width = mask->width();
469 float scale = ui::GetImageScale(scale_factor);
470 // The canvas and mask has to use the same scale factor.
471 if (!mask->HasRepresentation(scale))
472 scale_factor = ui::SCALE_FACTOR_100P;
474 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
476 // For custom images the background starts at the top of the tab strip.
477 // Otherwise the background starts at the top of the frame.
478 gfx::ImageSkia* background =
479 GetThemeProvider()->GetImageSkiaNamed(background_id);
480 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
481 0 : background_offset_.y();
483 // The new tab background is mirrored in RTL mode, but the theme background
484 // should never be mirrored. Mirror it here to compensate.
485 float x_scale = 1.0f;
486 int x = GetMirroredX() + background_offset_.x();
487 if (base::i18n::IsRTL()) {
488 x_scale = -1.0f;
489 // Offset by |width| such that the same region is painted as if there was no
490 // flip.
491 x += width;
493 canvas.TileImageInt(*background, x, newtab_button_v_offset() + offset_y,
494 x_scale, 1.0f, 0, 0, width, height);
496 if (alpha != 255) {
497 SkPaint paint;
498 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
499 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
500 paint.setStyle(SkPaint::kFill_Style);
501 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
504 // White highlight on hover.
505 if (state == views::CustomButton::STATE_HOVERED)
506 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
508 return gfx::ImageSkiaOperations::CreateMaskedImage(
509 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
512 gfx::ImageSkia NewTabButton::GetImageForState(
513 views::CustomButton::ButtonState state,
514 ui::ScaleFactor scale_factor) const {
515 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
516 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
517 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
519 gfx::Canvas canvas(
520 gfx::Size(overlay->width(), overlay->height()),
521 ui::GetImageScale(scale_factor),
522 false);
523 canvas.DrawImageInt(GetBackgroundImage(state, scale_factor), 0, 0);
525 // Draw the button border with a slight alpha.
526 const int kNativeFrameOverlayAlpha = 178;
527 const int kOpaqueFrameOverlayAlpha = 230;
528 uint8 alpha = ShouldUseNativeFrame() ?
529 kNativeFrameOverlayAlpha : kOpaqueFrameOverlayAlpha;
530 canvas.DrawImageInt(*overlay, 0, 0, alpha);
532 return gfx::ImageSkia(canvas.ExtractImageRep());
535 gfx::ImageSkia NewTabButton::GetImageForScale(
536 ui::ScaleFactor scale_factor) const {
537 if (!hover_animation_->is_animating())
538 return GetImageForState(state(), scale_factor);
539 return gfx::ImageSkiaOperations::CreateBlendedImage(
540 GetImageForState(views::CustomButton::STATE_NORMAL, scale_factor),
541 GetImageForState(views::CustomButton::STATE_HOVERED, scale_factor),
542 hover_animation_->GetCurrentValue());
545 ///////////////////////////////////////////////////////////////////////////////
546 // TabStrip::RemoveTabDelegate
548 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
549 // done.
550 class TabStrip::RemoveTabDelegate
551 : public views::BoundsAnimator::OwnedAnimationDelegate {
552 public:
553 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
555 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
556 virtual void AnimationCanceled(const gfx::Animation* animation) OVERRIDE;
558 private:
559 void CompleteRemove();
561 // When the animation completes, we send the Container a message to simulate
562 // a mouse moved event at the current mouse position. This tickles the Tab
563 // the mouse is currently over to show the "hot" state of the close button.
564 void HighlightCloseButton();
566 TabStrip* tabstrip_;
567 Tab* tab_;
569 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
572 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
573 Tab* tab)
574 : tabstrip_(tab_strip),
575 tab_(tab) {
578 void TabStrip::RemoveTabDelegate::AnimationEnded(
579 const gfx::Animation* animation) {
580 CompleteRemove();
583 void TabStrip::RemoveTabDelegate::AnimationCanceled(
584 const gfx::Animation* animation) {
585 CompleteRemove();
588 void TabStrip::RemoveTabDelegate::CompleteRemove() {
589 DCHECK(tab_->closing());
590 tabstrip_->RemoveAndDeleteTab(tab_);
591 HighlightCloseButton();
594 void TabStrip::RemoveTabDelegate::HighlightCloseButton() {
595 if (tabstrip_->IsDragSessionActive() ||
596 !tabstrip_->ShouldHighlightCloseButtonAfterRemove()) {
597 // This function is not required (and indeed may crash!) for removes
598 // spawned by non-mouse closes and drag-detaches.
599 return;
602 views::Widget* widget = tabstrip_->GetWidget();
603 // This can be null during shutdown. See http://crbug.com/42737.
604 if (!widget)
605 return;
607 widget->SynthesizeMouseMoveEvent();
610 ///////////////////////////////////////////////////////////////////////////////
611 // TabStrip, public:
613 // static
614 const char TabStrip::kViewClassName[] = "TabStrip";
616 // static
617 const int TabStrip::kMiniToNonMiniGap = 3;
619 TabStrip::TabStrip(TabStripController* controller)
620 : controller_(controller),
621 newtab_button_(NULL),
622 current_unselected_width_(Tab::GetStandardSize().width()),
623 current_selected_width_(Tab::GetStandardSize().width()),
624 available_width_for_tabs_(-1),
625 in_tab_close_(false),
626 animation_container_(new gfx::AnimationContainer()),
627 bounds_animator_(this),
628 layout_type_(TAB_STRIP_LAYOUT_SHRINK),
629 adjust_layout_(false),
630 reset_to_shrink_on_exit_(false),
631 mouse_move_count_(0),
632 immersive_style_(false) {
633 Init();
636 TabStrip::~TabStrip() {
637 FOR_EACH_OBSERVER(TabStripObserver, observers_,
638 TabStripDeleted(this));
640 // The animations may reference the tabs. Shut down the animation before we
641 // delete the tabs.
642 StopAnimating(false);
644 DestroyDragController();
646 // Make sure we unhook ourselves as a message loop observer so that we don't
647 // crash in the case where the user closes the window after closing a tab
648 // but before moving the mouse.
649 RemoveMessageLoopObserver();
651 // The children (tabs) may callback to us from their destructor. Delete them
652 // so that if they call back we aren't in a weird state.
653 RemoveAllChildViews(true);
656 void TabStrip::AddObserver(TabStripObserver* observer) {
657 observers_.AddObserver(observer);
660 void TabStrip::RemoveObserver(TabStripObserver* observer) {
661 observers_.RemoveObserver(observer);
664 void TabStrip::SetLayoutType(TabStripLayoutType layout_type,
665 bool adjust_layout) {
666 adjust_layout_ = adjust_layout;
667 if (layout_type == layout_type_)
668 return;
670 const int active_index = controller_->GetActiveIndex();
671 int active_center = 0;
672 if (active_index != -1) {
673 active_center = ideal_bounds(active_index).x() +
674 ideal_bounds(active_index).width() / 2;
676 layout_type_ = layout_type;
677 SetResetToShrinkOnExit(false);
678 SwapLayoutIfNecessary();
679 // When transitioning to stacked try to keep the active tab centered.
680 if (touch_layout_.get() && active_index != -1) {
681 touch_layout_->SetActiveTabLocation(
682 active_center - ideal_bounds(active_index).width() / 2);
683 AnimateToIdealBounds();
687 gfx::Rect TabStrip::GetNewTabButtonBounds() {
688 return newtab_button_->bounds();
691 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
692 // Extend the button to the screen edge in maximized and immersive fullscreen.
693 views::Widget* widget = GetWidget();
694 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
695 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
698 void TabStrip::StartHighlight(int model_index) {
699 tab_at(model_index)->StartPulse();
702 void TabStrip::StopAllHighlighting() {
703 for (int i = 0; i < tab_count(); ++i)
704 tab_at(i)->StopPulse();
707 void TabStrip::AddTabAt(int model_index,
708 const TabRendererData& data,
709 bool is_active) {
710 // Stop dragging when a new tab is added and dragging a window. Doing
711 // otherwise results in a confusing state if the user attempts to reattach. We
712 // could allow this and make TabDragController update itself during the add,
713 // but this comes up infrequently enough that it's not work the complexity.
714 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
715 drag_controller_->is_dragging_window()) {
716 EndDrag(END_DRAG_COMPLETE);
718 Tab* tab = CreateTab();
719 tab->SetData(data);
720 UpdateTabsClosingMap(model_index, 1);
721 tabs_.Add(tab, model_index);
722 AddChildView(tab);
724 if (touch_layout_.get()) {
725 GenerateIdealBoundsForMiniTabs(NULL);
726 int add_types = 0;
727 if (data.mini)
728 add_types |= StackedTabStripLayout::kAddTypeMini;
729 if (is_active)
730 add_types |= StackedTabStripLayout::kAddTypeActive;
731 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
734 // Don't animate the first tab, it looks weird, and don't animate anything
735 // if the containing window isn't visible yet.
736 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
737 StartInsertTabAnimation(model_index);
738 else
739 DoLayout();
741 SwapLayoutIfNecessary();
743 FOR_EACH_OBSERVER(TabStripObserver, observers_,
744 TabStripAddedTabAt(this, model_index));
747 void TabStrip::MoveTab(int from_model_index,
748 int to_model_index,
749 const TabRendererData& data) {
750 DCHECK_GT(tabs_.view_size(), 0);
751 Tab* last_tab = tab_at(tab_count() - 1);
752 tab_at(from_model_index)->SetData(data);
753 if (touch_layout_.get()) {
754 tabs_.MoveViewOnly(from_model_index, to_model_index);
755 int mini_count = 0;
756 GenerateIdealBoundsForMiniTabs(&mini_count);
757 touch_layout_->MoveTab(
758 from_model_index, to_model_index, controller_->GetActiveIndex(),
759 GetStartXForNormalTabs(), mini_count);
760 } else {
761 tabs_.Move(from_model_index, to_model_index);
763 StartMoveTabAnimation();
764 if (TabDragController::IsAttachedTo(this) &&
765 (last_tab != tab_at(tab_count() - 1) || last_tab->dragging())) {
766 newtab_button_->SetVisible(false);
768 SwapLayoutIfNecessary();
770 FOR_EACH_OBSERVER(TabStripObserver, observers_,
771 TabStripMovedTab(this, from_model_index, to_model_index));
774 void TabStrip::RemoveTabAt(int model_index) {
775 if (touch_layout_.get()) {
776 Tab* tab = tab_at(model_index);
777 tab->set_closing(true);
778 int old_x = tabs_.ideal_bounds(model_index).x();
779 // We still need to paint the tab until we actually remove it. Put it in
780 // tabs_closing_map_ so we can find it.
781 RemoveTabFromViewModel(model_index);
782 touch_layout_->RemoveTab(model_index, GenerateIdealBoundsForMiniTabs(NULL),
783 old_x);
784 ScheduleRemoveTabAnimation(tab);
785 } else if (in_tab_close_ && model_index != GetModelCount()) {
786 StartMouseInitiatedRemoveTabAnimation(model_index);
787 } else {
788 StartRemoveTabAnimation(model_index);
790 SwapLayoutIfNecessary();
792 FOR_EACH_OBSERVER(TabStripObserver, observers_,
793 TabStripRemovedTabAt(this, model_index));
796 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
797 Tab* tab = tab_at(model_index);
798 bool mini_state_changed = tab->data().mini != data.mini;
799 tab->SetData(data);
801 if (mini_state_changed) {
802 if (touch_layout_.get()) {
803 int mini_tab_count = 0;
804 int start_x = GenerateIdealBoundsForMiniTabs(&mini_tab_count);
805 touch_layout_->SetXAndMiniCount(start_x, mini_tab_count);
807 if (GetWidget() && GetWidget()->IsVisible())
808 StartMiniTabAnimation();
809 else
810 DoLayout();
812 SwapLayoutIfNecessary();
815 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
816 if (!in_tab_close_ && IsAnimating()) {
817 // Cancel any current animations. We do this as remove uses the current
818 // ideal bounds and we need to know ideal bounds is in a good state.
819 StopAnimating(true);
822 if (!GetWidget())
823 return;
825 int model_count = GetModelCount();
826 if (model_index + 1 != model_count && model_count > 1) {
827 // The user is about to close a tab other than the last tab. Set
828 // available_width_for_tabs_ so that if we do a layout we don't position a
829 // tab past the end of the second to last tab. We do this so that as the
830 // user closes tabs with the mouse a tab continues to fall under the mouse.
831 Tab* last_tab = tab_at(model_count - 1);
832 Tab* tab_being_removed = tab_at(model_index);
833 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
834 tab_being_removed->width() - tab_h_offset();
835 if (model_index == 0 && tab_being_removed->data().mini &&
836 !tab_at(1)->data().mini) {
837 available_width_for_tabs_ -= kMiniToNonMiniGap;
841 in_tab_close_ = true;
842 resize_layout_timer_.Stop();
843 if (source == CLOSE_TAB_FROM_TOUCH) {
844 StartResizeLayoutTabsFromTouchTimer();
845 } else {
846 AddMessageLoopObserver();
850 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
851 const ui::ListSelectionModel& new_selection) {
852 if (touch_layout_.get()) {
853 touch_layout_->SetActiveIndex(new_selection.active());
854 // Only start an animation if we need to. Otherwise clicking on an
855 // unselected tab and dragging won't work because dragging is only allowed
856 // if not animating.
857 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
858 AnimateToIdealBounds();
859 SchedulePaint();
860 } else {
861 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
862 // a different size to the selected ones.
863 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
864 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
865 DoLayout();
866 } else {
867 SchedulePaint();
871 ui::ListSelectionModel::SelectedIndices no_longer_selected =
872 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
873 old_selection.selected_indices(),
874 new_selection.selected_indices());
875 for (size_t i = 0; i < no_longer_selected.size(); ++i)
876 tab_at(no_longer_selected[i])->StopMiniTabTitleAnimation();
879 void TabStrip::TabTitleChangedNotLoading(int model_index) {
880 Tab* tab = tab_at(model_index);
881 if (tab->data().mini && !tab->IsActive())
882 tab->StartMiniTabTitleAnimation();
885 Tab* TabStrip::tab_at(int index) const {
886 return static_cast<Tab*>(tabs_.view_at(index));
889 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
890 return tabs_.GetIndexOfView(tab);
893 int TabStrip::GetModelCount() const {
894 return controller_->GetCount();
897 bool TabStrip::IsValidModelIndex(int model_index) const {
898 return controller_->IsValidIndex(model_index);
901 Tab* TabStrip::CreateTabForDragging() {
902 Tab* tab = new Tab(NULL);
903 // Make sure the dragged tab shares our theme provider. We need to explicitly
904 // do this as during dragging there isn't a theme provider.
905 tab->set_theme_provider(GetThemeProvider());
906 return tab;
909 bool TabStrip::IsDragSessionActive() const {
910 return drag_controller_.get() != NULL;
913 bool TabStrip::IsActiveDropTarget() const {
914 for (int i = 0; i < tab_count(); ++i) {
915 Tab* tab = tab_at(i);
916 if (tab->dragging())
917 return true;
919 return false;
922 bool TabStrip::IsTabStripEditable() const {
923 return !IsDragSessionActive() && !IsActiveDropTarget();
926 bool TabStrip::IsTabStripCloseable() const {
927 return !IsDragSessionActive();
930 void TabStrip::UpdateLoadingAnimations() {
931 controller_->UpdateLoadingAnimations();
934 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
935 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
938 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
939 views::View* v = GetEventHandlerForRect(rect);
941 // If there is no control at this location, claim the hit was in the title
942 // bar to get a move action.
943 if (v == this)
944 return true;
946 // Check to see if the rect intersects the non-button parts of the new tab
947 // button. The button has a non-rectangular shape, so if it's not in the
948 // visual portions of the button we treat it as a click to the caption.
949 gfx::RectF rect_in_newtab_coords_f(rect);
950 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
951 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
952 rect_in_newtab_coords_f);
953 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
954 !newtab_button_->HitTestRect(rect_in_newtab_coords))
955 return true;
957 // All other regions, including the new Tab button, should be considered part
958 // of the containing Window's client area so that regular events can be
959 // processed for them.
960 return false;
963 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
964 for (int i = 0; i < tab_count(); ++i)
965 tab_at(i)->set_background_offset(offset);
966 newtab_button_->set_background_offset(offset);
969 views::View* TabStrip::newtab_button() {
970 return newtab_button_;
973 void TabStrip::SetImmersiveStyle(bool enable) {
974 if (immersive_style_ == enable)
975 return;
976 immersive_style_ = enable;
979 bool TabStrip::IsAnimating() const {
980 return bounds_animator_.IsAnimating();
983 void TabStrip::StopAnimating(bool layout) {
984 if (!IsAnimating())
985 return;
987 bounds_animator_.Cancel();
989 if (layout)
990 DoLayout();
993 void TabStrip::FileSupported(const GURL& url, bool supported) {
994 if (drop_info_.get() && drop_info_->url == url)
995 drop_info_->file_supported = supported;
998 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
999 return controller_->GetSelectionModel();
1002 bool TabStrip::SupportsMultipleSelection() {
1003 // TODO: currently only allow single selection in touch layout mode.
1004 return touch_layout_.get() == NULL;
1007 void TabStrip::SelectTab(Tab* tab) {
1008 int model_index = GetModelIndexOfTab(tab);
1009 if (IsValidModelIndex(model_index))
1010 controller_->SelectTab(model_index);
1013 void TabStrip::ExtendSelectionTo(Tab* tab) {
1014 int model_index = GetModelIndexOfTab(tab);
1015 if (IsValidModelIndex(model_index))
1016 controller_->ExtendSelectionTo(model_index);
1019 void TabStrip::ToggleSelected(Tab* tab) {
1020 int model_index = GetModelIndexOfTab(tab);
1021 if (IsValidModelIndex(model_index))
1022 controller_->ToggleSelected(model_index);
1025 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
1026 int model_index = GetModelIndexOfTab(tab);
1027 if (IsValidModelIndex(model_index))
1028 controller_->AddSelectionFromAnchorTo(model_index);
1031 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
1032 if (tab->closing()) {
1033 // If the tab is already closing, close the next tab. We do this so that the
1034 // user can rapidly close tabs by clicking the close button and not have
1035 // the animations interfere with that.
1036 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1037 i != tabs_closing_map_.end(); ++i) {
1038 std::vector<Tab*>::const_iterator j =
1039 std::find(i->second.begin(), i->second.end(), tab);
1040 if (j != i->second.end()) {
1041 if (i->first + 1 < GetModelCount())
1042 controller_->CloseTab(i->first + 1, source);
1043 return;
1046 // If we get here, it means a tab has been marked as closing but isn't in
1047 // the set of known closing tabs.
1048 NOTREACHED();
1049 return;
1051 int model_index = GetModelIndexOfTab(tab);
1052 if (IsValidModelIndex(model_index))
1053 controller_->CloseTab(model_index, source);
1056 void TabStrip::ShowContextMenuForTab(Tab* tab,
1057 const gfx::Point& p,
1058 ui::MenuSourceType source_type) {
1059 controller_->ShowContextMenuForTab(tab, p, source_type);
1062 bool TabStrip::IsActiveTab(const Tab* tab) const {
1063 int model_index = GetModelIndexOfTab(tab);
1064 return IsValidModelIndex(model_index) &&
1065 controller_->IsActiveTab(model_index);
1068 bool TabStrip::IsTabSelected(const Tab* tab) const {
1069 int model_index = GetModelIndexOfTab(tab);
1070 return IsValidModelIndex(model_index) &&
1071 controller_->IsTabSelected(model_index);
1074 bool TabStrip::IsTabPinned(const Tab* tab) const {
1075 if (tab->closing())
1076 return false;
1078 int model_index = GetModelIndexOfTab(tab);
1079 return IsValidModelIndex(model_index) &&
1080 controller_->IsTabPinned(model_index);
1083 void TabStrip::MaybeStartDrag(
1084 Tab* tab,
1085 const ui::LocatedEvent& event,
1086 const ui::ListSelectionModel& original_selection) {
1087 // Don't accidentally start any drag operations during animations if the
1088 // mouse is down... during an animation tabs are being resized automatically,
1089 // so the View system can misinterpret this easily if the mouse is down that
1090 // the user is dragging.
1091 if (IsAnimating() || tab->closing() ||
1092 controller_->HasAvailableDragActions() == 0) {
1093 return;
1096 // Do not do any dragging of tabs when using the super short immersive style.
1097 if (IsImmersiveStyle())
1098 return;
1100 int model_index = GetModelIndexOfTab(tab);
1101 if (!IsValidModelIndex(model_index)) {
1102 CHECK(false);
1103 return;
1105 std::vector<Tab*> tabs;
1106 int size_to_selected = 0;
1107 int x = tab->GetMirroredXInView(event.x());
1108 int y = event.y();
1109 // Build the set of selected tabs to drag and calculate the offset from the
1110 // first selected tab.
1111 for (int i = 0; i < tab_count(); ++i) {
1112 Tab* other_tab = tab_at(i);
1113 if (IsTabSelected(other_tab)) {
1114 tabs.push_back(other_tab);
1115 if (other_tab == tab) {
1116 size_to_selected = GetSizeNeededForTabs(tabs);
1117 x = size_to_selected - tab->width() + x;
1121 DCHECK(!tabs.empty());
1122 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1123 ui::ListSelectionModel selection_model;
1124 if (!original_selection.IsSelected(model_index))
1125 selection_model.Copy(original_selection);
1126 // Delete the existing DragController before creating a new one. We do this as
1127 // creating the DragController remembers the WebContents delegates and we need
1128 // to make sure the existing DragController isn't still a delegate.
1129 drag_controller_.reset();
1130 TabDragController::DetachBehavior detach_behavior =
1131 TabDragController::DETACHABLE;
1132 TabDragController::MoveBehavior move_behavior =
1133 TabDragController::REORDER;
1134 // Use MOVE_VISIBILE_TABS in the following conditions:
1135 // . Mouse event generated from touch and the left button is down (the right
1136 // button corresponds to a long press, which we want to reorder).
1137 // . Gesture begin and control key isn't down.
1138 // . Real mouse event and control is down. This is mostly for testing.
1139 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1140 event.type() == ui::ET_GESTURE_BEGIN);
1141 if (touch_layout_.get() &&
1142 ((event.type() == ui::ET_MOUSE_PRESSED &&
1143 (((event.flags() & ui::EF_FROM_TOUCH) &&
1144 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1145 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1146 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1147 (event.type() == ui::ET_GESTURE_BEGIN && !event.IsControlDown()))) {
1148 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1151 views::Widget* widget = GetWidget();
1152 #if defined(OS_WIN)
1153 // It doesn't make sense to drag tabs out on Win8's single window Metro mode.
1154 if (win8::IsSingleWindowMetroMode())
1155 detach_behavior = TabDragController::NOT_DETACHABLE;
1156 #endif
1157 // Gestures don't automatically do a capture. We don't allow multiple drags at
1158 // the same time, so we explicitly capture.
1159 if (event.type() == ui::ET_GESTURE_BEGIN)
1160 widget->SetCapture(this);
1161 drag_controller_.reset(new TabDragController);
1162 drag_controller_->Init(
1163 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1164 detach_behavior, move_behavior, EventSourceFromEvent(event));
1167 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1168 if (drag_controller_.get() &&
1169 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1170 gfx::Point screen_location(event.location());
1171 views::View::ConvertPointToScreen(view, &screen_location);
1172 drag_controller_->Drag(screen_location);
1176 bool TabStrip::EndDrag(EndDragReason reason) {
1177 if (!drag_controller_.get())
1178 return false;
1179 bool started_drag = drag_controller_->started_drag();
1180 drag_controller_->EndDrag(reason);
1181 return started_drag;
1184 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1185 gfx::Point local_point = tab_in_tab_coordinates;
1186 ConvertPointToTarget(tab, this, &local_point);
1188 views::View* view = GetEventHandlerForPoint(local_point);
1189 if (!view)
1190 return NULL; // No tab contains the point.
1192 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1193 while (view && view != this && view->id() != VIEW_ID_TAB)
1194 view = view->parent();
1196 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1199 void TabStrip::OnMouseEventInTab(views::View* source,
1200 const ui::MouseEvent& event) {
1201 UpdateLayoutTypeFromMouseEvent(source, event);
1204 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1205 // Only touch layout needs to restrict the clip.
1206 if (!(touch_layout_.get() || IsStackingDraggedTabs()))
1207 return true;
1209 int index = GetModelIndexOfTab(tab);
1210 if (index == -1)
1211 return true; // Tab is closing, paint it all.
1213 int active_index = IsStackingDraggedTabs() ?
1214 controller_->GetActiveIndex() : touch_layout_->active_index();
1215 if (active_index == tab_count())
1216 active_index--;
1218 if (index < active_index) {
1219 if (tab_at(index)->x() == tab_at(index + 1)->x())
1220 return false;
1222 if (tab_at(index)->x() > tab_at(index + 1)->x())
1223 return true; // Can happen during dragging.
1225 clip->SetRect(0, 0, tab_at(index + 1)->x() - tab_at(index)->x() +
1226 stacked_tab_left_clip(),
1227 tab_at(index)->height());
1228 } else if (index > active_index && index > 0) {
1229 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1230 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1231 if (tab_bounds.x() == previous_tab_bounds.x())
1232 return false;
1234 if (tab_bounds.x() < previous_tab_bounds.x())
1235 return true; // Can happen during dragging.
1237 if (previous_tab_bounds.right() + tab_h_offset() != tab_bounds.x()) {
1238 int x = previous_tab_bounds.right() - tab_bounds.x() -
1239 stacked_tab_right_clip();
1240 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1243 return true;
1246 bool TabStrip::IsImmersiveStyle() const {
1247 return immersive_style_;
1250 void TabStrip::MouseMovedOutOfHost() {
1251 ResizeLayoutTabs();
1252 if (reset_to_shrink_on_exit_) {
1253 reset_to_shrink_on_exit_ = false;
1254 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true);
1255 controller_->LayoutTypeMaybeChanged();
1259 ///////////////////////////////////////////////////////////////////////////////
1260 // TabStrip, views::View overrides:
1262 void TabStrip::Layout() {
1263 // Only do a layout if our size changed.
1264 if (last_layout_size_ == size())
1265 return;
1266 if (IsDragSessionActive())
1267 return;
1268 DoLayout();
1271 void TabStrip::PaintChildren(gfx::Canvas* canvas) {
1272 // The view order doesn't match the paint order (tabs_ contains the tab
1273 // ordering). Additionally we need to paint the tabs that are closing in
1274 // |tabs_closing_map_|.
1275 Tab* active_tab = NULL;
1276 std::vector<Tab*> tabs_dragging;
1277 std::vector<Tab*> selected_tabs;
1278 int selected_tab_count = 0;
1279 bool is_dragging = false;
1280 int active_tab_index = -1;
1281 // Since |touch_layout_| is created based on number of tabs and width we use
1282 // the ideal state to determine if we should paint stacked. This minimizes
1283 // painting changes as we switch between the two.
1284 const bool stacking = (layout_type_ == TAB_STRIP_LAYOUT_STACKED) ||
1285 IsStackingDraggedTabs();
1287 const chrome::HostDesktopType host_desktop_type =
1288 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1289 const int inactive_tab_alpha =
1290 host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH ?
1291 kInactiveTabAndNewTabButtonAlphaAsh :
1292 kInactiveTabAndNewTabButtonAlpha;
1294 if (inactive_tab_alpha < 255)
1295 canvas->SaveLayerAlpha(inactive_tab_alpha);
1297 PaintClosingTabs(canvas, tab_count());
1299 for (int i = tab_count() - 1; i >= 0; --i) {
1300 Tab* tab = tab_at(i);
1301 if (tab->IsSelected())
1302 selected_tab_count++;
1303 if (tab->dragging() && !stacking) {
1304 is_dragging = true;
1305 if (tab->IsActive()) {
1306 active_tab = tab;
1307 active_tab_index = i;
1308 } else {
1309 tabs_dragging.push_back(tab);
1311 } else if (!tab->IsActive()) {
1312 if (!tab->IsSelected()) {
1313 if (!stacking)
1314 tab->Paint(canvas);
1315 } else {
1316 selected_tabs.push_back(tab);
1318 } else {
1319 active_tab = tab;
1320 active_tab_index = i;
1322 PaintClosingTabs(canvas, i);
1325 // Draw from the left and then the right if we're in touch mode.
1326 if (stacking && active_tab_index >= 0) {
1327 for (int i = 0; i < active_tab_index; ++i) {
1328 Tab* tab = tab_at(i);
1329 tab->Paint(canvas);
1332 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1333 Tab* tab = tab_at(i);
1334 tab->Paint(canvas);
1337 if (inactive_tab_alpha < 255)
1338 canvas->Restore();
1340 if (GetWidget()->ShouldUseNativeFrame()) {
1341 // Make sure non-active tabs are somewhat transparent.
1342 SkPaint paint;
1343 // If there are multiple tabs selected, fade non-selected tabs more to make
1344 // the selected tabs more noticable.
1345 int alpha = selected_tab_count > 1 ?
1346 kNativeFrameInactiveTabAlphaMultiSelection :
1347 kNativeFrameInactiveTabAlpha;
1348 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1349 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1350 paint.setStyle(SkPaint::kFill_Style);
1351 // The tabstrip area overlaps the toolbar area by 2 px.
1352 canvas->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint);
1355 // Now selected but not active. We don't want these dimmed if using native
1356 // frame, so they're painted after initial pass.
1357 for (size_t i = 0; i < selected_tabs.size(); ++i)
1358 selected_tabs[i]->Paint(canvas);
1360 // Next comes the active tab.
1361 if (active_tab && !is_dragging)
1362 active_tab->Paint(canvas);
1364 // Paint the New Tab button.
1365 if (inactive_tab_alpha < 255)
1366 canvas->SaveLayerAlpha(inactive_tab_alpha);
1367 newtab_button_->Paint(canvas);
1368 if (inactive_tab_alpha < 255)
1369 canvas->Restore();
1371 // And the dragged tabs.
1372 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1373 tabs_dragging[i]->Paint(canvas);
1375 // If the active tab is being dragged, it goes last.
1376 if (active_tab && is_dragging)
1377 active_tab->Paint(canvas);
1380 const char* TabStrip::GetClassName() const {
1381 return kViewClassName;
1384 gfx::Size TabStrip::GetPreferredSize() {
1385 // For stacked tabs the minimum size is calculated as the size needed to
1386 // handle showing any number of tabs. Otherwise report the minimum width as
1387 // the size required for a single selected tab plus the new tab button. Don't
1388 // base it on the actual number of tabs because it's undesirable to have the
1389 // minimum window size change when a new tab is opened.
1390 int needed_width;
1391 if (touch_layout_.get() || adjust_layout_) {
1392 needed_width = Tab::GetTouchWidth() +
1393 (2 * kStackedPadding * kMaxStackedCount);
1394 } else {
1395 needed_width = Tab::GetMinimumSelectedSize().width();
1397 needed_width += new_tab_button_width();
1398 if (immersive_style_)
1399 return gfx::Size(needed_width, Tab::GetImmersiveHeight());
1400 return gfx::Size(needed_width, Tab::GetMinimumUnselectedSize().height());
1403 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1404 // Force animations to stop, otherwise it makes the index calculation tricky.
1405 StopAnimating(true);
1407 UpdateDropIndex(event);
1409 GURL url;
1410 base::string16 title;
1412 // Check whether the event data includes supported drop data.
1413 if (event.data().GetURLAndTitle(
1414 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1415 url.is_valid()) {
1416 drop_info_->url = url;
1418 // For file:// URLs, kick off a MIME type request in case they're dropped.
1419 if (url.SchemeIsFile())
1420 controller()->CheckFileSupported(url);
1424 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1425 // Update the drop index even if the file is unsupported, to allow
1426 // dragging a file to the contents of another tab.
1427 UpdateDropIndex(event);
1429 if (!drop_info_->file_supported)
1430 return ui::DragDropTypes::DRAG_NONE;
1432 return GetDropEffect(event);
1435 void TabStrip::OnDragExited() {
1436 SetDropIndex(-1, false);
1439 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1440 if (!drop_info_.get())
1441 return ui::DragDropTypes::DRAG_NONE;
1443 const int drop_index = drop_info_->drop_index;
1444 const bool drop_before = drop_info_->drop_before;
1445 const bool file_supported = drop_info_->file_supported;
1447 // Hide the drop indicator.
1448 SetDropIndex(-1, false);
1450 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1451 // have been changed after |drop_info_| was created.
1452 GURL url;
1453 base::string16 title;
1454 if (!file_supported ||
1455 !event.data().GetURLAndTitle(
1456 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1457 !url.is_valid())
1458 return ui::DragDropTypes::DRAG_NONE;
1460 controller()->PerformDrop(drop_before, drop_index, url);
1462 return GetDropEffect(event);
1465 void TabStrip::GetAccessibleState(ui::AccessibleViewState* state) {
1466 state->role = ui::AccessibilityTypes::ROLE_PAGETABLIST;
1469 views::View* TabStrip::GetEventHandlerForRect(const gfx::Rect& rect) {
1470 if (!views::UsePointBasedTargeting(rect))
1471 return View::GetEventHandlerForRect(rect);
1472 const gfx::Point point(rect.CenterPoint());
1474 if (!touch_layout_.get()) {
1475 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1476 // want to interfere.
1477 views::View* v = View::GetEventHandlerForRect(rect);
1478 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1479 return v;
1481 views::View* tab = FindTabHitByPoint(point);
1482 if (tab)
1483 return tab;
1484 } else {
1485 if (newtab_button_->visible()) {
1486 views::View* view =
1487 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
1488 if (view)
1489 return view;
1491 Tab* tab = FindTabForEvent(point);
1492 if (tab)
1493 return ConvertPointToViewAndGetEventHandler(this, tab, point);
1495 return this;
1498 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1499 if (!HitTestPoint(point))
1500 return NULL;
1502 if (!touch_layout_.get()) {
1503 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1504 // want to interfere.
1505 views::View* v = View::GetTooltipHandlerForPoint(point);
1506 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1507 return v;
1509 views::View* tab = FindTabHitByPoint(point);
1510 if (tab)
1511 return tab;
1512 } else {
1513 if (newtab_button_->visible()) {
1514 views::View* view =
1515 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1516 if (view)
1517 return view;
1519 Tab* tab = FindTabForEvent(point);
1520 if (tab)
1521 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1523 return this;
1526 // static
1527 int TabStrip::GetImmersiveHeight() {
1528 return Tab::GetImmersiveHeight();
1531 int TabStrip::GetMiniTabCount() const {
1532 int mini_count = 0;
1533 while (mini_count < tab_count() && tab_at(mini_count)->data().mini)
1534 mini_count++;
1535 return mini_count;
1538 ///////////////////////////////////////////////////////////////////////////////
1539 // TabStrip, views::ButtonListener implementation:
1541 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
1542 if (sender == newtab_button_) {
1543 content::RecordAction(UserMetricsAction("NewTab_Button"));
1544 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
1545 TabStripModel::NEW_TAB_ENUM_COUNT);
1546 if (event.IsMouseEvent()) {
1547 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
1548 if (mouse.IsOnlyMiddleMouseButton()) {
1549 base::string16 clipboard_text = GetClipboardText();
1550 if (!clipboard_text.empty())
1551 controller()->CreateNewTabWithLocation(clipboard_text);
1552 return;
1556 controller()->CreateNewTab();
1557 if (event.type() == ui::ET_GESTURE_TAP)
1558 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
1562 ///////////////////////////////////////////////////////////////////////////////
1563 // TabStrip, protected:
1565 // Overridden to support automation. See automation_proxy_uitest.cc.
1566 const views::View* TabStrip::GetViewByID(int view_id) const {
1567 if (tab_count() > 0) {
1568 if (view_id == VIEW_ID_TAB_LAST) {
1569 return tab_at(tab_count() - 1);
1570 } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
1571 int index = view_id - VIEW_ID_TAB_0;
1572 if (index >= 0 && index < tab_count()) {
1573 return tab_at(index);
1574 } else {
1575 return NULL;
1580 return View::GetViewByID(view_id);
1583 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
1584 UpdateLayoutTypeFromMouseEvent(this, event);
1585 // We can't return true here, else clicking in an empty area won't drag the
1586 // window.
1587 return false;
1590 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
1591 ContinueDrag(this, event);
1592 return true;
1595 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
1596 EndDrag(END_DRAG_COMPLETE);
1597 UpdateLayoutTypeFromMouseEvent(this, event);
1600 void TabStrip::OnMouseCaptureLost() {
1601 EndDrag(END_DRAG_CAPTURE_LOST);
1604 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
1605 UpdateLayoutTypeFromMouseEvent(this, event);
1608 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
1609 SetResetToShrinkOnExit(true);
1612 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
1613 SetResetToShrinkOnExit(false);
1614 switch (event->type()) {
1615 case ui::ET_GESTURE_SCROLL_END:
1616 case ui::ET_SCROLL_FLING_START:
1617 case ui::ET_GESTURE_END:
1618 EndDrag(END_DRAG_COMPLETE);
1619 if (adjust_layout_) {
1620 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true);
1621 controller_->LayoutTypeMaybeChanged();
1623 break;
1625 case ui::ET_GESTURE_LONG_PRESS:
1626 if (drag_controller_.get())
1627 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
1628 break;
1630 case ui::ET_GESTURE_LONG_TAP: {
1631 EndDrag(END_DRAG_CANCEL);
1632 gfx::Point local_point = event->location();
1633 Tab* tab = FindTabForEvent(local_point);
1634 if (tab) {
1635 ConvertPointToScreen(this, &local_point);
1636 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
1638 break;
1641 case ui::ET_GESTURE_SCROLL_UPDATE:
1642 ContinueDrag(this, *event);
1643 break;
1645 case ui::ET_GESTURE_BEGIN:
1646 EndDrag(END_DRAG_CANCEL);
1647 break;
1649 case ui::ET_GESTURE_TAP: {
1650 const int active_index = controller_->GetActiveIndex();
1651 DCHECK_NE(-1, active_index);
1652 Tab* active_tab = tab_at(active_index);
1653 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
1654 if (active_tab->tab_activated_with_last_gesture_begin())
1655 action = TouchUMA::GESTURE_TABSWITCH_TAP;
1656 TouchUMA::RecordGestureAction(action);
1657 break;
1660 default:
1661 break;
1663 event->SetHandled();
1666 ///////////////////////////////////////////////////////////////////////////////
1667 // TabStrip, private:
1669 void TabStrip::Init() {
1670 set_id(VIEW_ID_TAB_STRIP);
1671 // So we get enter/exit on children to switch layout type.
1672 set_notify_enter_exit_on_child(true);
1673 newtab_button_bounds_.SetRect(0,
1675 newtab_button_asset_width(),
1676 newtab_button_asset_height() +
1677 newtab_button_v_offset());
1678 newtab_button_ = new NewTabButton(this, this);
1679 newtab_button_->SetTooltipText(
1680 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1681 newtab_button_->SetAccessibleName(
1682 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1683 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1684 views::ImageButton::ALIGN_BOTTOM);
1685 AddChildView(newtab_button_);
1686 if (drop_indicator_width == 0) {
1687 // Direction doesn't matter, both images are the same size.
1688 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1689 drop_indicator_width = drop_image->width();
1690 drop_indicator_height = drop_image->height();
1694 Tab* TabStrip::CreateTab() {
1695 Tab* tab = new Tab(this);
1696 tab->set_animation_container(animation_container_.get());
1697 return tab;
1700 void TabStrip::StartInsertTabAnimation(int model_index) {
1701 PrepareForAnimation();
1703 // The TabStrip can now use its entire width to lay out Tabs.
1704 in_tab_close_ = false;
1705 available_width_for_tabs_ = -1;
1707 GenerateIdealBounds();
1709 Tab* tab = tab_at(model_index);
1710 if (model_index == 0) {
1711 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1712 ideal_bounds(model_index).height());
1713 } else {
1714 Tab* last_tab = tab_at(model_index - 1);
1715 tab->SetBounds(last_tab->bounds().right() + tab_h_offset(),
1716 ideal_bounds(model_index).y(), 0,
1717 ideal_bounds(model_index).height());
1720 AnimateToIdealBounds();
1723 void TabStrip::StartMoveTabAnimation() {
1724 PrepareForAnimation();
1725 GenerateIdealBounds();
1726 AnimateToIdealBounds();
1729 void TabStrip::StartRemoveTabAnimation(int model_index) {
1730 PrepareForAnimation();
1732 // Mark the tab as closing.
1733 Tab* tab = tab_at(model_index);
1734 tab->set_closing(true);
1736 RemoveTabFromViewModel(model_index);
1738 ScheduleRemoveTabAnimation(tab);
1741 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1742 // Start an animation for the tabs.
1743 GenerateIdealBounds();
1744 AnimateToIdealBounds();
1746 // Animate the tab being closed to 0x0.
1747 gfx::Rect tab_bounds = tab->bounds();
1748 tab_bounds.set_width(0);
1749 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1751 // Register delegate to do cleanup when done, BoundsAnimator takes
1752 // ownership of RemoveTabDelegate.
1753 bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab),
1754 true);
1756 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1757 // like the new tab button magically appears from beyond the end of the tab
1758 // strip.
1759 if (TabDragController::IsAttachedTo(this)) {
1760 bounds_animator_.StopAnimatingView(newtab_button_);
1761 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1765 void TabStrip::AnimateToIdealBounds() {
1766 for (int i = 0; i < tab_count(); ++i) {
1767 Tab* tab = tab_at(i);
1768 if (!tab->dragging())
1769 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1772 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1775 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1776 return in_tab_close_;
1779 void TabStrip::DoLayout() {
1780 last_layout_size_ = size();
1782 StopAnimating(false);
1784 SwapLayoutIfNecessary();
1786 if (touch_layout_.get())
1787 touch_layout_->SetWidth(size().width() - new_tab_button_width());
1789 GenerateIdealBounds();
1791 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1793 SchedulePaint();
1795 bounds_animator_.StopAnimatingView(newtab_button_);
1796 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1799 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1800 int delta) {
1801 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1802 if (!touch_layout_.get()) {
1803 StackDraggedTabs(delta);
1804 return;
1806 SetIdealBoundsFromPositions(initial_positions);
1807 touch_layout_->DragActiveTab(delta);
1808 DoLayout();
1811 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1812 if (static_cast<size_t>(tab_count()) != positions.size())
1813 return;
1815 for (int i = 0; i < tab_count(); ++i) {
1816 gfx::Rect bounds(ideal_bounds(i));
1817 bounds.set_x(positions[i]);
1818 set_ideal_bounds(i, bounds);
1822 void TabStrip::StackDraggedTabs(int delta) {
1823 DCHECK(!touch_layout_.get());
1824 GenerateIdealBounds();
1825 const int active_index = controller_->GetActiveIndex();
1826 DCHECK_NE(-1, active_index);
1827 if (delta < 0) {
1828 // Drag the tabs to the left, stacking tabs before the active tab.
1829 const int adjusted_delta =
1830 std::min(ideal_bounds(active_index).x() -
1831 kStackedPadding * std::min(active_index, kMaxStackedCount),
1832 -delta);
1833 for (int i = 0; i <= active_index; ++i) {
1834 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1835 gfx::Rect new_bounds(ideal_bounds(i));
1836 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1837 set_ideal_bounds(i, new_bounds);
1839 const bool is_active_mini = tab_at(active_index)->data().mini;
1840 const int active_width = ideal_bounds(active_index).width();
1841 for (int i = active_index + 1; i < tab_count(); ++i) {
1842 const int max_x = ideal_bounds(active_index).x() +
1843 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1844 gfx::Rect new_bounds(ideal_bounds(i));
1845 int new_x = std::max(new_bounds.x() + delta, max_x);
1846 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini &&
1847 new_bounds.width() != active_width)
1848 new_x += (active_width - new_bounds.width());
1849 new_bounds.set_x(new_x);
1850 set_ideal_bounds(i, new_bounds);
1852 } else {
1853 // Drag the tabs to the right, stacking tabs after the active tab.
1854 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1855 const int last_tab_x = width() - new_tab_button_width() - last_tab_width;
1856 if (active_index == tab_count() - 1 &&
1857 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1858 return;
1859 const int adjusted_delta =
1860 std::min(last_tab_x -
1861 kStackedPadding * std::min(tab_count() - active_index - 1,
1862 kMaxStackedCount) -
1863 ideal_bounds(active_index).x(),
1864 delta);
1865 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1866 --i) {
1867 const int max_x = last_tab_x -
1868 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1869 gfx::Rect new_bounds(ideal_bounds(i));
1870 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1871 // Because of rounding not all tabs are the same width. Adjust the
1872 // position to accommodate this, otherwise the stacking is off.
1873 if (new_x == max_x && !tab_at(i)->data().mini &&
1874 new_bounds.width() != last_tab_width)
1875 new_x += (last_tab_width - new_bounds.width());
1876 new_bounds.set_x(new_x);
1877 set_ideal_bounds(i, new_bounds);
1879 for (int i = active_index - 1; i >= 0; --i) {
1880 const int min_x = ideal_bounds(active_index).x() -
1881 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1882 gfx::Rect new_bounds(ideal_bounds(i));
1883 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1884 set_ideal_bounds(i, new_bounds);
1886 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1887 newtab_button_->SetVisible(false);
1889 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1890 SchedulePaint();
1893 bool TabStrip::IsStackingDraggedTabs() const {
1894 return drag_controller_.get() && drag_controller_->started_drag() &&
1895 (drag_controller_->move_behavior() ==
1896 TabDragController::MOVE_VISIBILE_TABS);
1899 void TabStrip::LayoutDraggedTabsAt(const std::vector<Tab*>& tabs,
1900 Tab* active_tab,
1901 const gfx::Point& location,
1902 bool initial_drag) {
1903 // Immediately hide the new tab button if the last tab is being dragged.
1904 if (tab_at(tab_count() - 1)->dragging())
1905 newtab_button_->SetVisible(false);
1906 std::vector<gfx::Rect> bounds;
1907 CalculateBoundsForDraggedTabs(tabs, &bounds);
1908 DCHECK_EQ(tabs.size(), bounds.size());
1909 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1910 int active_tab_index = static_cast<int>(
1911 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1912 for (size_t i = 0; i < tabs.size(); ++i) {
1913 Tab* tab = tabs[i];
1914 gfx::Rect new_bounds = bounds[i];
1915 new_bounds.Offset(location.x(), location.y());
1916 int consecutive_index =
1917 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1918 // If this is the initial layout during a drag and the tabs aren't
1919 // consecutive animate the view into position. Do the same if the tab is
1920 // already animating (which means we previously caused it to animate).
1921 if ((initial_drag &&
1922 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1923 bounds_animator_.IsAnimating(tabs[i])) {
1924 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1925 } else {
1926 tab->SetBoundsRect(new_bounds);
1931 void TabStrip::CalculateBoundsForDraggedTabs(const std::vector<Tab*>& tabs,
1932 std::vector<gfx::Rect>* bounds) {
1933 int x = 0;
1934 for (size_t i = 0; i < tabs.size(); ++i) {
1935 Tab* tab = tabs[i];
1936 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1937 x += kMiniToNonMiniGap;
1938 gfx::Rect new_bounds = tab->bounds();
1939 new_bounds.set_origin(gfx::Point(x, 0));
1940 bounds->push_back(new_bounds);
1941 x += tab->width() + tab_h_offset();
1945 int TabStrip::GetSizeNeededForTabs(const std::vector<Tab*>& tabs) {
1946 int width = 0;
1947 for (size_t i = 0; i < tabs.size(); ++i) {
1948 Tab* tab = tabs[i];
1949 width += tab->width();
1950 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1951 width += kMiniToNonMiniGap;
1953 if (tabs.size() > 0)
1954 width += tab_h_offset() * static_cast<int>(tabs.size() - 1);
1955 return width;
1958 void TabStrip::RemoveTabFromViewModel(int index) {
1959 // We still need to paint the tab until we actually remove it. Put it
1960 // in tabs_closing_map_ so we can find it.
1961 tabs_closing_map_[index].push_back(tab_at(index));
1962 UpdateTabsClosingMap(index + 1, -1);
1963 tabs_.Remove(index);
1966 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1967 scoped_ptr<Tab> deleter(tab);
1968 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1969 i != tabs_closing_map_.end(); ++i) {
1970 std::vector<Tab*>::iterator j =
1971 std::find(i->second.begin(), i->second.end(), tab);
1972 if (j != i->second.end()) {
1973 i->second.erase(j);
1974 if (i->second.empty())
1975 tabs_closing_map_.erase(i);
1976 return;
1979 NOTREACHED();
1982 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1983 if (tabs_closing_map_.empty())
1984 return;
1986 if (delta == -1 &&
1987 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1988 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1989 const std::vector<Tab*>& tabs(tabs_closing_map_[index]);
1990 tabs_closing_map_[index - 1].insert(
1991 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1993 TabsClosingMap updated_map;
1994 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1995 i != tabs_closing_map_.end(); ++i) {
1996 if (i->first > index)
1997 updated_map[i->first + delta] = i->second;
1998 else if (i->first < index)
1999 updated_map[i->first] = i->second;
2001 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
2002 updated_map[index + delta] = tabs_closing_map_[index];
2003 tabs_closing_map_.swap(updated_map);
2006 void TabStrip::StartedDraggingTabs(const std::vector<Tab*>& tabs) {
2007 // Let the controller know that the user started dragging tabs.
2008 controller()->OnStartedDraggingTabs();
2010 // Hide the new tab button immediately if we didn't originate the drag.
2011 if (!drag_controller_.get())
2012 newtab_button_->SetVisible(false);
2014 PrepareForAnimation();
2016 // Reset dragging state of existing tabs.
2017 for (int i = 0; i < tab_count(); ++i)
2018 tab_at(i)->set_dragging(false);
2020 for (size_t i = 0; i < tabs.size(); ++i) {
2021 tabs[i]->set_dragging(true);
2022 bounds_animator_.StopAnimatingView(tabs[i]);
2025 // Move the dragged tabs to their ideal bounds.
2026 GenerateIdealBounds();
2028 // Sets the bounds of the dragged tabs.
2029 for (size_t i = 0; i < tabs.size(); ++i) {
2030 int tab_data_index = GetModelIndexOfTab(tabs[i]);
2031 DCHECK_NE(-1, tab_data_index);
2032 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
2034 SchedulePaint();
2037 void TabStrip::DraggedTabsDetached() {
2038 // Let the controller know that the user is not dragging this tabstrip's tabs
2039 // anymore.
2040 controller()->OnStoppedDraggingTabs();
2041 newtab_button_->SetVisible(true);
2044 void TabStrip::StoppedDraggingTabs(const std::vector<Tab*>& tabs,
2045 const std::vector<int>& initial_positions,
2046 bool move_only,
2047 bool completed) {
2048 // Let the controller know that the user stopped dragging tabs.
2049 controller()->OnStoppedDraggingTabs();
2051 newtab_button_->SetVisible(true);
2052 if (move_only && touch_layout_.get()) {
2053 if (completed) {
2054 touch_layout_->SizeToFit();
2055 } else {
2056 SetIdealBoundsFromPositions(initial_positions);
2059 bool is_first_tab = true;
2060 for (size_t i = 0; i < tabs.size(); ++i)
2061 StoppedDraggingTab(tabs[i], &is_first_tab);
2064 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
2065 int tab_data_index = GetModelIndexOfTab(tab);
2066 if (tab_data_index == -1) {
2067 // The tab was removed before the drag completed. Don't do anything.
2068 return;
2071 if (*is_first_tab) {
2072 *is_first_tab = false;
2073 PrepareForAnimation();
2075 // Animate the view back to its correct position.
2076 GenerateIdealBounds();
2077 AnimateToIdealBounds();
2079 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
2080 // Install a delegate to reset the dragging state when done. We have to leave
2081 // dragging true for the tab otherwise it'll draw beneath the new tab button.
2082 bounds_animator_.SetAnimationDelegate(
2083 tab, new ResetDraggingStateDelegate(tab), true);
2086 void TabStrip::OwnDragController(TabDragController* controller) {
2087 // Typically, ReleaseDragController() and OwnDragController() calls are paired
2088 // via corresponding calls to TabDragController::Detach() and
2089 // TabDragController::Attach(). There is one exception to that rule: when a
2090 // drag might start, we create a TabDragController that is owned by the
2091 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
2092 // we then call Attach() on the source tabstrip, but since the source tabstrip
2093 // already owns the TabDragController, so we don't need to do anything.
2094 if (controller != drag_controller_.get())
2095 drag_controller_.reset(controller);
2098 void TabStrip::DestroyDragController() {
2099 newtab_button_->SetVisible(true);
2100 drag_controller_.reset();
2103 TabDragController* TabStrip::ReleaseDragController() {
2104 return drag_controller_.release();
2107 void TabStrip::PaintClosingTabs(gfx::Canvas* canvas, int index) {
2108 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
2109 return;
2111 const std::vector<Tab*>& tabs = tabs_closing_map_[index];
2112 for (std::vector<Tab*>::const_reverse_iterator i(tabs.rbegin());
2113 i != tabs.rend(); ++i) {
2114 (*i)->Paint(canvas);
2118 void TabStrip::UpdateLayoutTypeFromMouseEvent(views::View* source,
2119 const ui::MouseEvent& event) {
2120 if (!GetAdjustLayout())
2121 return;
2123 // The following code attempts to switch to TAB_STRIP_LAYOUT_SHRINK when the
2124 // mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
2125 // TAB_STRIP_LAYOUT_STACKED when a touch device is used. This is made
2126 // problematic by windows generating mouse move events that do not clearly
2127 // indicate the move is the result of a touch device. This assumes a real
2128 // mouse is used if |kMouseMoveCountBeforeConsiderReal| mouse move events are
2129 // received within the time window |kMouseMoveTimeMS|. At the time we get a
2130 // mouse press we know whether its from a touch device or not, but we don't
2131 // layout then else everything shifts. Instead we wait for the release.
2133 // TODO(sky): revisit this when touch events are really plumbed through.
2135 switch (event.type()) {
2136 case ui::ET_MOUSE_PRESSED:
2137 mouse_move_count_ = 0;
2138 last_mouse_move_time_ = base::TimeTicks();
2139 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
2140 if (reset_to_shrink_on_exit_ && touch_layout_.get()) {
2141 gfx::Point tab_strip_point(event.location());
2142 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
2143 Tab* tab = FindTabForEvent(tab_strip_point);
2144 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
2145 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true);
2146 controller_->LayoutTypeMaybeChanged();
2149 break;
2151 case ui::ET_MOUSE_MOVED: {
2152 #if defined(USE_ASH)
2153 // Ash does not synthesize mouse events from touch events.
2154 SetResetToShrinkOnExit(true);
2155 #else
2156 gfx::Point location(event.location());
2157 ConvertPointToTarget(source, this, &location);
2158 if (location == last_mouse_move_location_)
2159 return; // Ignore spurious moves.
2160 last_mouse_move_location_ = location;
2161 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2162 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2163 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2164 kMouseMoveTimeMS) {
2165 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2166 SetResetToShrinkOnExit(true);
2167 } else {
2168 mouse_move_count_ = 1;
2169 last_mouse_move_time_ = base::TimeTicks::Now();
2171 } else {
2172 last_mouse_move_time_ = base::TimeTicks();
2174 #endif
2175 break;
2178 case ui::ET_MOUSE_RELEASED: {
2179 gfx::Point location(event.location());
2180 ConvertPointToTarget(source, this, &location);
2181 last_mouse_move_location_ = location;
2182 mouse_move_count_ = 0;
2183 last_mouse_move_time_ = base::TimeTicks();
2184 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2185 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true);
2186 controller_->LayoutTypeMaybeChanged();
2188 break;
2191 default:
2192 break;
2196 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2197 double* selected_width) const {
2198 *unselected_width = current_unselected_width_;
2199 *selected_width = current_selected_width_;
2202 void TabStrip::GetDesiredTabWidths(int tab_count,
2203 int mini_tab_count,
2204 double* unselected_width,
2205 double* selected_width) const {
2206 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
2207 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2208 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2210 *unselected_width = min_unselected_width;
2211 *selected_width = min_selected_width;
2213 if (tab_count == 0) {
2214 // Return immediately to avoid divide-by-zero below.
2215 return;
2218 // Determine how much space we can actually allocate to tabs.
2219 int available_width;
2220 if (available_width_for_tabs_ < 0) {
2221 available_width = width() - new_tab_button_width();
2222 } else {
2223 // Interesting corner case: if |available_width_for_tabs_| > the result
2224 // of the calculation in the conditional arm above, the strip is in
2225 // overflow. We can either use the specified width or the true available
2226 // width here; the first preserves the consistent "leave the last tab under
2227 // the user's mouse so they can close many tabs" behavior at the cost of
2228 // prolonging the glitchy appearance of the overflow state, while the second
2229 // gets us out of overflow as soon as possible but forces the user to move
2230 // their mouse for a few tabs' worth of closing. We choose visual
2231 // imperfection over behavioral imperfection and select the first option.
2232 available_width = available_width_for_tabs_;
2235 if (mini_tab_count > 0) {
2236 available_width -= mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset());
2237 tab_count -= mini_tab_count;
2238 if (tab_count == 0) {
2239 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2240 return;
2242 // Account for gap between the last mini-tab and first non-mini-tab.
2243 available_width -= kMiniToNonMiniGap;
2246 // Calculate the desired tab widths by dividing the available space into equal
2247 // portions. Don't let tabs get larger than the "standard width" or smaller
2248 // than the minimum width for each type, respectively.
2249 const int total_offset = tab_h_offset() * (tab_count - 1);
2250 const double desired_tab_width = std::min((static_cast<double>(
2251 available_width - total_offset) / static_cast<double>(tab_count)),
2252 static_cast<double>(Tab::GetStandardSize().width()));
2253 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2254 *selected_width = std::max(desired_tab_width, min_selected_width);
2256 // When there are multiple tabs, we'll have one selected and some unselected
2257 // tabs. If the desired width was between the minimum sizes of these types,
2258 // try to shrink the tabs with the smaller minimum. For example, if we have
2259 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2260 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2261 // width of 1, the above code would set *unselected_width = 2.5,
2262 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2263 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2264 // of 10.
2265 if (tab_count > 1) {
2266 if ((min_unselected_width < min_selected_width) &&
2267 (desired_tab_width < min_selected_width)) {
2268 // Unselected width = (total width - selected width) / (num_tabs - 1)
2269 *unselected_width = std::max(static_cast<double>(
2270 available_width - total_offset - min_selected_width) /
2271 static_cast<double>(tab_count - 1), min_unselected_width);
2272 } else if ((min_unselected_width > min_selected_width) &&
2273 (desired_tab_width < min_unselected_width)) {
2274 // Selected width = (total width - (unselected width * (num_tabs - 1)))
2275 *selected_width = std::max(available_width - total_offset -
2276 (min_unselected_width * (tab_count - 1)), min_selected_width);
2281 void TabStrip::ResizeLayoutTabs() {
2282 // We've been called back after the TabStrip has been emptied out (probably
2283 // just prior to the window being destroyed). We need to do nothing here or
2284 // else GetTabAt below will crash.
2285 if (tab_count() == 0)
2286 return;
2288 // It is critically important that this is unhooked here, otherwise we will
2289 // keep spying on messages forever.
2290 RemoveMessageLoopObserver();
2292 in_tab_close_ = false;
2293 available_width_for_tabs_ = -1;
2294 int mini_tab_count = GetMiniTabCount();
2295 if (mini_tab_count == tab_count()) {
2296 // Only mini-tabs, we know the tab widths won't have changed (all
2297 // mini-tabs have the same width), so there is nothing to do.
2298 return;
2300 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2301 // then the width of the active tab may not change, but other widths may
2302 // have. This is particularly important if we've overflowed (all tabs are at
2303 // the min).
2304 StartResizeLayoutAnimation();
2307 void TabStrip::ResizeLayoutTabsFromTouch() {
2308 // Don't resize if the user is interacting with the tabstrip.
2309 if (!drag_controller_.get())
2310 ResizeLayoutTabs();
2311 else
2312 StartResizeLayoutTabsFromTouchTimer();
2315 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2316 resize_layout_timer_.Stop();
2317 resize_layout_timer_.Start(
2318 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2319 this, &TabStrip::ResizeLayoutTabsFromTouch);
2322 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2323 StopAnimating(false);
2324 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2325 for (int i = 0; i < tab_count(); ++i)
2326 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2329 void TabStrip::AddMessageLoopObserver() {
2330 if (!mouse_watcher_.get()) {
2331 mouse_watcher_.reset(
2332 new views::MouseWatcher(
2333 new views::MouseWatcherViewHost(
2334 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2335 this));
2337 mouse_watcher_->Start();
2340 void TabStrip::RemoveMessageLoopObserver() {
2341 mouse_watcher_.reset(NULL);
2344 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2345 bool drop_before,
2346 bool* is_beneath) {
2347 DCHECK_NE(drop_index, -1);
2348 int center_x;
2349 if (drop_index < tab_count()) {
2350 Tab* tab = tab_at(drop_index);
2351 if (drop_before)
2352 center_x = tab->x() - (tab_h_offset() / 2);
2353 else
2354 center_x = tab->x() + (tab->width() / 2);
2355 } else {
2356 Tab* last_tab = tab_at(drop_index - 1);
2357 center_x = last_tab->x() + last_tab->width() + (tab_h_offset() / 2);
2360 // Mirror the center point if necessary.
2361 center_x = GetMirroredXInView(center_x);
2363 // Determine the screen bounds.
2364 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2365 -drop_indicator_height);
2366 ConvertPointToScreen(this, &drop_loc);
2367 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2368 drop_indicator_height);
2370 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2371 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2372 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2373 *is_beneath = !display.bounds().Contains(drop_bounds);
2374 if (*is_beneath)
2375 drop_bounds.Offset(0, drop_bounds.height() + height());
2377 return drop_bounds;
2380 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2381 // If the UI layout is right-to-left, we need to mirror the mouse
2382 // coordinates since we calculate the drop index based on the
2383 // original (and therefore non-mirrored) positions of the tabs.
2384 const int x = GetMirroredXInView(event.x());
2385 // We don't allow replacing the urls of mini-tabs.
2386 for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
2387 Tab* tab = tab_at(i);
2388 const int tab_max_x = tab->x() + tab->width();
2389 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2390 if (x < tab_max_x) {
2391 if (x < tab->x() + hot_width)
2392 SetDropIndex(i, true);
2393 else if (x >= tab_max_x - hot_width)
2394 SetDropIndex(i + 1, true);
2395 else
2396 SetDropIndex(i, false);
2397 return;
2401 // The drop isn't over a tab, add it to the end.
2402 SetDropIndex(tab_count(), true);
2405 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2406 // Let the controller know of the index update.
2407 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2409 if (tab_data_index == -1) {
2410 if (drop_info_.get())
2411 drop_info_.reset(NULL);
2412 return;
2415 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2416 drop_info_->drop_before == drop_before) {
2417 return;
2420 bool is_beneath;
2421 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2422 &is_beneath);
2424 if (!drop_info_.get()) {
2425 drop_info_.reset(
2426 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2427 } else {
2428 drop_info_->drop_index = tab_data_index;
2429 drop_info_->drop_before = drop_before;
2430 if (is_beneath == drop_info_->point_down) {
2431 drop_info_->point_down = !is_beneath;
2432 drop_info_->arrow_view->SetImage(
2433 GetDropArrowImage(drop_info_->point_down));
2437 // Reposition the window. Need to show it too as the window is initially
2438 // hidden.
2439 drop_info_->arrow_window->SetBounds(drop_bounds);
2440 drop_info_->arrow_window->Show();
2443 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2444 const int source_ops = event.source_operations();
2445 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2446 return ui::DragDropTypes::DRAG_COPY;
2447 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2448 return ui::DragDropTypes::DRAG_LINK;
2449 return ui::DragDropTypes::DRAG_MOVE;
2452 // static
2453 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2454 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2455 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2458 // TabStrip::DropInfo ----------------------------------------------------------
2460 TabStrip::DropInfo::DropInfo(int drop_index,
2461 bool drop_before,
2462 bool point_down,
2463 views::Widget* context)
2464 : drop_index(drop_index),
2465 drop_before(drop_before),
2466 point_down(point_down),
2467 file_supported(true) {
2468 arrow_view = new views::ImageView;
2469 arrow_view->SetImage(GetDropArrowImage(point_down));
2471 arrow_window = new views::Widget;
2472 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2473 params.keep_on_top = true;
2474 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2475 params.accept_events = false;
2476 params.can_activate = false;
2477 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2478 params.context = context->GetNativeView();
2479 arrow_window->Init(params);
2480 arrow_window->SetContentsView(arrow_view);
2483 TabStrip::DropInfo::~DropInfo() {
2484 // Close eventually deletes the window, which deletes arrow_view too.
2485 arrow_window->Close();
2488 ///////////////////////////////////////////////////////////////////////////////
2490 void TabStrip::PrepareForAnimation() {
2491 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2492 for (int i = 0; i < tab_count(); ++i)
2493 tab_at(i)->set_dragging(false);
2497 void TabStrip::GenerateIdealBounds() {
2498 int new_tab_y = 0;
2500 if (touch_layout_.get()) {
2501 if (tabs_.view_size() == 0)
2502 return;
2504 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2505 newtab_button_h_offset();
2506 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2507 return;
2510 double unselected, selected;
2511 GetDesiredTabWidths(tab_count(), GetMiniTabCount(), &unselected, &selected);
2512 current_unselected_width_ = unselected;
2513 current_selected_width_ = selected;
2515 // NOTE: This currently assumes a tab's height doesn't differ based on
2516 // selected state or the number of tabs in the strip!
2517 int tab_height = Tab::GetStandardSize().height();
2518 int first_non_mini_index = 0;
2519 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index);
2520 for (int i = first_non_mini_index; i < tab_count(); ++i) {
2521 Tab* tab = tab_at(i);
2522 DCHECK(!tab->data().mini);
2523 double tab_width = tab->IsActive() ? selected : unselected;
2524 double end_of_tab = tab_x + tab_width;
2525 int rounded_tab_x = Round(tab_x);
2526 set_ideal_bounds(
2528 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2529 tab_height));
2530 tab_x = end_of_tab + tab_h_offset();
2533 // Update bounds of new tab button.
2534 int new_tab_x;
2535 if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 &&
2536 !in_tab_close_) {
2537 // We're shrinking tabs, so we need to anchor the New Tab button to the
2538 // right edge of the TabStrip's bounds, rather than the right edge of the
2539 // right-most Tab, otherwise it'll bounce when animating.
2540 new_tab_x = width() - newtab_button_bounds_.width();
2541 } else {
2542 new_tab_x = Round(tab_x - tab_h_offset()) + newtab_button_h_offset();
2544 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2547 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) {
2548 int next_x = 0;
2549 int mini_width = Tab::GetMiniWidth();
2550 int tab_height = Tab::GetStandardSize().height();
2551 int index = 0;
2552 for (; index < tab_count() && tab_at(index)->data().mini; ++index) {
2553 set_ideal_bounds(index,
2554 gfx::Rect(next_x, 0, mini_width, tab_height));
2555 next_x += mini_width + tab_h_offset();
2557 if (index > 0 && index < tab_count())
2558 next_x += kMiniToNonMiniGap;
2559 if (first_non_mini_index)
2560 *first_non_mini_index = index;
2561 return next_x;
2564 // static
2565 int TabStrip::new_tab_button_width() {
2566 return newtab_button_asset_width() + newtab_button_h_offset();
2569 // static
2570 int TabStrip::button_v_offset() {
2571 return newtab_button_v_offset();
2574 int TabStrip::tab_area_width() const {
2575 return width() - new_tab_button_width();
2578 void TabStrip::StartResizeLayoutAnimation() {
2579 PrepareForAnimation();
2580 GenerateIdealBounds();
2581 AnimateToIdealBounds();
2584 void TabStrip::StartMiniTabAnimation() {
2585 in_tab_close_ = false;
2586 available_width_for_tabs_ = -1;
2588 PrepareForAnimation();
2590 GenerateIdealBounds();
2591 AnimateToIdealBounds();
2594 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2595 // The user initiated the close. We want to persist the bounds of all the
2596 // existing tabs, so we manually shift ideal_bounds then animate.
2597 Tab* tab_closing = tab_at(model_index);
2598 int delta = tab_closing->width() + tab_h_offset();
2599 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2600 // add the extra padding.
2601 DCHECK_NE(model_index + 1, tab_count());
2602 if (tab_closing->data().mini && model_index + 1 < tab_count() &&
2603 !tab_at(model_index + 1)->data().mini) {
2604 delta += kMiniToNonMiniGap;
2607 for (int i = model_index + 1; i < tab_count(); ++i) {
2608 gfx::Rect bounds = ideal_bounds(i);
2609 bounds.set_x(bounds.x() - delta);
2610 set_ideal_bounds(i, bounds);
2613 newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta);
2615 PrepareForAnimation();
2617 tab_closing->set_closing(true);
2619 // We still need to paint the tab until we actually remove it. Put it in
2620 // tabs_closing_map_ so we can find it.
2621 RemoveTabFromViewModel(model_index);
2623 AnimateToIdealBounds();
2625 gfx::Rect tab_bounds = tab_closing->bounds();
2626 tab_bounds.set_width(0);
2627 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2629 // Register delegate to do cleanup when done, BoundsAnimator takes
2630 // ownership of RemoveTabDelegate.
2631 bounds_animator_.SetAnimationDelegate(
2632 tab_closing,
2633 new RemoveTabDelegate(this, tab_closing),
2634 true);
2637 bool TabStrip::IsPointInTab(Tab* tab,
2638 const gfx::Point& point_in_tabstrip_coords) {
2639 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2640 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2641 return tab->HitTestPoint(point_in_tab_coords);
2644 int TabStrip::GetStartXForNormalTabs() const {
2645 int mini_tab_count = GetMiniTabCount();
2646 if (mini_tab_count == 0)
2647 return 0;
2648 return mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset()) +
2649 kMiniToNonMiniGap;
2652 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2653 if (touch_layout_.get()) {
2654 int active_tab_index = touch_layout_->active_index();
2655 if (active_tab_index != -1) {
2656 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2657 if (!tab)
2658 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2659 return tab;
2660 } else if (tab_count()) {
2661 return FindTabForEventFrom(point, 0, 1);
2663 } else {
2664 for (int i = 0; i < tab_count(); ++i) {
2665 if (IsPointInTab(tab_at(i), point))
2666 return tab_at(i);
2669 return NULL;
2672 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2673 int start,
2674 int delta) {
2675 // |start| equals tab_count() when there are only pinned tabs.
2676 if (start == tab_count())
2677 start += delta;
2678 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2679 if (IsPointInTab(tab_at(i), point))
2680 return tab_at(i);
2682 return NULL;
2685 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2686 // The display order doesn't necessarily match the child list order, so we
2687 // walk the display list hit-testing Tabs. Since the active tab always
2688 // renders on top of adjacent tabs, it needs to be hit-tested before any
2689 // left-adjacent Tab, so we look ahead for it as we walk.
2690 for (int i = 0; i < tab_count(); ++i) {
2691 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2692 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2693 return next_tab;
2694 if (IsPointInTab(tab_at(i), point))
2695 return tab_at(i);
2698 return NULL;
2701 std::vector<int> TabStrip::GetTabXCoordinates() {
2702 std::vector<int> results;
2703 for (int i = 0; i < tab_count(); ++i)
2704 results.push_back(ideal_bounds(i).x());
2705 return results;
2708 void TabStrip::SwapLayoutIfNecessary() {
2709 bool needs_touch = NeedsTouchLayout();
2710 bool using_touch = touch_layout_.get() != NULL;
2711 if (needs_touch == using_touch)
2712 return;
2714 if (needs_touch) {
2715 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2716 tab_size.set_width(Tab::GetTouchWidth());
2717 touch_layout_.reset(new StackedTabStripLayout(
2718 tab_size,
2719 tab_h_offset(),
2720 kStackedPadding,
2721 kMaxStackedCount,
2722 &tabs_));
2723 touch_layout_->SetWidth(width() - new_tab_button_width());
2724 // This has to be after SetWidth() as SetWidth() is going to reset the
2725 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2726 // many mini-tabs there are).
2727 GenerateIdealBoundsForMiniTabs(NULL);
2728 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(),
2729 GetMiniTabCount());
2730 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2731 } else {
2732 touch_layout_.reset();
2734 PrepareForAnimation();
2735 GenerateIdealBounds();
2736 AnimateToIdealBounds();
2739 bool TabStrip::NeedsTouchLayout() const {
2740 if (layout_type_ == TAB_STRIP_LAYOUT_SHRINK)
2741 return false;
2743 int mini_tab_count = GetMiniTabCount();
2744 int normal_count = tab_count() - mini_tab_count;
2745 if (normal_count <= 1 || normal_count == mini_tab_count)
2746 return false;
2747 int x = GetStartXForNormalTabs();
2748 int available_width = width() - x - new_tab_button_width();
2749 return (Tab::GetTouchWidth() * normal_count +
2750 tab_h_offset() * (normal_count - 1)) > available_width;
2753 void TabStrip::SetResetToShrinkOnExit(bool value) {
2754 if (!GetAdjustLayout())
2755 return;
2757 if (value && layout_type_ == TAB_STRIP_LAYOUT_SHRINK)
2758 value = false; // We're already at TAB_STRIP_LAYOUT_SHRINK.
2760 if (value == reset_to_shrink_on_exit_)
2761 return;
2763 reset_to_shrink_on_exit_ = value;
2764 // Add an observer so we know when the mouse moves out of the tabstrip.
2765 if (reset_to_shrink_on_exit_)
2766 AddMessageLoopObserver();
2767 else
2768 RemoveMessageLoopObserver();
2771 bool TabStrip::GetAdjustLayout() const {
2772 if (!adjust_layout_)
2773 return false;
2775 #if defined(USE_AURA)
2776 return chrome::GetHostDesktopTypeForNativeView(
2777 GetWidget()->GetNativeView()) == chrome::HOST_DESKTOP_TYPE_ASH;
2778 #else
2779 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH)
2780 return false;
2781 #endif
2783 return true;