Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip.cc
blob0b4b7e6db92b0df461b75a915ed9bf9e73cf6dd9
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/command_line.h"
17 #include "base/compiler_specific.h"
18 #include "base/metrics/histogram.h"
19 #include "base/stl_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "chrome/browser/defaults.h"
22 #include "chrome/browser/ui/host_desktop.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/browser/ui/view_ids.h"
25 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
26 #include "chrome/browser/ui/views/tabs/tab.h"
27 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
29 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
30 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "content/public/browser/user_metrics.h"
34 #include "grit/theme_resources.h"
35 #include "ui/accessibility/ax_view_state.h"
36 #include "ui/base/default_theme_provider.h"
37 #include "ui/base/dragdrop/drag_drop_types.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/models/list_selection_model.h"
40 #include "ui/base/resource/resource_bundle.h"
41 #include "ui/gfx/animation/animation_container.h"
42 #include "ui/gfx/animation/throb_animation.h"
43 #include "ui/gfx/canvas.h"
44 #include "ui/gfx/display.h"
45 #include "ui/gfx/geometry/rect_conversions.h"
46 #include "ui/gfx/geometry/size.h"
47 #include "ui/gfx/image/image_skia.h"
48 #include "ui/gfx/image/image_skia_operations.h"
49 #include "ui/gfx/path.h"
50 #include "ui/gfx/screen.h"
51 #include "ui/gfx/skia_util.h"
52 #include "ui/views/controls/image_view.h"
53 #include "ui/views/masked_targeter_delegate.h"
54 #include "ui/views/mouse_watcher_view_host.h"
55 #include "ui/views/rect_based_targeting_utils.h"
56 #include "ui/views/view_model_utils.h"
57 #include "ui/views/view_targeter.h"
58 #include "ui/views/widget/root_view.h"
59 #include "ui/views/widget/widget.h"
60 #include "ui/views/window/non_client_view.h"
62 #if defined(OS_WIN)
63 #include "ui/gfx/win/dpi.h"
64 #include "ui/gfx/win/hwnd_util.h"
65 #include "ui/views/widget/monitor_win.h"
66 #include "ui/views/win/hwnd_util.h"
67 #endif
69 using base::UserMetricsAction;
70 using ui::DropTargetEvent;
72 namespace {
74 static const int kTabStripAnimationVSlop = 40;
75 // Inactive tabs in a native frame are slightly transparent.
76 static const int kGlassFrameInactiveTabAlpha = 200;
77 // If there are multiple tabs selected then make non-selected inactive tabs
78 // even more transparent.
79 static const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
81 // Alpha applied to all elements save the selected tabs.
82 static const int kInactiveTabAndNewTabButtonAlphaAsh = 230;
83 static const int kInactiveTabAndNewTabButtonAlpha = 255;
85 // Inverse ratio of the width of a tab edge to the width of the tab. When
86 // hovering over the left or right edge of a tab, the drop indicator will
87 // point between tabs.
88 static const int kTabEdgeRatioInverse = 4;
90 // Size of the drop indicator.
91 static int drop_indicator_width;
92 static int drop_indicator_height;
94 static inline int Round(double x) {
95 // Why oh why is this not in a standard header?
96 return static_cast<int>(floor(x + 0.5));
99 // Max number of stacked tabs.
100 static const int kMaxStackedCount = 4;
102 // Padding between stacked tabs.
103 static const int kStackedPadding = 6;
105 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
106 #if !defined(USE_ASH)
107 const int kMouseMoveTimeMS = 200;
108 const int kMouseMoveCountBeforeConsiderReal = 3;
109 #endif
111 // Amount of time we delay before resizing after a close from a touch.
112 const int kTouchResizeLayoutTimeMS = 2000;
114 // Amount the left edge of a tab is offset from the rectangle of the tab's
115 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
116 // Affects the size of the "V" between adjacent tabs.
117 #if defined(OS_MACOSX)
118 const int kTabHorizontalOffset = -19;
119 #else
120 const int kTabHorizontalOffset = -26;
121 #endif
123 // Amount to adjust the clip by when the tab is stacked before the active index.
124 const int kStackedTabLeftClip = 20;
126 // Amount to adjust the clip by when the tab is stacked after the active index.
127 const int kStackedTabRightClip = 20;
129 base::string16 GetClipboardText() {
130 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
131 return base::string16();
132 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
133 CHECK(clipboard);
134 base::string16 clipboard_text;
135 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
136 return clipboard_text;
139 // Animation delegate used for any automatic tab movement. Hides the tab if it
140 // is not fully visible within the tabstrip area, to prevent overflow clipping.
141 class TabAnimationDelegate : public gfx::AnimationDelegate {
142 public:
143 TabAnimationDelegate(TabStrip* tab_strip, Tab* tab);
144 ~TabAnimationDelegate() override;
146 void AnimationProgressed(const gfx::Animation* animation) override;
148 protected:
149 TabStrip* tab_strip() { return tab_strip_; }
150 Tab* tab() { return tab_; }
152 private:
153 TabStrip* const tab_strip_;
154 Tab* const tab_;
156 DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate);
159 TabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab)
160 : tab_strip_(tab_strip),
161 tab_(tab) {
164 TabAnimationDelegate::~TabAnimationDelegate() {
167 void TabAnimationDelegate::AnimationProgressed(
168 const gfx::Animation* animation) {
169 tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_));
172 // Animation delegate used when a dragged tab is released. When done sets the
173 // dragging state to false.
174 class ResetDraggingStateDelegate : public TabAnimationDelegate {
175 public:
176 ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab);
177 ~ResetDraggingStateDelegate() override;
179 void AnimationEnded(const gfx::Animation* animation) override;
180 void AnimationCanceled(const gfx::Animation* animation) override;
182 private:
183 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
186 ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip,
187 Tab* tab)
188 : TabAnimationDelegate(tab_strip, tab) {
191 ResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
194 void ResetDraggingStateDelegate::AnimationEnded(
195 const gfx::Animation* animation) {
196 tab()->set_dragging(false);
197 AnimationProgressed(animation); // Forces tab visibility to update.
200 void ResetDraggingStateDelegate::AnimationCanceled(
201 const gfx::Animation* animation) {
202 AnimationEnded(animation);
205 // If |dest| contains the point |point_in_source| the event handler from |dest|
206 // is returned. Otherwise NULL is returned.
207 views::View* ConvertPointToViewAndGetEventHandler(
208 views::View* source,
209 views::View* dest,
210 const gfx::Point& point_in_source) {
211 gfx::Point dest_point(point_in_source);
212 views::View::ConvertPointToTarget(source, dest, &dest_point);
213 return dest->HitTestPoint(dest_point) ?
214 dest->GetEventHandlerForPoint(dest_point) : NULL;
217 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
218 // should return NULL if it does not contain the point.
219 views::View* ConvertPointToViewAndGetTooltipHandler(
220 views::View* source,
221 views::View* dest,
222 const gfx::Point& point_in_source) {
223 gfx::Point dest_point(point_in_source);
224 views::View::ConvertPointToTarget(source, dest, &dest_point);
225 return dest->GetTooltipHandlerForPoint(dest_point);
228 TabDragController::EventSource EventSourceFromEvent(
229 const ui::LocatedEvent& event) {
230 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
231 TabDragController::EVENT_SOURCE_MOUSE;
234 } // namespace
236 ///////////////////////////////////////////////////////////////////////////////
237 // NewTabButton
239 // A subclass of button that hit-tests to the shape of the new tab button and
240 // does custom drawing.
242 class NewTabButton : public views::ImageButton,
243 public views::MaskedTargeterDelegate {
244 public:
245 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
246 ~NewTabButton() override;
248 // Set the background offset used to match the background image to the frame
249 // image.
250 void set_background_offset(const gfx::Point& offset) {
251 background_offset_ = offset;
254 protected:
255 // views::View:
256 #if defined(OS_WIN)
257 virtual void OnMouseReleased(const ui::MouseEvent& event) override;
258 #endif
259 void OnPaint(gfx::Canvas* canvas) override;
261 // ui::EventHandler:
262 void OnGestureEvent(ui::GestureEvent* event) override;
264 private:
265 // views::MaskedTargeterDelegate:
266 bool GetHitTestMask(gfx::Path* mask) const override;
268 bool ShouldWindowContentsBeTransparent() const;
269 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
270 float scale) const;
271 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
272 float scale) const;
273 gfx::ImageSkia GetImageForScale(float scale) const;
275 // Tab strip that contains this button.
276 TabStrip* tab_strip_;
278 // The offset used to paint the background image.
279 gfx::Point background_offset_;
281 // were we destroyed?
282 bool* destroyed_;
284 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
287 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
288 : views::ImageButton(listener),
289 tab_strip_(tab_strip),
290 destroyed_(NULL) {
291 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
292 set_triggerable_event_flags(triggerable_event_flags() |
293 ui::EF_MIDDLE_MOUSE_BUTTON);
294 #endif
297 NewTabButton::~NewTabButton() {
298 if (destroyed_)
299 *destroyed_ = true;
302 #if defined(OS_WIN)
303 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
304 if (event.IsOnlyRightMouseButton()) {
305 gfx::Point point = event.location();
306 views::View::ConvertPointToScreen(this, &point);
307 point = gfx::win::DIPToScreenPoint(point);
308 bool destroyed = false;
309 destroyed_ = &destroyed;
310 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
311 if (destroyed)
312 return;
314 destroyed_ = NULL;
315 SetState(views::CustomButton::STATE_NORMAL);
316 return;
318 views::ImageButton::OnMouseReleased(event);
320 #endif
322 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
323 gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
324 canvas->DrawImageInt(image, 0, height() - image.height());
327 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
328 // Consume all gesture events here so that the parent (Tab) does not
329 // start consuming gestures.
330 views::ImageButton::OnGestureEvent(event);
331 event->SetHandled();
334 bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
335 DCHECK(mask);
337 // When the button is sized to the top of the tab strip, we want the hit
338 // test mask to be defined as the complete (rectangular) bounds of the
339 // button.
340 if (tab_strip_->SizeTabButtonToTopOfTabStrip()) {
341 gfx::Rect button_bounds(GetContentsBounds());
342 button_bounds.set_x(GetMirroredXForRect(button_bounds));
343 mask->addRect(RectToSkRect(button_bounds));
344 return true;
347 SkScalar w = SkIntToScalar(width());
348 SkScalar v_offset = SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset);
350 // These values are defined by the shape of the new tab image. Should that
351 // image ever change, these values will need to be updated. They're so
352 // custom it's not really worth defining constants for.
353 // These values are correct for regular and USE_ASH versions of the image.
354 mask->moveTo(0, v_offset + 1);
355 mask->lineTo(w - 7, v_offset + 1);
356 mask->lineTo(w - 4, v_offset + 4);
357 mask->lineTo(w, v_offset + 16);
358 mask->lineTo(w - 1, v_offset + 17);
359 mask->lineTo(7, v_offset + 17);
360 mask->lineTo(4, v_offset + 13);
361 mask->lineTo(0, v_offset + 1);
362 mask->close();
364 return true;
367 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
368 return GetWidget() &&
369 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
372 gfx::ImageSkia NewTabButton::GetBackgroundImage(
373 views::CustomButton::ButtonState state,
374 float scale) const {
375 int background_id = 0;
376 if (ShouldWindowContentsBeTransparent()) {
377 background_id = IDR_THEME_TAB_BACKGROUND_V;
378 } else if (tab_strip_->controller()->IsIncognito()) {
379 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
380 } else {
381 background_id = IDR_THEME_TAB_BACKGROUND;
384 int alpha = 0;
385 switch (state) {
386 case views::CustomButton::STATE_NORMAL:
387 case views::CustomButton::STATE_HOVERED:
388 alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
389 : 255;
390 break;
391 case views::CustomButton::STATE_PRESSED:
392 alpha = 145;
393 break;
394 default:
395 NOTREACHED();
396 break;
399 gfx::ImageSkia* mask =
400 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
401 int height = mask->height();
402 int width = mask->width();
403 // The canvas and mask has to use the same scale factor.
404 if (!mask->HasRepresentation(scale))
405 scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
407 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
409 // For custom images the background starts at the top of the tab strip.
410 // Otherwise the background starts at the top of the frame.
411 gfx::ImageSkia* background =
412 GetThemeProvider()->GetImageSkiaNamed(background_id);
413 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
414 0 : background_offset_.y();
416 // The new tab background is mirrored in RTL mode, but the theme background
417 // should never be mirrored. Mirror it here to compensate.
418 float x_scale = 1.0f;
419 int x = GetMirroredX() + background_offset_.x();
420 if (base::i18n::IsRTL()) {
421 x_scale = -1.0f;
422 // Offset by |width| such that the same region is painted as if there was no
423 // flip.
424 x += width;
426 canvas.TileImageInt(*background, x,
427 TabStrip::kNewTabButtonVerticalOffset + offset_y,
428 x_scale, 1.0f, 0, 0, width, height);
430 if (alpha != 255) {
431 SkPaint paint;
432 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
433 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
434 paint.setStyle(SkPaint::kFill_Style);
435 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
438 // White highlight on hover.
439 if (state == views::CustomButton::STATE_HOVERED)
440 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
442 return gfx::ImageSkiaOperations::CreateMaskedImage(
443 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
446 gfx::ImageSkia NewTabButton::GetImageForState(
447 views::CustomButton::ButtonState state,
448 float scale) const {
449 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
450 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
451 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
453 gfx::Canvas canvas(
454 gfx::Size(overlay->width(), overlay->height()),
455 scale,
456 false);
457 canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
459 // Draw the button border with a slight alpha.
460 const int kGlassFrameOverlayAlpha = 178;
461 const int kOpaqueFrameOverlayAlpha = 230;
462 uint8 alpha = ShouldWindowContentsBeTransparent() ?
463 kGlassFrameOverlayAlpha : kOpaqueFrameOverlayAlpha;
464 canvas.DrawImageInt(*overlay, 0, 0, alpha);
466 return gfx::ImageSkia(canvas.ExtractImageRep());
469 gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
470 if (!hover_animation_->is_animating())
471 return GetImageForState(state(), scale);
472 return gfx::ImageSkiaOperations::CreateBlendedImage(
473 GetImageForState(views::CustomButton::STATE_NORMAL, scale),
474 GetImageForState(views::CustomButton::STATE_HOVERED, scale),
475 hover_animation_->GetCurrentValue());
478 ///////////////////////////////////////////////////////////////////////////////
479 // TabStrip::RemoveTabDelegate
481 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
482 // done.
483 class TabStrip::RemoveTabDelegate : public TabAnimationDelegate {
484 public:
485 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
487 void AnimationEnded(const gfx::Animation* animation) override;
488 void AnimationCanceled(const gfx::Animation* animation) override;
490 private:
491 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
494 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
495 Tab* tab)
496 : TabAnimationDelegate(tab_strip, tab) {
499 void TabStrip::RemoveTabDelegate::AnimationEnded(
500 const gfx::Animation* animation) {
501 DCHECK(tab()->closing());
502 tab_strip()->RemoveAndDeleteTab(tab());
504 // Send the Container a message to simulate a mouse moved event at the current
505 // mouse position. This tickles the Tab the mouse is currently over to show
506 // the "hot" state of the close button. Note that this is not required (and
507 // indeed may crash!) for removes spawned by non-mouse closes and
508 // drag-detaches.
509 if (!tab_strip()->IsDragSessionActive() &&
510 tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
511 // The widget can apparently be null during shutdown.
512 views::Widget* widget = tab_strip()->GetWidget();
513 if (widget)
514 widget->SynthesizeMouseMoveEvent();
518 void TabStrip::RemoveTabDelegate::AnimationCanceled(
519 const gfx::Animation* animation) {
520 AnimationEnded(animation);
523 ///////////////////////////////////////////////////////////////////////////////
524 // TabStrip, public:
526 // static
527 const char TabStrip::kViewClassName[] = "TabStrip";
528 const int TabStrip::kNewTabButtonVerticalOffset = 7;
529 const int TabStrip::kNewTabButtonAssetWidth = 34;
530 const int TabStrip::kNewTabButtonAssetHeight = 18;
531 #if defined(OS_MACOSX)
532 const int TabStrip::kNewTabButtonHorizontalOffset = -8;
533 const int TabStrip::kMiniToNonMiniGap = 2;
534 #else
535 const int TabStrip::kNewTabButtonHorizontalOffset = -11;
536 const int TabStrip::kMiniToNonMiniGap = 3;
537 #endif
539 TabStrip::TabStrip(TabStripController* controller)
540 : controller_(controller),
541 newtab_button_(NULL),
542 current_unselected_width_(Tab::GetStandardSize().width()),
543 current_selected_width_(Tab::GetStandardSize().width()),
544 available_width_for_tabs_(-1),
545 in_tab_close_(false),
546 animation_container_(new gfx::AnimationContainer()),
547 bounds_animator_(this),
548 stacked_layout_(false),
549 adjust_layout_(false),
550 reset_to_shrink_on_exit_(false),
551 mouse_move_count_(0),
552 immersive_style_(false) {
553 Init();
554 SetEventTargeter(
555 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
558 TabStrip::~TabStrip() {
559 FOR_EACH_OBSERVER(TabStripObserver, observers_,
560 TabStripDeleted(this));
562 // The animations may reference the tabs. Shut down the animation before we
563 // delete the tabs.
564 StopAnimating(false);
566 DestroyDragController();
568 // Make sure we unhook ourselves as a message loop observer so that we don't
569 // crash in the case where the user closes the window after closing a tab
570 // but before moving the mouse.
571 RemoveMessageLoopObserver();
573 // The children (tabs) may callback to us from their destructor. Delete them
574 // so that if they call back we aren't in a weird state.
575 RemoveAllChildViews(true);
578 void TabStrip::AddObserver(TabStripObserver* observer) {
579 observers_.AddObserver(observer);
582 void TabStrip::RemoveObserver(TabStripObserver* observer) {
583 observers_.RemoveObserver(observer);
586 void TabStrip::SetStackedLayout(bool stacked_layout) {
587 if (stacked_layout == stacked_layout_)
588 return;
590 const int active_index = controller_->GetActiveIndex();
591 int active_center = 0;
592 if (active_index != -1) {
593 active_center = ideal_bounds(active_index).x() +
594 ideal_bounds(active_index).width() / 2;
596 stacked_layout_ = stacked_layout;
597 SetResetToShrinkOnExit(false);
598 SwapLayoutIfNecessary();
599 // When transitioning to stacked try to keep the active tab centered.
600 if (touch_layout_ && active_index != -1) {
601 touch_layout_->SetActiveTabLocation(
602 active_center - ideal_bounds(active_index).width() / 2);
603 AnimateToIdealBounds();
607 gfx::Rect TabStrip::GetNewTabButtonBounds() {
608 return newtab_button_->bounds();
611 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
612 // Extend the button to the screen edge in maximized and immersive fullscreen.
613 views::Widget* widget = GetWidget();
614 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
615 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
618 void TabStrip::StartHighlight(int model_index) {
619 tab_at(model_index)->StartPulse();
622 void TabStrip::StopAllHighlighting() {
623 for (int i = 0; i < tab_count(); ++i)
624 tab_at(i)->StopPulse();
627 void TabStrip::AddTabAt(int model_index,
628 const TabRendererData& data,
629 bool is_active) {
630 // Stop dragging when a new tab is added and dragging a window. Doing
631 // otherwise results in a confusing state if the user attempts to reattach. We
632 // could allow this and make TabDragController update itself during the add,
633 // but this comes up infrequently enough that it's not work the complexity.
634 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
635 drag_controller_->is_dragging_window()) {
636 EndDrag(END_DRAG_COMPLETE);
638 Tab* tab = CreateTab();
639 tab->SetData(data);
640 UpdateTabsClosingMap(model_index, 1);
641 tabs_.Add(tab, model_index);
642 AddChildView(tab);
644 if (touch_layout_) {
645 GenerateIdealBoundsForMiniTabs(NULL);
646 int add_types = 0;
647 if (data.mini)
648 add_types |= StackedTabStripLayout::kAddTypeMini;
649 if (is_active)
650 add_types |= StackedTabStripLayout::kAddTypeActive;
651 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
654 // Don't animate the first tab, it looks weird, and don't animate anything
655 // if the containing window isn't visible yet.
656 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
657 StartInsertTabAnimation(model_index);
658 else
659 DoLayout();
661 SwapLayoutIfNecessary();
663 FOR_EACH_OBSERVER(TabStripObserver, observers_,
664 TabStripAddedTabAt(this, model_index));
667 void TabStrip::MoveTab(int from_model_index,
668 int to_model_index,
669 const TabRendererData& data) {
670 DCHECK_GT(tabs_.view_size(), 0);
671 const Tab* last_tab = GetLastVisibleTab();
672 tab_at(from_model_index)->SetData(data);
673 if (touch_layout_) {
674 tabs_.MoveViewOnly(from_model_index, to_model_index);
675 int mini_count = 0;
676 GenerateIdealBoundsForMiniTabs(&mini_count);
677 touch_layout_->MoveTab(
678 from_model_index, to_model_index, controller_->GetActiveIndex(),
679 GetStartXForNormalTabs(), mini_count);
680 } else {
681 tabs_.Move(from_model_index, to_model_index);
683 StartMoveTabAnimation();
684 if (TabDragController::IsAttachedTo(this) &&
685 (last_tab != GetLastVisibleTab() || last_tab->dragging())) {
686 newtab_button_->SetVisible(false);
688 SwapLayoutIfNecessary();
690 FOR_EACH_OBSERVER(TabStripObserver, observers_,
691 TabStripMovedTab(this, from_model_index, to_model_index));
694 void TabStrip::RemoveTabAt(int model_index) {
695 if (touch_layout_) {
696 Tab* tab = tab_at(model_index);
697 tab->set_closing(true);
698 int old_x = tabs_.ideal_bounds(model_index).x();
699 // We still need to paint the tab until we actually remove it. Put it in
700 // tabs_closing_map_ so we can find it.
701 RemoveTabFromViewModel(model_index);
702 touch_layout_->RemoveTab(model_index, GenerateIdealBoundsForMiniTabs(NULL),
703 old_x);
704 ScheduleRemoveTabAnimation(tab);
705 } else if (in_tab_close_ && model_index != GetModelCount()) {
706 StartMouseInitiatedRemoveTabAnimation(model_index);
707 } else {
708 StartRemoveTabAnimation(model_index);
710 SwapLayoutIfNecessary();
712 FOR_EACH_OBSERVER(TabStripObserver, observers_,
713 TabStripRemovedTabAt(this, model_index));
716 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
717 Tab* tab = tab_at(model_index);
718 bool mini_state_changed = tab->data().mini != data.mini;
719 tab->SetData(data);
721 if (mini_state_changed) {
722 if (touch_layout_) {
723 int mini_tab_count = 0;
724 int start_x = GenerateIdealBoundsForMiniTabs(&mini_tab_count);
725 touch_layout_->SetXAndMiniCount(start_x, mini_tab_count);
727 if (GetWidget() && GetWidget()->IsVisible())
728 StartMiniTabAnimation();
729 else
730 DoLayout();
732 SwapLayoutIfNecessary();
735 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
736 // Detached tabs should always be invisible (as they close).
737 if (tab->detached())
738 return false;
740 // When stacking tabs, all tabs should always be visible.
741 if (stacked_layout_)
742 return true;
744 // If the tab is currently clipped, it shouldn't be visible. Note that we
745 // allow dragged tabs to draw over the "New Tab button" region as well,
746 // because either the New Tab button will be hidden, or the dragged tabs will
747 // be animating back to their normal positions and we don't want to hide them
748 // in the New Tab button region in case they re-appear after leaving it.
749 // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab
750 // button area, even when the button is invisible, so that they don't appear
751 // to "pop in" when the button disappears.
752 // TODO: Probably doesn't work for RTL
753 int right_edge = tab->bounds().right();
754 const int visible_width = tab->dragging() ? width() : tab_area_width();
755 if (right_edge > visible_width)
756 return false;
758 // Non-clipped dragging tabs should always be visible.
759 if (tab->dragging())
760 return true;
762 // Let all non-clipped closing tabs be visible. These will probably finish
763 // closing before the user changes the active tab, so there's little reason to
764 // try and make the more complex logic below apply.
765 if (tab->closing())
766 return true;
768 // Now we need to check whether the tab isn't currently clipped, but could
769 // become clipped if we changed the active tab, widening either this tab or
770 // the tabstrip portion before it.
772 // Mini tabs don't change size when activated, so any tab in the mini tab
773 // region is safe.
774 if (tab->data().mini)
775 return true;
777 // If the active tab is on or before this tab, we're safe.
778 if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab))
779 return true;
781 // We need to check what would happen if the active tab were to move to this
782 // tab or before.
783 return (right_edge + current_selected_width_ - current_unselected_width_) <=
784 tab_area_width();
787 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
788 if (!in_tab_close_ && IsAnimating()) {
789 // Cancel any current animations. We do this as remove uses the current
790 // ideal bounds and we need to know ideal bounds is in a good state.
791 StopAnimating(true);
794 if (!GetWidget())
795 return;
797 int model_count = GetModelCount();
798 if (model_count > 1 && model_index != model_count - 1) {
799 // The user is about to close a tab other than the last tab. Set
800 // available_width_for_tabs_ so that if we do a layout we don't position a
801 // tab past the end of the second to last tab. We do this so that as the
802 // user closes tabs with the mouse a tab continues to fall under the mouse.
803 Tab* last_tab = tab_at(model_count - 1);
804 Tab* tab_being_removed = tab_at(model_index);
805 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
806 tab_being_removed->width() - kTabHorizontalOffset;
807 if (model_index == 0 && tab_being_removed->data().mini &&
808 !tab_at(1)->data().mini) {
809 available_width_for_tabs_ -= kMiniToNonMiniGap;
813 in_tab_close_ = true;
814 resize_layout_timer_.Stop();
815 if (source == CLOSE_TAB_FROM_TOUCH) {
816 StartResizeLayoutTabsFromTouchTimer();
817 } else {
818 AddMessageLoopObserver();
822 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
823 const ui::ListSelectionModel& new_selection) {
824 if (touch_layout_) {
825 touch_layout_->SetActiveIndex(new_selection.active());
826 // Only start an animation if we need to. Otherwise clicking on an
827 // unselected tab and dragging won't work because dragging is only allowed
828 // if not animating.
829 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
830 AnimateToIdealBounds();
831 SchedulePaint();
832 } else {
833 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
834 // a different size to the selected ones.
835 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
836 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
837 DoLayout();
838 } else {
839 SchedulePaint();
843 // Use STLSetDifference to get the indices of elements newly selected
844 // and no longer selected, since selected_indices() is always sorted.
845 ui::ListSelectionModel::SelectedIndices no_longer_selected =
846 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
847 old_selection.selected_indices(),
848 new_selection.selected_indices());
849 ui::ListSelectionModel::SelectedIndices newly_selected =
850 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
851 new_selection.selected_indices(),
852 old_selection.selected_indices());
854 // Fire accessibility events that reflect the changes to selection, and
855 // stop the mini tab title animation on tabs no longer selected.
856 for (size_t i = 0; i < no_longer_selected.size(); ++i) {
857 tab_at(no_longer_selected[i])->StopMiniTabTitleAnimation();
858 tab_at(no_longer_selected[i])->NotifyAccessibilityEvent(
859 ui::AX_EVENT_SELECTION_REMOVE, true);
861 for (size_t i = 0; i < newly_selected.size(); ++i) {
862 tab_at(newly_selected[i])->NotifyAccessibilityEvent(
863 ui::AX_EVENT_SELECTION_ADD, true);
865 tab_at(new_selection.active())->NotifyAccessibilityEvent(
866 ui::AX_EVENT_SELECTION, true);
869 void TabStrip::TabTitleChangedNotLoading(int model_index) {
870 Tab* tab = tab_at(model_index);
871 if (tab->data().mini && !tab->IsActive())
872 tab->StartMiniTabTitleAnimation();
875 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
876 return tabs_.GetIndexOfView(tab);
879 int TabStrip::GetModelCount() const {
880 return controller_->GetCount();
883 bool TabStrip::IsValidModelIndex(int model_index) const {
884 return controller_->IsValidIndex(model_index);
887 bool TabStrip::IsDragSessionActive() const {
888 return drag_controller_.get() != NULL;
891 bool TabStrip::IsActiveDropTarget() const {
892 for (int i = 0; i < tab_count(); ++i) {
893 Tab* tab = tab_at(i);
894 if (tab->dragging())
895 return true;
897 return false;
900 bool TabStrip::IsTabStripEditable() const {
901 return !IsDragSessionActive() && !IsActiveDropTarget();
904 bool TabStrip::IsTabStripCloseable() const {
905 return !IsDragSessionActive();
908 void TabStrip::UpdateLoadingAnimations() {
909 controller_->UpdateLoadingAnimations();
912 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
913 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
916 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
917 views::View* v = GetEventHandlerForRect(rect);
919 // If there is no control at this location, claim the hit was in the title
920 // bar to get a move action.
921 if (v == this)
922 return true;
924 // Check to see if the rect intersects the non-button parts of the new tab
925 // button. The button has a non-rectangular shape, so if it's not in the
926 // visual portions of the button we treat it as a click to the caption.
927 gfx::RectF rect_in_newtab_coords_f(rect);
928 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
929 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
930 rect_in_newtab_coords_f);
931 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
932 !newtab_button_->HitTestRect(rect_in_newtab_coords))
933 return true;
935 // All other regions, including the new Tab button, should be considered part
936 // of the containing Window's client area so that regular events can be
937 // processed for them.
938 return false;
941 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
942 for (int i = 0; i < tab_count(); ++i)
943 tab_at(i)->set_background_offset(offset);
944 newtab_button_->set_background_offset(offset);
947 void TabStrip::SetImmersiveStyle(bool enable) {
948 if (immersive_style_ == enable)
949 return;
950 immersive_style_ = enable;
953 bool TabStrip::IsAnimating() const {
954 return bounds_animator_.IsAnimating();
957 void TabStrip::StopAnimating(bool layout) {
958 if (!IsAnimating())
959 return;
961 bounds_animator_.Cancel();
963 if (layout)
964 DoLayout();
967 void TabStrip::FileSupported(const GURL& url, bool supported) {
968 if (drop_info_.get() && drop_info_->url == url)
969 drop_info_->file_supported = supported;
972 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
973 return controller_->GetSelectionModel();
976 bool TabStrip::SupportsMultipleSelection() {
977 // TODO: currently only allow single selection in touch layout mode.
978 return touch_layout_ == NULL;
981 bool TabStrip::ShouldHideCloseButtonForInactiveTabs() {
982 if (!touch_layout_)
983 return false;
985 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
986 switches::kDisableHideInactiveStackedTabCloseButtons);
989 void TabStrip::SelectTab(Tab* tab) {
990 int model_index = GetModelIndexOfTab(tab);
991 if (IsValidModelIndex(model_index))
992 controller_->SelectTab(model_index);
995 void TabStrip::ExtendSelectionTo(Tab* tab) {
996 int model_index = GetModelIndexOfTab(tab);
997 if (IsValidModelIndex(model_index))
998 controller_->ExtendSelectionTo(model_index);
1001 void TabStrip::ToggleSelected(Tab* tab) {
1002 int model_index = GetModelIndexOfTab(tab);
1003 if (IsValidModelIndex(model_index))
1004 controller_->ToggleSelected(model_index);
1007 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
1008 int model_index = GetModelIndexOfTab(tab);
1009 if (IsValidModelIndex(model_index))
1010 controller_->AddSelectionFromAnchorTo(model_index);
1013 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
1014 if (tab->closing()) {
1015 // If the tab is already closing, close the next tab. We do this so that the
1016 // user can rapidly close tabs by clicking the close button and not have
1017 // the animations interfere with that.
1018 const int closed_tab_index = FindClosingTab(tab).first->first;
1019 if (closed_tab_index < GetModelCount())
1020 controller_->CloseTab(closed_tab_index, source);
1021 return;
1023 int model_index = GetModelIndexOfTab(tab);
1024 if (IsValidModelIndex(model_index))
1025 controller_->CloseTab(model_index, source);
1028 void TabStrip::ToggleTabAudioMute(Tab* tab) {
1029 int model_index = GetModelIndexOfTab(tab);
1030 if (IsValidModelIndex(model_index))
1031 controller_->ToggleTabAudioMute(model_index);
1034 void TabStrip::ShowContextMenuForTab(Tab* tab,
1035 const gfx::Point& p,
1036 ui::MenuSourceType source_type) {
1037 controller_->ShowContextMenuForTab(tab, p, source_type);
1040 bool TabStrip::IsActiveTab(const Tab* tab) const {
1041 int model_index = GetModelIndexOfTab(tab);
1042 return IsValidModelIndex(model_index) &&
1043 controller_->IsActiveTab(model_index);
1046 bool TabStrip::IsTabSelected(const Tab* tab) const {
1047 int model_index = GetModelIndexOfTab(tab);
1048 return IsValidModelIndex(model_index) &&
1049 controller_->IsTabSelected(model_index);
1052 bool TabStrip::IsTabPinned(const Tab* tab) const {
1053 if (tab->closing())
1054 return false;
1056 int model_index = GetModelIndexOfTab(tab);
1057 return IsValidModelIndex(model_index) &&
1058 controller_->IsTabPinned(model_index);
1061 void TabStrip::MaybeStartDrag(
1062 Tab* tab,
1063 const ui::LocatedEvent& event,
1064 const ui::ListSelectionModel& original_selection) {
1065 // Don't accidentally start any drag operations during animations if the
1066 // mouse is down... during an animation tabs are being resized automatically,
1067 // so the View system can misinterpret this easily if the mouse is down that
1068 // the user is dragging.
1069 if (IsAnimating() || tab->closing() ||
1070 controller_->HasAvailableDragActions() == 0) {
1071 return;
1074 // Do not do any dragging of tabs when using the super short immersive style.
1075 if (IsImmersiveStyle())
1076 return;
1078 int model_index = GetModelIndexOfTab(tab);
1079 if (!IsValidModelIndex(model_index)) {
1080 CHECK(false);
1081 return;
1083 Tabs tabs;
1084 int size_to_selected = 0;
1085 int x = tab->GetMirroredXInView(event.x());
1086 int y = event.y();
1087 // Build the set of selected tabs to drag and calculate the offset from the
1088 // first selected tab.
1089 for (int i = 0; i < tab_count(); ++i) {
1090 Tab* other_tab = tab_at(i);
1091 if (IsTabSelected(other_tab)) {
1092 tabs.push_back(other_tab);
1093 if (other_tab == tab) {
1094 size_to_selected = GetSizeNeededForTabs(tabs);
1095 x = size_to_selected - tab->width() + x;
1099 DCHECK(!tabs.empty());
1100 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1101 ui::ListSelectionModel selection_model;
1102 if (!original_selection.IsSelected(model_index))
1103 selection_model.Copy(original_selection);
1104 // Delete the existing DragController before creating a new one. We do this as
1105 // creating the DragController remembers the WebContents delegates and we need
1106 // to make sure the existing DragController isn't still a delegate.
1107 drag_controller_.reset();
1108 TabDragController::MoveBehavior move_behavior =
1109 TabDragController::REORDER;
1110 // Use MOVE_VISIBILE_TABS in the following conditions:
1111 // . Mouse event generated from touch and the left button is down (the right
1112 // button corresponds to a long press, which we want to reorder).
1113 // . Gesture tap down and control key isn't down.
1114 // . Real mouse event and control is down. This is mostly for testing.
1115 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1116 event.type() == ui::ET_GESTURE_TAP_DOWN);
1117 if (touch_layout_ &&
1118 ((event.type() == ui::ET_MOUSE_PRESSED &&
1119 (((event.flags() & ui::EF_FROM_TOUCH) &&
1120 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1121 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1122 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1123 (event.type() == ui::ET_GESTURE_TAP_DOWN && !event.IsControlDown()))) {
1124 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1127 drag_controller_.reset(new TabDragController);
1128 drag_controller_->Init(
1129 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1130 move_behavior, EventSourceFromEvent(event));
1133 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1134 if (drag_controller_.get() &&
1135 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1136 gfx::Point screen_location(event.location());
1137 views::View::ConvertPointToScreen(view, &screen_location);
1138 drag_controller_->Drag(screen_location);
1142 bool TabStrip::EndDrag(EndDragReason reason) {
1143 if (!drag_controller_.get())
1144 return false;
1145 bool started_drag = drag_controller_->started_drag();
1146 drag_controller_->EndDrag(reason);
1147 return started_drag;
1150 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1151 gfx::Point local_point = tab_in_tab_coordinates;
1152 ConvertPointToTarget(tab, this, &local_point);
1154 views::View* view = GetEventHandlerForPoint(local_point);
1155 if (!view)
1156 return NULL; // No tab contains the point.
1158 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1159 while (view && view != this && view->id() != VIEW_ID_TAB)
1160 view = view->parent();
1162 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1165 void TabStrip::OnMouseEventInTab(views::View* source,
1166 const ui::MouseEvent& event) {
1167 UpdateStackedLayoutFromMouseEvent(source, event);
1170 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1171 // Only touch layout needs to restrict the clip.
1172 if (!touch_layout_ && !IsStackingDraggedTabs())
1173 return true;
1175 int index = GetModelIndexOfTab(tab);
1176 if (index == -1)
1177 return true; // Tab is closing, paint it all.
1179 int active_index = IsStackingDraggedTabs() ?
1180 controller_->GetActiveIndex() : touch_layout_->active_index();
1181 if (active_index == tab_count())
1182 active_index--;
1184 if (index < active_index) {
1185 if (tab_at(index)->x() == tab_at(index + 1)->x())
1186 return false;
1188 if (tab_at(index)->x() > tab_at(index + 1)->x())
1189 return true; // Can happen during dragging.
1191 clip->SetRect(
1192 0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + kStackedTabLeftClip,
1193 tab_at(index)->height());
1194 } else if (index > active_index && index > 0) {
1195 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1196 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1197 if (tab_bounds.x() == previous_tab_bounds.x())
1198 return false;
1200 if (tab_bounds.x() < previous_tab_bounds.x())
1201 return true; // Can happen during dragging.
1203 if (previous_tab_bounds.right() + kTabHorizontalOffset != tab_bounds.x()) {
1204 int x = previous_tab_bounds.right() - tab_bounds.x() -
1205 kStackedTabRightClip;
1206 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1209 return true;
1212 bool TabStrip::IsImmersiveStyle() const {
1213 return immersive_style_;
1216 void TabStrip::UpdateTabAccessibilityState(const Tab* tab,
1217 ui::AXViewState* state) {
1218 state->count = tab_count();
1219 state->index = GetModelIndexOfTab(tab);
1222 void TabStrip::MouseMovedOutOfHost() {
1223 ResizeLayoutTabs();
1224 if (reset_to_shrink_on_exit_) {
1225 reset_to_shrink_on_exit_ = false;
1226 SetStackedLayout(false);
1227 controller_->StackedLayoutMaybeChanged();
1231 ///////////////////////////////////////////////////////////////////////////////
1232 // TabStrip, views::View overrides:
1234 void TabStrip::Layout() {
1235 // Only do a layout if our size changed.
1236 if (last_layout_size_ == size())
1237 return;
1238 if (IsDragSessionActive())
1239 return;
1240 DoLayout();
1243 void TabStrip::PaintChildren(const PaintContext& context) {
1244 gfx::Canvas* canvas = context.canvas();
1245 // The view order doesn't match the paint order (tabs_ contains the tab
1246 // ordering). Additionally we need to paint the tabs that are closing in
1247 // |tabs_closing_map_|.
1248 Tab* active_tab = NULL;
1249 Tabs tabs_dragging;
1250 Tabs selected_tabs;
1251 int selected_tab_count = 0;
1252 bool is_dragging = false;
1253 int active_tab_index = -1;
1255 const chrome::HostDesktopType host_desktop_type =
1256 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1257 const int inactive_tab_alpha =
1258 (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH) ?
1259 kInactiveTabAndNewTabButtonAlphaAsh : kInactiveTabAndNewTabButtonAlpha;
1261 if (inactive_tab_alpha < 255)
1262 canvas->SaveLayerAlpha(inactive_tab_alpha);
1264 PaintClosingTabs(tab_count(), context);
1266 for (int i = tab_count() - 1; i >= 0; --i) {
1267 Tab* tab = tab_at(i);
1268 if (tab->IsSelected())
1269 selected_tab_count++;
1270 if (tab->dragging() && !stacked_layout_) {
1271 is_dragging = true;
1272 if (tab->IsActive()) {
1273 active_tab = tab;
1274 active_tab_index = i;
1275 } else {
1276 tabs_dragging.push_back(tab);
1278 } else if (!tab->IsActive()) {
1279 if (!tab->IsSelected()) {
1280 if (!stacked_layout_)
1281 tab->Paint(context);
1282 } else {
1283 selected_tabs.push_back(tab);
1285 } else {
1286 active_tab = tab;
1287 active_tab_index = i;
1289 PaintClosingTabs(i, context);
1292 // Draw from the left and then the right if we're in touch mode.
1293 if (stacked_layout_ && active_tab_index >= 0) {
1294 for (int i = 0; i < active_tab_index; ++i) {
1295 Tab* tab = tab_at(i);
1296 tab->Paint(context);
1299 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1300 Tab* tab = tab_at(i);
1301 tab->Paint(context);
1304 if (inactive_tab_alpha < 255)
1305 canvas->Restore();
1307 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1308 // Make sure non-active tabs are somewhat transparent.
1309 SkPaint paint;
1310 // If there are multiple tabs selected, fade non-selected tabs more to make
1311 // the selected tabs more noticable.
1312 int alpha = selected_tab_count > 1 ?
1313 kGlassFrameInactiveTabAlphaMultiSelection :
1314 kGlassFrameInactiveTabAlpha;
1315 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1316 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1317 paint.setStyle(SkPaint::kFill_Style);
1319 // The tab graphics include some shadows at the top, so the actual
1320 // tabstrip top is 4 px. above the apparent top of the tab, to provide room
1321 // to draw these. Exclude this region when trying to make tabs transparent
1322 // as it's transparent enough already, and drawing in this region can
1323 // overlap the avatar button, leading to visual artifacts.
1324 const int kTopOffset = 4;
1325 // The tabstrip area overlaps the toolbar area by 2 px.
1326 canvas->DrawRect(
1327 gfx::Rect(0, kTopOffset, width(), height() - kTopOffset - 2), paint);
1330 // Now selected but not active. We don't want these dimmed if using native
1331 // frame, so they're painted after initial pass.
1332 for (size_t i = 0; i < selected_tabs.size(); ++i)
1333 selected_tabs[i]->Paint(context);
1335 // Next comes the active tab.
1336 if (active_tab && !is_dragging)
1337 active_tab->Paint(context);
1339 // Paint the New Tab button.
1340 if (inactive_tab_alpha < 255)
1341 canvas->SaveLayerAlpha(inactive_tab_alpha);
1342 newtab_button_->Paint(context);
1343 if (inactive_tab_alpha < 255)
1344 canvas->Restore();
1346 // And the dragged tabs.
1347 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1348 tabs_dragging[i]->Paint(context);
1350 // If the active tab is being dragged, it goes last.
1351 if (active_tab && is_dragging)
1352 active_tab->Paint(context);
1355 const char* TabStrip::GetClassName() const {
1356 return kViewClassName;
1359 gfx::Size TabStrip::GetPreferredSize() const {
1360 int needed_tab_width;
1361 if (touch_layout_ || adjust_layout_) {
1362 // For stacked tabs the minimum size is calculated as the size needed to
1363 // handle showing any number of tabs.
1364 needed_tab_width =
1365 Tab::GetTouchWidth() + (2 * kStackedPadding * kMaxStackedCount);
1366 } else {
1367 // Otherwise the minimum width is based on the actual number of tabs.
1368 const int mini_tab_count = GetMiniTabCount();
1369 needed_tab_width = mini_tab_count * Tab::GetMiniWidth();
1370 const int remaining_tab_count = tab_count() - mini_tab_count;
1371 const int min_selected_width = Tab::GetMinimumSelectedSize().width();
1372 const int min_unselected_width = Tab::GetMinimumUnselectedSize().width();
1373 if (remaining_tab_count > 0) {
1374 needed_tab_width += kMiniToNonMiniGap + min_selected_width +
1375 ((remaining_tab_count - 1) * min_unselected_width);
1377 if (tab_count() > 1)
1378 needed_tab_width += (tab_count() - 1) * kTabHorizontalOffset;
1380 // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1381 // and don't force it to be larger than is necessary to show 20 tabs.
1382 const int largest_min_tab_width =
1383 min_selected_width + 19 * (min_unselected_width + kTabHorizontalOffset);
1384 needed_tab_width = std::min(
1385 std::max(needed_tab_width, min_selected_width), largest_min_tab_width);
1387 return gfx::Size(
1388 needed_tab_width + new_tab_button_width(),
1389 immersive_style_ ?
1390 Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1393 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1394 // Force animations to stop, otherwise it makes the index calculation tricky.
1395 StopAnimating(true);
1397 UpdateDropIndex(event);
1399 GURL url;
1400 base::string16 title;
1402 // Check whether the event data includes supported drop data.
1403 if (event.data().GetURLAndTitle(
1404 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1405 url.is_valid()) {
1406 drop_info_->url = url;
1408 // For file:// URLs, kick off a MIME type request in case they're dropped.
1409 if (url.SchemeIsFile())
1410 controller()->CheckFileSupported(url);
1414 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1415 // Update the drop index even if the file is unsupported, to allow
1416 // dragging a file to the contents of another tab.
1417 UpdateDropIndex(event);
1419 if (!drop_info_->file_supported)
1420 return ui::DragDropTypes::DRAG_NONE;
1422 return GetDropEffect(event);
1425 void TabStrip::OnDragExited() {
1426 SetDropIndex(-1, false);
1429 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1430 if (!drop_info_.get())
1431 return ui::DragDropTypes::DRAG_NONE;
1433 const int drop_index = drop_info_->drop_index;
1434 const bool drop_before = drop_info_->drop_before;
1435 const bool file_supported = drop_info_->file_supported;
1437 // Hide the drop indicator.
1438 SetDropIndex(-1, false);
1440 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1441 // have been changed after |drop_info_| was created.
1442 GURL url;
1443 base::string16 title;
1444 if (!file_supported ||
1445 !event.data().GetURLAndTitle(
1446 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1447 !url.is_valid())
1448 return ui::DragDropTypes::DRAG_NONE;
1450 controller()->PerformDrop(drop_before, drop_index, url);
1452 return GetDropEffect(event);
1455 void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1456 state->role = ui::AX_ROLE_TAB_LIST;
1459 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1460 if (!HitTestPoint(point))
1461 return NULL;
1463 if (!touch_layout_) {
1464 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1465 // want to interfere.
1466 views::View* v = View::GetTooltipHandlerForPoint(point);
1467 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1468 return v;
1470 views::View* tab = FindTabHitByPoint(point);
1471 if (tab)
1472 return tab;
1473 } else {
1474 if (newtab_button_->visible()) {
1475 views::View* view =
1476 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1477 if (view)
1478 return view;
1480 Tab* tab = FindTabForEvent(point);
1481 if (tab)
1482 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1484 return this;
1487 // static
1488 int TabStrip::GetImmersiveHeight() {
1489 return Tab::GetImmersiveHeight();
1492 ///////////////////////////////////////////////////////////////////////////////
1493 // TabStrip, private:
1495 void TabStrip::Init() {
1496 set_id(VIEW_ID_TAB_STRIP);
1497 // So we get enter/exit on children to switch stacked layout on and off.
1498 set_notify_enter_exit_on_child(true);
1499 newtab_button_bounds_.SetRect(0,
1501 kNewTabButtonAssetWidth,
1502 kNewTabButtonAssetHeight +
1503 kNewTabButtonVerticalOffset);
1504 newtab_button_ = new NewTabButton(this, this);
1505 newtab_button_->SetTooltipText(
1506 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1507 newtab_button_->SetAccessibleName(
1508 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1509 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1510 views::ImageButton::ALIGN_BOTTOM);
1511 newtab_button_->SetEventTargeter(
1512 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(newtab_button_)));
1513 AddChildView(newtab_button_);
1515 if (drop_indicator_width == 0) {
1516 // Direction doesn't matter, both images are the same size.
1517 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1518 drop_indicator_width = drop_image->width();
1519 drop_indicator_height = drop_image->height();
1523 Tab* TabStrip::CreateTab() {
1524 Tab* tab = new Tab(this);
1525 tab->set_animation_container(animation_container_.get());
1526 return tab;
1529 void TabStrip::StartInsertTabAnimation(int model_index) {
1530 PrepareForAnimation();
1532 // The TabStrip can now use its entire width to lay out Tabs.
1533 in_tab_close_ = false;
1534 available_width_for_tabs_ = -1;
1536 GenerateIdealBounds();
1538 Tab* tab = tab_at(model_index);
1539 if (model_index == 0) {
1540 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1541 ideal_bounds(model_index).height());
1542 } else {
1543 Tab* last_tab = tab_at(model_index - 1);
1544 tab->SetBounds(last_tab->bounds().right() + kTabHorizontalOffset,
1545 ideal_bounds(model_index).y(), 0,
1546 ideal_bounds(model_index).height());
1549 AnimateToIdealBounds();
1552 void TabStrip::StartMoveTabAnimation() {
1553 PrepareForAnimation();
1554 GenerateIdealBounds();
1555 AnimateToIdealBounds();
1558 void TabStrip::StartRemoveTabAnimation(int model_index) {
1559 PrepareForAnimation();
1561 // Mark the tab as closing.
1562 Tab* tab = tab_at(model_index);
1563 tab->set_closing(true);
1565 RemoveTabFromViewModel(model_index);
1567 ScheduleRemoveTabAnimation(tab);
1570 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1571 // Start an animation for the tabs.
1572 GenerateIdealBounds();
1573 AnimateToIdealBounds();
1575 // Animate the tab being closed to zero width.
1576 gfx::Rect tab_bounds = tab->bounds();
1577 tab_bounds.set_width(0);
1578 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1579 bounds_animator_.SetAnimationDelegate(
1580 tab,
1581 scoped_ptr<gfx::AnimationDelegate>(new RemoveTabDelegate(this, tab)));
1583 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1584 // like the new tab button magically appears from beyond the end of the tab
1585 // strip.
1586 if (TabDragController::IsAttachedTo(this)) {
1587 bounds_animator_.StopAnimatingView(newtab_button_);
1588 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1592 void TabStrip::AnimateToIdealBounds() {
1593 for (int i = 0; i < tab_count(); ++i) {
1594 Tab* tab = tab_at(i);
1595 if (!tab->dragging()) {
1596 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1597 bounds_animator_.SetAnimationDelegate(
1598 tab,
1599 scoped_ptr<gfx::AnimationDelegate>(
1600 new TabAnimationDelegate(this, tab)));
1604 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1607 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1608 return in_tab_close_;
1611 void TabStrip::DoLayout() {
1612 last_layout_size_ = size();
1614 StopAnimating(false);
1616 SwapLayoutIfNecessary();
1618 if (touch_layout_)
1619 touch_layout_->SetWidth(tab_area_width());
1621 GenerateIdealBounds();
1623 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1624 SetTabVisibility();
1626 SchedulePaint();
1628 bounds_animator_.StopAnimatingView(newtab_button_);
1629 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1632 void TabStrip::SetTabVisibility() {
1633 // We could probably be more efficient here by making use of the fact that the
1634 // tabstrip will always have any visible tabs, and then any invisible tabs, so
1635 // we could e.g. binary-search for the changeover point. But since we have to
1636 // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1637 // worth it.
1638 for (int i = 0; i < tab_count(); ++i) {
1639 Tab* tab = tab_at(i);
1640 tab->SetVisible(ShouldTabBeVisible(tab));
1642 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1643 i != tabs_closing_map_.end(); ++i) {
1644 for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) {
1645 Tab* tab = *j;
1646 tab->SetVisible(ShouldTabBeVisible(tab));
1651 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1652 int delta) {
1653 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1654 if (!touch_layout_) {
1655 StackDraggedTabs(delta);
1656 return;
1658 SetIdealBoundsFromPositions(initial_positions);
1659 touch_layout_->DragActiveTab(delta);
1660 DoLayout();
1663 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1664 if (static_cast<size_t>(tab_count()) != positions.size())
1665 return;
1667 for (int i = 0; i < tab_count(); ++i) {
1668 gfx::Rect bounds(ideal_bounds(i));
1669 bounds.set_x(positions[i]);
1670 tabs_.set_ideal_bounds(i, bounds);
1674 void TabStrip::StackDraggedTabs(int delta) {
1675 DCHECK(!touch_layout_);
1676 GenerateIdealBounds();
1677 const int active_index = controller_->GetActiveIndex();
1678 DCHECK_NE(-1, active_index);
1679 if (delta < 0) {
1680 // Drag the tabs to the left, stacking tabs before the active tab.
1681 const int adjusted_delta =
1682 std::min(ideal_bounds(active_index).x() -
1683 kStackedPadding * std::min(active_index, kMaxStackedCount),
1684 -delta);
1685 for (int i = 0; i <= active_index; ++i) {
1686 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1687 gfx::Rect new_bounds(ideal_bounds(i));
1688 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1689 tabs_.set_ideal_bounds(i, new_bounds);
1691 const bool is_active_mini = tab_at(active_index)->data().mini;
1692 const int active_width = ideal_bounds(active_index).width();
1693 for (int i = active_index + 1; i < tab_count(); ++i) {
1694 const int max_x = ideal_bounds(active_index).x() +
1695 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1696 gfx::Rect new_bounds(ideal_bounds(i));
1697 int new_x = std::max(new_bounds.x() + delta, max_x);
1698 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini &&
1699 new_bounds.width() != active_width)
1700 new_x += (active_width - new_bounds.width());
1701 new_bounds.set_x(new_x);
1702 tabs_.set_ideal_bounds(i, new_bounds);
1704 } else {
1705 // Drag the tabs to the right, stacking tabs after the active tab.
1706 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1707 const int last_tab_x = tab_area_width() - last_tab_width;
1708 if (active_index == tab_count() - 1 &&
1709 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1710 return;
1711 const int adjusted_delta =
1712 std::min(last_tab_x -
1713 kStackedPadding * std::min(tab_count() - active_index - 1,
1714 kMaxStackedCount) -
1715 ideal_bounds(active_index).x(),
1716 delta);
1717 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1718 --i) {
1719 const int max_x = last_tab_x -
1720 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1721 gfx::Rect new_bounds(ideal_bounds(i));
1722 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1723 // Because of rounding not all tabs are the same width. Adjust the
1724 // position to accommodate this, otherwise the stacking is off.
1725 if (new_x == max_x && !tab_at(i)->data().mini &&
1726 new_bounds.width() != last_tab_width)
1727 new_x += (last_tab_width - new_bounds.width());
1728 new_bounds.set_x(new_x);
1729 tabs_.set_ideal_bounds(i, new_bounds);
1731 for (int i = active_index - 1; i >= 0; --i) {
1732 const int min_x = ideal_bounds(active_index).x() -
1733 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1734 gfx::Rect new_bounds(ideal_bounds(i));
1735 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1736 tabs_.set_ideal_bounds(i, new_bounds);
1738 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1739 newtab_button_->SetVisible(false);
1741 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1742 SchedulePaint();
1745 bool TabStrip::IsStackingDraggedTabs() const {
1746 return drag_controller_.get() && drag_controller_->started_drag() &&
1747 (drag_controller_->move_behavior() ==
1748 TabDragController::MOVE_VISIBILE_TABS);
1751 void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs,
1752 Tab* active_tab,
1753 const gfx::Point& location,
1754 bool initial_drag) {
1755 // Immediately hide the new tab button if the last tab is being dragged.
1756 const Tab* last_visible_tab = GetLastVisibleTab();
1757 if (last_visible_tab && last_visible_tab->dragging())
1758 newtab_button_->SetVisible(false);
1759 std::vector<gfx::Rect> bounds;
1760 CalculateBoundsForDraggedTabs(tabs, &bounds);
1761 DCHECK_EQ(tabs.size(), bounds.size());
1762 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1763 int active_tab_index = static_cast<int>(
1764 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1765 for (size_t i = 0; i < tabs.size(); ++i) {
1766 Tab* tab = tabs[i];
1767 gfx::Rect new_bounds = bounds[i];
1768 new_bounds.Offset(location.x(), location.y());
1769 int consecutive_index =
1770 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1771 // If this is the initial layout during a drag and the tabs aren't
1772 // consecutive animate the view into position. Do the same if the tab is
1773 // already animating (which means we previously caused it to animate).
1774 if ((initial_drag &&
1775 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1776 bounds_animator_.IsAnimating(tabs[i])) {
1777 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1778 } else {
1779 tab->SetBoundsRect(new_bounds);
1782 SetTabVisibility();
1785 void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs,
1786 std::vector<gfx::Rect>* bounds) {
1787 int x = 0;
1788 for (size_t i = 0; i < tabs.size(); ++i) {
1789 Tab* tab = tabs[i];
1790 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1791 x += kMiniToNonMiniGap;
1792 gfx::Rect new_bounds = tab->bounds();
1793 new_bounds.set_origin(gfx::Point(x, 0));
1794 bounds->push_back(new_bounds);
1795 x += tab->width() + kTabHorizontalOffset;
1799 int TabStrip::GetSizeNeededForTabs(const Tabs& tabs) {
1800 int width = 0;
1801 for (size_t i = 0; i < tabs.size(); ++i) {
1802 Tab* tab = tabs[i];
1803 width += tab->width();
1804 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini)
1805 width += kMiniToNonMiniGap;
1807 if (tabs.size() > 0)
1808 width += kTabHorizontalOffset * static_cast<int>(tabs.size() - 1);
1809 return width;
1812 int TabStrip::GetMiniTabCount() const {
1813 int mini_count = 0;
1814 while (mini_count < tab_count() && tab_at(mini_count)->data().mini)
1815 mini_count++;
1816 return mini_count;
1819 const Tab* TabStrip::GetLastVisibleTab() const {
1820 for (int i = tab_count() - 1; i >= 0; --i) {
1821 const Tab* tab = tab_at(i);
1822 if (tab->visible())
1823 return tab;
1825 // While in normal use the tabstrip should always be wide enough to have at
1826 // least one visible tab, it can be zero-width in tests, meaning we get here.
1827 return NULL;
1830 void TabStrip::RemoveTabFromViewModel(int index) {
1831 // We still need to paint the tab until we actually remove it. Put it
1832 // in tabs_closing_map_ so we can find it.
1833 tabs_closing_map_[index].push_back(tab_at(index));
1834 UpdateTabsClosingMap(index + 1, -1);
1835 tabs_.Remove(index);
1838 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1839 scoped_ptr<Tab> deleter(tab);
1840 FindClosingTabResult res(FindClosingTab(tab));
1841 res.first->second.erase(res.second);
1842 if (res.first->second.empty())
1843 tabs_closing_map_.erase(res.first);
1846 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1847 if (tabs_closing_map_.empty())
1848 return;
1850 if (delta == -1 &&
1851 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1852 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1853 const Tabs& tabs(tabs_closing_map_[index]);
1854 tabs_closing_map_[index - 1].insert(
1855 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1857 TabsClosingMap updated_map;
1858 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1859 i != tabs_closing_map_.end(); ++i) {
1860 if (i->first > index)
1861 updated_map[i->first + delta] = i->second;
1862 else if (i->first < index)
1863 updated_map[i->first] = i->second;
1865 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1866 updated_map[index + delta] = tabs_closing_map_[index];
1867 tabs_closing_map_.swap(updated_map);
1870 void TabStrip::StartedDraggingTabs(const Tabs& tabs) {
1871 // Let the controller know that the user started dragging tabs.
1872 controller()->OnStartedDraggingTabs();
1874 // Hide the new tab button immediately if we didn't originate the drag.
1875 if (!drag_controller_.get())
1876 newtab_button_->SetVisible(false);
1878 PrepareForAnimation();
1880 // Reset dragging state of existing tabs.
1881 for (int i = 0; i < tab_count(); ++i)
1882 tab_at(i)->set_dragging(false);
1884 for (size_t i = 0; i < tabs.size(); ++i) {
1885 tabs[i]->set_dragging(true);
1886 bounds_animator_.StopAnimatingView(tabs[i]);
1889 // Move the dragged tabs to their ideal bounds.
1890 GenerateIdealBounds();
1892 // Sets the bounds of the dragged tabs.
1893 for (size_t i = 0; i < tabs.size(); ++i) {
1894 int tab_data_index = GetModelIndexOfTab(tabs[i]);
1895 DCHECK_NE(-1, tab_data_index);
1896 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1898 SetTabVisibility();
1899 SchedulePaint();
1902 void TabStrip::DraggedTabsDetached() {
1903 // Let the controller know that the user is not dragging this tabstrip's tabs
1904 // anymore.
1905 controller()->OnStoppedDraggingTabs();
1906 newtab_button_->SetVisible(true);
1909 void TabStrip::StoppedDraggingTabs(const Tabs& tabs,
1910 const std::vector<int>& initial_positions,
1911 bool move_only,
1912 bool completed) {
1913 // Let the controller know that the user stopped dragging tabs.
1914 controller()->OnStoppedDraggingTabs();
1916 newtab_button_->SetVisible(true);
1917 if (move_only && touch_layout_) {
1918 if (completed)
1919 touch_layout_->SizeToFit();
1920 else
1921 SetIdealBoundsFromPositions(initial_positions);
1923 bool is_first_tab = true;
1924 for (size_t i = 0; i < tabs.size(); ++i)
1925 StoppedDraggingTab(tabs[i], &is_first_tab);
1928 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1929 int tab_data_index = GetModelIndexOfTab(tab);
1930 if (tab_data_index == -1) {
1931 // The tab was removed before the drag completed. Don't do anything.
1932 return;
1935 if (*is_first_tab) {
1936 *is_first_tab = false;
1937 PrepareForAnimation();
1939 // Animate the view back to its correct position.
1940 GenerateIdealBounds();
1941 AnimateToIdealBounds();
1943 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1944 // Install a delegate to reset the dragging state when done. We have to leave
1945 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1946 bounds_animator_.SetAnimationDelegate(
1947 tab,
1948 scoped_ptr<gfx::AnimationDelegate>(
1949 new ResetDraggingStateDelegate(this, tab)));
1952 void TabStrip::OwnDragController(TabDragController* controller) {
1953 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1954 // via corresponding calls to TabDragController::Detach() and
1955 // TabDragController::Attach(). There is one exception to that rule: when a
1956 // drag might start, we create a TabDragController that is owned by the
1957 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1958 // we then call Attach() on the source tabstrip, but since the source tabstrip
1959 // already owns the TabDragController, so we don't need to do anything.
1960 if (controller != drag_controller_.get())
1961 drag_controller_.reset(controller);
1964 void TabStrip::DestroyDragController() {
1965 newtab_button_->SetVisible(true);
1966 drag_controller_.reset();
1969 TabDragController* TabStrip::ReleaseDragController() {
1970 return drag_controller_.release();
1973 TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
1974 DCHECK(tab->closing());
1975 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1976 i != tabs_closing_map_.end(); ++i) {
1977 Tabs::iterator j = std::find(i->second.begin(), i->second.end(), tab);
1978 if (j != i->second.end())
1979 return FindClosingTabResult(i, j);
1981 NOTREACHED();
1982 return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
1985 void TabStrip::PaintClosingTabs(int index, const PaintContext& context) {
1986 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
1987 return;
1989 const Tabs& tabs = tabs_closing_map_[index];
1990 for (Tabs::const_reverse_iterator i(tabs.rbegin()); i != tabs.rend(); ++i)
1991 (*i)->Paint(context);
1994 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
1995 const ui::MouseEvent& event) {
1996 if (!adjust_layout_)
1997 return;
1999 // The following code attempts to switch to shrink (not stacked) layout when
2000 // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
2001 // to stacked layout when a touch device is used. This is made problematic by
2002 // windows generating mouse move events that do not clearly indicate the move
2003 // is the result of a touch device. This assumes a real mouse is used if
2004 // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
2005 // the time window |kMouseMoveTimeMS|. At the time we get a mouse press we
2006 // know whether its from a touch device or not, but we don't layout then else
2007 // everything shifts. Instead we wait for the release.
2009 // TODO(sky): revisit this when touch events are really plumbed through.
2011 switch (event.type()) {
2012 case ui::ET_MOUSE_PRESSED:
2013 mouse_move_count_ = 0;
2014 last_mouse_move_time_ = base::TimeTicks();
2015 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
2016 if (reset_to_shrink_on_exit_ && touch_layout_) {
2017 gfx::Point tab_strip_point(event.location());
2018 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
2019 Tab* tab = FindTabForEvent(tab_strip_point);
2020 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
2021 SetStackedLayout(false);
2022 controller_->StackedLayoutMaybeChanged();
2025 break;
2027 case ui::ET_MOUSE_MOVED: {
2028 #if defined(USE_ASH)
2029 // Ash does not synthesize mouse events from touch events.
2030 SetResetToShrinkOnExit(true);
2031 #else
2032 gfx::Point location(event.location());
2033 ConvertPointToTarget(source, this, &location);
2034 if (location == last_mouse_move_location_)
2035 return; // Ignore spurious moves.
2036 last_mouse_move_location_ = location;
2037 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2038 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2039 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2040 kMouseMoveTimeMS) {
2041 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2042 SetResetToShrinkOnExit(true);
2043 } else {
2044 mouse_move_count_ = 1;
2045 last_mouse_move_time_ = base::TimeTicks::Now();
2047 } else {
2048 last_mouse_move_time_ = base::TimeTicks();
2050 #endif
2051 break;
2054 case ui::ET_MOUSE_RELEASED: {
2055 gfx::Point location(event.location());
2056 ConvertPointToTarget(source, this, &location);
2057 last_mouse_move_location_ = location;
2058 mouse_move_count_ = 0;
2059 last_mouse_move_time_ = base::TimeTicks();
2060 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2061 SetStackedLayout(true);
2062 controller_->StackedLayoutMaybeChanged();
2064 break;
2067 default:
2068 break;
2072 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2073 double* selected_width) const {
2074 *unselected_width = current_unselected_width_;
2075 *selected_width = current_selected_width_;
2078 void TabStrip::GetDesiredTabWidths(int tab_count,
2079 int mini_tab_count,
2080 double* unselected_width,
2081 double* selected_width) const {
2082 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count);
2083 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2084 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2086 *unselected_width = min_unselected_width;
2087 *selected_width = min_selected_width;
2089 if (tab_count == 0) {
2090 // Return immediately to avoid divide-by-zero below.
2091 return;
2094 // Determine how much space we can actually allocate to tabs.
2095 int available_width = (available_width_for_tabs_ < 0) ?
2096 tab_area_width() : available_width_for_tabs_;
2097 if (mini_tab_count > 0) {
2098 available_width -=
2099 mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset);
2100 tab_count -= mini_tab_count;
2101 if (tab_count == 0) {
2102 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2103 return;
2105 // Account for gap between the last mini-tab and first non-mini-tab.
2106 available_width -= kMiniToNonMiniGap;
2109 // Calculate the desired tab widths by dividing the available space into equal
2110 // portions. Don't let tabs get larger than the "standard width" or smaller
2111 // than the minimum width for each type, respectively.
2112 const int total_offset = kTabHorizontalOffset * (tab_count - 1);
2113 const double desired_tab_width = std::min((static_cast<double>(
2114 available_width - total_offset) / static_cast<double>(tab_count)),
2115 static_cast<double>(Tab::GetStandardSize().width()));
2116 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2117 *selected_width = std::max(desired_tab_width, min_selected_width);
2119 // When there are multiple tabs, we'll have one selected and some unselected
2120 // tabs. If the desired width was between the minimum sizes of these types,
2121 // try to shrink the tabs with the smaller minimum. For example, if we have
2122 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2123 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2124 // width of 1, the above code would set *unselected_width = 2.5,
2125 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2126 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2127 // of 10.
2128 if (tab_count > 1) {
2129 if (desired_tab_width < min_selected_width) {
2130 // Unselected width = (total width - selected width) / (num_tabs - 1)
2131 *unselected_width = std::max(static_cast<double>(
2132 available_width - total_offset - min_selected_width) /
2133 static_cast<double>(tab_count - 1), min_unselected_width);
2138 void TabStrip::ResizeLayoutTabs() {
2139 // We've been called back after the TabStrip has been emptied out (probably
2140 // just prior to the window being destroyed). We need to do nothing here or
2141 // else GetTabAt below will crash.
2142 if (tab_count() == 0)
2143 return;
2145 // It is critically important that this is unhooked here, otherwise we will
2146 // keep spying on messages forever.
2147 RemoveMessageLoopObserver();
2149 in_tab_close_ = false;
2150 available_width_for_tabs_ = -1;
2151 int mini_tab_count = GetMiniTabCount();
2152 if (mini_tab_count == tab_count()) {
2153 // Only mini-tabs, we know the tab widths won't have changed (all
2154 // mini-tabs have the same width), so there is nothing to do.
2155 return;
2157 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2158 // then the width of the active tab may not change, but other widths may
2159 // have. This is particularly important if we've overflowed (all tabs are at
2160 // the min).
2161 StartResizeLayoutAnimation();
2164 void TabStrip::ResizeLayoutTabsFromTouch() {
2165 // Don't resize if the user is interacting with the tabstrip.
2166 if (!drag_controller_.get())
2167 ResizeLayoutTabs();
2168 else
2169 StartResizeLayoutTabsFromTouchTimer();
2172 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2173 resize_layout_timer_.Stop();
2174 resize_layout_timer_.Start(
2175 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2176 this, &TabStrip::ResizeLayoutTabsFromTouch);
2179 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2180 StopAnimating(false);
2181 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2182 for (int i = 0; i < tab_count(); ++i)
2183 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2184 // Reset the layout size as we've effectively layed out a different size.
2185 // This ensures a layout happens after the drag is done.
2186 last_layout_size_ = gfx::Size();
2189 void TabStrip::AddMessageLoopObserver() {
2190 if (!mouse_watcher_.get()) {
2191 mouse_watcher_.reset(
2192 new views::MouseWatcher(
2193 new views::MouseWatcherViewHost(
2194 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2195 this));
2197 mouse_watcher_->Start();
2200 void TabStrip::RemoveMessageLoopObserver() {
2201 mouse_watcher_.reset(NULL);
2204 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2205 bool drop_before,
2206 bool* is_beneath) {
2207 DCHECK_NE(drop_index, -1);
2208 int center_x;
2209 if (drop_index < tab_count()) {
2210 Tab* tab = tab_at(drop_index);
2211 if (drop_before)
2212 center_x = tab->x() - (kTabHorizontalOffset / 2);
2213 else
2214 center_x = tab->x() + (tab->width() / 2);
2215 } else {
2216 Tab* last_tab = tab_at(drop_index - 1);
2217 center_x = last_tab->x() + last_tab->width() + (kTabHorizontalOffset / 2);
2220 // Mirror the center point if necessary.
2221 center_x = GetMirroredXInView(center_x);
2223 // Determine the screen bounds.
2224 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2225 -drop_indicator_height);
2226 ConvertPointToScreen(this, &drop_loc);
2227 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2228 drop_indicator_height);
2230 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2231 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2232 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2233 *is_beneath = !display.bounds().Contains(drop_bounds);
2234 if (*is_beneath)
2235 drop_bounds.Offset(0, drop_bounds.height() + height());
2237 return drop_bounds;
2240 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2241 // If the UI layout is right-to-left, we need to mirror the mouse
2242 // coordinates since we calculate the drop index based on the
2243 // original (and therefore non-mirrored) positions of the tabs.
2244 const int x = GetMirroredXInView(event.x());
2245 // We don't allow replacing the urls of mini-tabs.
2246 for (int i = GetMiniTabCount(); i < tab_count(); ++i) {
2247 Tab* tab = tab_at(i);
2248 const int tab_max_x = tab->x() + tab->width();
2249 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2250 if (x < tab_max_x) {
2251 if (x < tab->x() + hot_width)
2252 SetDropIndex(i, true);
2253 else if (x >= tab_max_x - hot_width)
2254 SetDropIndex(i + 1, true);
2255 else
2256 SetDropIndex(i, false);
2257 return;
2261 // The drop isn't over a tab, add it to the end.
2262 SetDropIndex(tab_count(), true);
2265 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2266 // Let the controller know of the index update.
2267 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2269 if (tab_data_index == -1) {
2270 if (drop_info_.get())
2271 drop_info_.reset(NULL);
2272 return;
2275 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2276 drop_info_->drop_before == drop_before) {
2277 return;
2280 bool is_beneath;
2281 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2282 &is_beneath);
2284 if (!drop_info_.get()) {
2285 drop_info_.reset(
2286 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2287 } else {
2288 drop_info_->drop_index = tab_data_index;
2289 drop_info_->drop_before = drop_before;
2290 if (is_beneath == drop_info_->point_down) {
2291 drop_info_->point_down = !is_beneath;
2292 drop_info_->arrow_view->SetImage(
2293 GetDropArrowImage(drop_info_->point_down));
2297 // Reposition the window. Need to show it too as the window is initially
2298 // hidden.
2299 drop_info_->arrow_window->SetBounds(drop_bounds);
2300 drop_info_->arrow_window->Show();
2303 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2304 const int source_ops = event.source_operations();
2305 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2306 return ui::DragDropTypes::DRAG_COPY;
2307 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2308 return ui::DragDropTypes::DRAG_LINK;
2309 return ui::DragDropTypes::DRAG_MOVE;
2312 // static
2313 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2314 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2315 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2318 // TabStrip::DropInfo ----------------------------------------------------------
2320 TabStrip::DropInfo::DropInfo(int drop_index,
2321 bool drop_before,
2322 bool point_down,
2323 views::Widget* context)
2324 : drop_index(drop_index),
2325 drop_before(drop_before),
2326 point_down(point_down),
2327 file_supported(true) {
2328 arrow_view = new views::ImageView;
2329 arrow_view->SetImage(GetDropArrowImage(point_down));
2331 arrow_window = new views::Widget;
2332 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2333 params.keep_on_top = true;
2334 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2335 params.accept_events = false;
2336 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2337 params.context = context->GetNativeWindow();
2338 arrow_window->Init(params);
2339 arrow_window->SetContentsView(arrow_view);
2342 TabStrip::DropInfo::~DropInfo() {
2343 // Close eventually deletes the window, which deletes arrow_view too.
2344 arrow_window->Close();
2347 ///////////////////////////////////////////////////////////////////////////////
2349 void TabStrip::PrepareForAnimation() {
2350 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2351 for (int i = 0; i < tab_count(); ++i)
2352 tab_at(i)->set_dragging(false);
2356 void TabStrip::GenerateIdealBounds() {
2357 int new_tab_y = 0;
2359 if (touch_layout_) {
2360 if (tabs_.view_size() == 0)
2361 return;
2363 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2364 kNewTabButtonHorizontalOffset;
2365 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2366 return;
2369 GetDesiredTabWidths(tab_count(), GetMiniTabCount(),
2370 &current_unselected_width_, &current_selected_width_);
2372 // NOTE: This currently assumes a tab's height doesn't differ based on
2373 // selected state or the number of tabs in the strip!
2374 int tab_height = Tab::GetStandardSize().height();
2375 int first_non_mini_index = 0;
2376 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index);
2377 for (int i = first_non_mini_index; i < tab_count(); ++i) {
2378 Tab* tab = tab_at(i);
2379 DCHECK(!tab->data().mini);
2380 double tab_width =
2381 tab->IsActive() ? current_selected_width_ : current_unselected_width_;
2382 double end_of_tab = tab_x + tab_width;
2383 int rounded_tab_x = Round(tab_x);
2384 tabs_.set_ideal_bounds(
2386 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2387 tab_height));
2388 tab_x = end_of_tab + kTabHorizontalOffset;
2391 // Update bounds of new tab button.
2392 int new_tab_x;
2393 if ((Tab::GetStandardSize().width() - Round(current_unselected_width_)) > 1 &&
2394 !in_tab_close_) {
2395 // We're shrinking tabs, so we need to anchor the New Tab button to the
2396 // right edge of the TabStrip's bounds, rather than the right edge of the
2397 // right-most Tab, otherwise it'll bounce when animating.
2398 new_tab_x = width() - newtab_button_bounds_.width();
2399 } else {
2400 new_tab_x = Round(tab_x - kTabHorizontalOffset) +
2401 kNewTabButtonHorizontalOffset;
2403 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2406 int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) {
2407 int next_x = 0;
2408 int mini_width = Tab::GetMiniWidth();
2409 int tab_height = Tab::GetStandardSize().height();
2410 int index = 0;
2411 for (; index < tab_count() && tab_at(index)->data().mini; ++index) {
2412 tabs_.set_ideal_bounds(index, gfx::Rect(next_x, 0, mini_width, tab_height));
2413 next_x += mini_width + kTabHorizontalOffset;
2415 if (index > 0 && index < tab_count())
2416 next_x += kMiniToNonMiniGap;
2417 if (first_non_mini_index)
2418 *first_non_mini_index = index;
2419 return next_x;
2422 void TabStrip::StartResizeLayoutAnimation() {
2423 PrepareForAnimation();
2424 GenerateIdealBounds();
2425 AnimateToIdealBounds();
2428 void TabStrip::StartMiniTabAnimation() {
2429 in_tab_close_ = false;
2430 available_width_for_tabs_ = -1;
2432 PrepareForAnimation();
2434 GenerateIdealBounds();
2435 AnimateToIdealBounds();
2438 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2439 // The user initiated the close. We want to persist the bounds of all the
2440 // existing tabs, so we manually shift ideal_bounds then animate.
2441 Tab* tab_closing = tab_at(model_index);
2442 int delta = tab_closing->width() + kTabHorizontalOffset;
2443 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to
2444 // add the extra padding.
2445 DCHECK_LT(model_index, tab_count() - 1);
2446 if (tab_closing->data().mini && !tab_at(model_index + 1)->data().mini)
2447 delta += kMiniToNonMiniGap;
2449 for (int i = model_index + 1; i < tab_count(); ++i) {
2450 gfx::Rect bounds = ideal_bounds(i);
2451 bounds.set_x(bounds.x() - delta);
2452 tabs_.set_ideal_bounds(i, bounds);
2455 // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2456 // overflow tabs that will be able to animate into the strip, in which case
2457 // the new tab button should stay where it is.
2458 newtab_button_bounds_.set_x(std::min(
2459 width() - newtab_button_bounds_.width(),
2460 ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset));
2462 PrepareForAnimation();
2464 tab_closing->set_closing(true);
2466 // We still need to paint the tab until we actually remove it. Put it in
2467 // tabs_closing_map_ so we can find it.
2468 RemoveTabFromViewModel(model_index);
2470 AnimateToIdealBounds();
2472 gfx::Rect tab_bounds = tab_closing->bounds();
2473 tab_bounds.set_width(0);
2474 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2476 // Register delegate to do cleanup when done, BoundsAnimator takes
2477 // ownership of RemoveTabDelegate.
2478 bounds_animator_.SetAnimationDelegate(
2479 tab_closing,
2480 scoped_ptr<gfx::AnimationDelegate>(
2481 new RemoveTabDelegate(this, tab_closing)));
2484 bool TabStrip::IsPointInTab(Tab* tab,
2485 const gfx::Point& point_in_tabstrip_coords) {
2486 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2487 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2488 return tab->HitTestPoint(point_in_tab_coords);
2491 int TabStrip::GetStartXForNormalTabs() const {
2492 int mini_tab_count = GetMiniTabCount();
2493 if (mini_tab_count == 0)
2494 return 0;
2495 return mini_tab_count * (Tab::GetMiniWidth() + kTabHorizontalOffset) +
2496 kMiniToNonMiniGap;
2499 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2500 if (touch_layout_) {
2501 int active_tab_index = touch_layout_->active_index();
2502 if (active_tab_index != -1) {
2503 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2504 if (!tab)
2505 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2506 return tab;
2508 if (tab_count())
2509 return FindTabForEventFrom(point, 0, 1);
2510 } else {
2511 for (int i = 0; i < tab_count(); ++i) {
2512 if (IsPointInTab(tab_at(i), point))
2513 return tab_at(i);
2516 return NULL;
2519 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2520 int start,
2521 int delta) {
2522 // |start| equals tab_count() when there are only pinned tabs.
2523 if (start == tab_count())
2524 start += delta;
2525 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2526 if (IsPointInTab(tab_at(i), point))
2527 return tab_at(i);
2529 return NULL;
2532 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2533 // The display order doesn't necessarily match the child list order, so we
2534 // walk the display list hit-testing Tabs. Since the active tab always
2535 // renders on top of adjacent tabs, it needs to be hit-tested before any
2536 // left-adjacent Tab, so we look ahead for it as we walk.
2537 for (int i = 0; i < tab_count(); ++i) {
2538 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2539 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2540 return next_tab;
2541 if (IsPointInTab(tab_at(i), point))
2542 return tab_at(i);
2545 return NULL;
2548 std::vector<int> TabStrip::GetTabXCoordinates() {
2549 std::vector<int> results;
2550 for (int i = 0; i < tab_count(); ++i)
2551 results.push_back(ideal_bounds(i).x());
2552 return results;
2555 void TabStrip::SwapLayoutIfNecessary() {
2556 bool needs_touch = NeedsTouchLayout();
2557 bool using_touch = touch_layout_ != NULL;
2558 if (needs_touch == using_touch)
2559 return;
2561 if (needs_touch) {
2562 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2563 tab_size.set_width(Tab::GetTouchWidth());
2564 touch_layout_.reset(new StackedTabStripLayout(
2565 tab_size,
2566 kTabHorizontalOffset,
2567 kStackedPadding,
2568 kMaxStackedCount,
2569 &tabs_));
2570 touch_layout_->SetWidth(tab_area_width());
2571 // This has to be after SetWidth() as SetWidth() is going to reset the
2572 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how
2573 // many mini-tabs there are).
2574 GenerateIdealBoundsForMiniTabs(NULL);
2575 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(),
2576 GetMiniTabCount());
2577 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2578 } else {
2579 touch_layout_.reset();
2581 PrepareForAnimation();
2582 GenerateIdealBounds();
2583 SetTabVisibility();
2584 AnimateToIdealBounds();
2587 bool TabStrip::NeedsTouchLayout() const {
2588 if (!stacked_layout_)
2589 return false;
2591 int mini_tab_count = GetMiniTabCount();
2592 int normal_count = tab_count() - mini_tab_count;
2593 if (normal_count <= 1 || normal_count == mini_tab_count)
2594 return false;
2595 int x = GetStartXForNormalTabs();
2596 int available_width = tab_area_width() - x;
2597 return (Tab::GetTouchWidth() * normal_count +
2598 kTabHorizontalOffset * (normal_count - 1)) > available_width;
2601 void TabStrip::SetResetToShrinkOnExit(bool value) {
2602 if (!adjust_layout_)
2603 return;
2605 if (value && !stacked_layout_)
2606 value = false; // We're already using shrink (not stacked) layout.
2608 if (value == reset_to_shrink_on_exit_)
2609 return;
2611 reset_to_shrink_on_exit_ = value;
2612 // Add an observer so we know when the mouse moves out of the tabstrip.
2613 if (reset_to_shrink_on_exit_)
2614 AddMessageLoopObserver();
2615 else
2616 RemoveMessageLoopObserver();
2619 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
2620 if (sender == newtab_button_) {
2621 content::RecordAction(UserMetricsAction("NewTab_Button"));
2622 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
2623 TabStripModel::NEW_TAB_ENUM_COUNT);
2624 if (event.IsMouseEvent()) {
2625 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
2626 if (mouse.IsOnlyMiddleMouseButton()) {
2627 base::string16 clipboard_text = GetClipboardText();
2628 if (!clipboard_text.empty())
2629 controller()->CreateNewTabWithLocation(clipboard_text);
2630 return;
2634 controller()->CreateNewTab();
2635 if (event.type() == ui::ET_GESTURE_TAP)
2636 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
2640 // Overridden to support automation. See automation_proxy_uitest.cc.
2641 const views::View* TabStrip::GetViewByID(int view_id) const {
2642 if (tab_count() > 0) {
2643 if (view_id == VIEW_ID_TAB_LAST)
2644 return tab_at(tab_count() - 1);
2645 if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
2646 int index = view_id - VIEW_ID_TAB_0;
2647 return (index >= 0 && index < tab_count()) ? tab_at(index) : NULL;
2651 return View::GetViewByID(view_id);
2654 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
2655 UpdateStackedLayoutFromMouseEvent(this, event);
2656 // We can't return true here, else clicking in an empty area won't drag the
2657 // window.
2658 return false;
2661 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
2662 ContinueDrag(this, event);
2663 return true;
2666 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
2667 EndDrag(END_DRAG_COMPLETE);
2668 UpdateStackedLayoutFromMouseEvent(this, event);
2671 void TabStrip::OnMouseCaptureLost() {
2672 EndDrag(END_DRAG_CAPTURE_LOST);
2675 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
2676 UpdateStackedLayoutFromMouseEvent(this, event);
2679 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
2680 SetResetToShrinkOnExit(true);
2683 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
2684 SetResetToShrinkOnExit(false);
2685 switch (event->type()) {
2686 case ui::ET_GESTURE_SCROLL_END:
2687 case ui::ET_SCROLL_FLING_START:
2688 case ui::ET_GESTURE_END:
2689 EndDrag(END_DRAG_COMPLETE);
2690 if (adjust_layout_) {
2691 SetStackedLayout(true);
2692 controller_->StackedLayoutMaybeChanged();
2694 break;
2696 case ui::ET_GESTURE_LONG_PRESS:
2697 if (drag_controller_.get())
2698 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
2699 break;
2701 case ui::ET_GESTURE_LONG_TAP: {
2702 EndDrag(END_DRAG_CANCEL);
2703 gfx::Point local_point = event->location();
2704 Tab* tab = FindTabForEvent(local_point);
2705 if (tab) {
2706 ConvertPointToScreen(this, &local_point);
2707 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
2709 break;
2712 case ui::ET_GESTURE_SCROLL_UPDATE:
2713 ContinueDrag(this, *event);
2714 break;
2716 case ui::ET_GESTURE_TAP_DOWN:
2717 EndDrag(END_DRAG_CANCEL);
2718 break;
2720 case ui::ET_GESTURE_TAP: {
2721 const int active_index = controller_->GetActiveIndex();
2722 DCHECK_NE(-1, active_index);
2723 Tab* active_tab = tab_at(active_index);
2724 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
2725 if (active_tab->tab_activated_with_last_tap_down())
2726 action = TouchUMA::GESTURE_TABSWITCH_TAP;
2727 TouchUMA::RecordGestureAction(action);
2728 break;
2731 default:
2732 break;
2734 event->SetHandled();
2737 views::View* TabStrip::TargetForRect(views::View* root, const gfx::Rect& rect) {
2738 CHECK_EQ(root, this);
2740 if (!views::UsePointBasedTargeting(rect))
2741 return views::ViewTargeterDelegate::TargetForRect(root, rect);
2742 const gfx::Point point(rect.CenterPoint());
2744 if (!touch_layout_) {
2745 // Return any view that isn't a Tab or this TabStrip immediately. We don't
2746 // want to interfere.
2747 views::View* v = views::ViewTargeterDelegate::TargetForRect(root, rect);
2748 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
2749 return v;
2751 views::View* tab = FindTabHitByPoint(point);
2752 if (tab)
2753 return tab;
2754 } else {
2755 if (newtab_button_->visible()) {
2756 views::View* view =
2757 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
2758 if (view)
2759 return view;
2761 Tab* tab = FindTabForEvent(point);
2762 if (tab)
2763 return ConvertPointToViewAndGetEventHandler(this, tab, point);
2765 return this;