BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip.cc
blobea3c1456245c0403bcd71469add180650720da23
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/layout_constants.h"
26 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
27 #include "chrome/browser/ui/views/tabs/tab.h"
28 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
29 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
30 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
31 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/user_metrics.h"
35 #include "grit/theme_resources.h"
36 #include "ui/accessibility/ax_view_state.h"
37 #include "ui/base/default_theme_provider.h"
38 #include "ui/base/dragdrop/drag_drop_types.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/base/models/list_selection_model.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/compositor/compositing_recorder.h"
43 #include "ui/compositor/paint_recorder.h"
44 #include "ui/gfx/animation/animation_container.h"
45 #include "ui/gfx/animation/throb_animation.h"
46 #include "ui/gfx/canvas.h"
47 #include "ui/gfx/display.h"
48 #include "ui/gfx/geometry/rect_conversions.h"
49 #include "ui/gfx/geometry/size.h"
50 #include "ui/gfx/image/image_skia.h"
51 #include "ui/gfx/image/image_skia_operations.h"
52 #include "ui/gfx/path.h"
53 #include "ui/gfx/screen.h"
54 #include "ui/gfx/skia_util.h"
55 #include "ui/views/controls/image_view.h"
56 #include "ui/views/masked_targeter_delegate.h"
57 #include "ui/views/mouse_watcher_view_host.h"
58 #include "ui/views/rect_based_targeting_utils.h"
59 #include "ui/views/view_model_utils.h"
60 #include "ui/views/view_targeter.h"
61 #include "ui/views/widget/root_view.h"
62 #include "ui/views/widget/widget.h"
63 #include "ui/views/window/non_client_view.h"
65 #if defined(OS_WIN)
66 #include "ui/gfx/win/dpi.h"
67 #include "ui/gfx/win/hwnd_util.h"
68 #include "ui/views/widget/monitor_win.h"
69 #include "ui/views/win/hwnd_util.h"
70 #endif
72 using base::UserMetricsAction;
73 using ui::DropTargetEvent;
75 namespace {
77 static const int kTabStripAnimationVSlop = 40;
78 // Inactive tabs in a native frame are slightly transparent.
79 static const uint8_t kGlassFrameInactiveTabAlpha = 200;
80 // If there are multiple tabs selected then make non-selected inactive tabs
81 // even more transparent.
82 static const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
84 // Alpha applied to all elements save the selected tabs.
85 static const uint8_t kInactiveTabAndNewTabButtonAlphaAsh = 230;
86 static const uint8_t kInactiveTabAndNewTabButtonAlpha = 255;
88 // Inverse ratio of the width of a tab edge to the width of the tab. When
89 // hovering over the left or right edge of a tab, the drop indicator will
90 // point between tabs.
91 static const int kTabEdgeRatioInverse = 4;
93 // Size of the drop indicator.
94 static int drop_indicator_width;
95 static int drop_indicator_height;
97 static inline int Round(double x) {
98 // Why oh why is this not in a standard header?
99 return static_cast<int>(floor(x + 0.5));
102 // Max number of stacked tabs.
103 static const int kMaxStackedCount = 4;
105 // Padding between stacked tabs.
106 static const int kStackedPadding = 6;
108 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
109 #if !defined(USE_ASH)
110 const int kMouseMoveTimeMS = 200;
111 const int kMouseMoveCountBeforeConsiderReal = 3;
112 #endif
114 // Amount of time we delay before resizing after a close from a touch.
115 const int kTouchResizeLayoutTimeMS = 2000;
117 // Amount to adjust the clip by when the tab is stacked before the active index.
118 const int kStackedTabLeftClip = 20;
120 // Amount to adjust the clip by when the tab is stacked after the active index.
121 const int kStackedTabRightClip = 20;
123 base::string16 GetClipboardText() {
124 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
125 return base::string16();
126 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
127 CHECK(clipboard);
128 base::string16 clipboard_text;
129 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
130 return clipboard_text;
133 // Animation delegate used for any automatic tab movement. Hides the tab if it
134 // is not fully visible within the tabstrip area, to prevent overflow clipping.
135 class TabAnimationDelegate : public gfx::AnimationDelegate {
136 public:
137 TabAnimationDelegate(TabStrip* tab_strip, Tab* tab);
138 ~TabAnimationDelegate() override;
140 void AnimationProgressed(const gfx::Animation* animation) override;
142 protected:
143 TabStrip* tab_strip() { return tab_strip_; }
144 Tab* tab() { return tab_; }
146 private:
147 TabStrip* const tab_strip_;
148 Tab* const tab_;
150 DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate);
153 TabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab)
154 : tab_strip_(tab_strip),
155 tab_(tab) {
158 TabAnimationDelegate::~TabAnimationDelegate() {
161 void TabAnimationDelegate::AnimationProgressed(
162 const gfx::Animation* animation) {
163 tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_));
166 // Animation delegate used when a dragged tab is released. When done sets the
167 // dragging state to false.
168 class ResetDraggingStateDelegate : public TabAnimationDelegate {
169 public:
170 ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab);
171 ~ResetDraggingStateDelegate() override;
173 void AnimationEnded(const gfx::Animation* animation) override;
174 void AnimationCanceled(const gfx::Animation* animation) override;
176 private:
177 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
180 ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip,
181 Tab* tab)
182 : TabAnimationDelegate(tab_strip, tab) {
185 ResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
188 void ResetDraggingStateDelegate::AnimationEnded(
189 const gfx::Animation* animation) {
190 tab()->set_dragging(false);
191 AnimationProgressed(animation); // Forces tab visibility to update.
194 void ResetDraggingStateDelegate::AnimationCanceled(
195 const gfx::Animation* animation) {
196 AnimationEnded(animation);
199 // If |dest| contains the point |point_in_source| the event handler from |dest|
200 // is returned. Otherwise NULL is returned.
201 views::View* ConvertPointToViewAndGetEventHandler(
202 views::View* source,
203 views::View* dest,
204 const gfx::Point& point_in_source) {
205 gfx::Point dest_point(point_in_source);
206 views::View::ConvertPointToTarget(source, dest, &dest_point);
207 return dest->HitTestPoint(dest_point) ?
208 dest->GetEventHandlerForPoint(dest_point) : NULL;
211 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
212 // should return NULL if it does not contain the point.
213 views::View* ConvertPointToViewAndGetTooltipHandler(
214 views::View* source,
215 views::View* dest,
216 const gfx::Point& point_in_source) {
217 gfx::Point dest_point(point_in_source);
218 views::View::ConvertPointToTarget(source, dest, &dest_point);
219 return dest->GetTooltipHandlerForPoint(dest_point);
222 TabDragController::EventSource EventSourceFromEvent(
223 const ui::LocatedEvent& event) {
224 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
225 TabDragController::EVENT_SOURCE_MOUSE;
228 } // namespace
230 ///////////////////////////////////////////////////////////////////////////////
231 // NewTabButton
233 // A subclass of button that hit-tests to the shape of the new tab button and
234 // does custom drawing.
236 class NewTabButton : public views::ImageButton,
237 public views::MaskedTargeterDelegate {
238 public:
239 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
240 ~NewTabButton() override;
242 // Set the background offset used to match the background image to the frame
243 // image.
244 void set_background_offset(const gfx::Point& offset) {
245 background_offset_ = offset;
248 protected:
249 // views::View:
250 #if defined(OS_WIN)
251 void OnMouseReleased(const ui::MouseEvent& event) override;
252 #endif
253 void OnPaint(gfx::Canvas* canvas) override;
255 // ui::EventHandler:
256 void OnGestureEvent(ui::GestureEvent* event) override;
258 private:
259 // views::MaskedTargeterDelegate:
260 bool GetHitTestMask(gfx::Path* mask) const override;
262 bool ShouldWindowContentsBeTransparent() const;
263 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
264 float scale) const;
265 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
266 float scale) const;
267 gfx::ImageSkia GetImageForScale(float scale) const;
269 // Tab strip that contains this button.
270 TabStrip* tab_strip_;
272 // The offset used to paint the background image.
273 gfx::Point background_offset_;
275 // were we destroyed?
276 bool* destroyed_;
278 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
281 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
282 : views::ImageButton(listener),
283 tab_strip_(tab_strip),
284 destroyed_(NULL) {
285 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
286 set_triggerable_event_flags(triggerable_event_flags() |
287 ui::EF_MIDDLE_MOUSE_BUTTON);
288 #endif
291 NewTabButton::~NewTabButton() {
292 if (destroyed_)
293 *destroyed_ = true;
296 #if defined(OS_WIN)
297 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
298 if (event.IsOnlyRightMouseButton()) {
299 gfx::Point point = event.location();
300 views::View::ConvertPointToScreen(this, &point);
301 point = gfx::win::DIPToScreenPoint(point);
302 bool destroyed = false;
303 destroyed_ = &destroyed;
304 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
305 if (destroyed)
306 return;
308 destroyed_ = NULL;
309 SetState(views::CustomButton::STATE_NORMAL);
310 return;
312 views::ImageButton::OnMouseReleased(event);
314 #endif
316 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
317 gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
318 canvas->DrawImageInt(image, 0, height() - image.height());
321 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
322 // Consume all gesture events here so that the parent (Tab) does not
323 // start consuming gestures.
324 views::ImageButton::OnGestureEvent(event);
325 event->SetHandled();
328 bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
329 DCHECK(mask);
331 // When the button is sized to the top of the tab strip, we want the hit
332 // test mask to be defined as the complete (rectangular) bounds of the
333 // button.
334 if (tab_strip_->SizeTabButtonToTopOfTabStrip()) {
335 gfx::Rect button_bounds(GetContentsBounds());
336 button_bounds.set_x(GetMirroredXForRect(button_bounds));
337 mask->addRect(RectToSkRect(button_bounds));
338 return true;
341 SkScalar w = SkIntToScalar(width());
342 SkScalar v_offset = SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset);
344 // These values are defined by the shape of the new tab image. Should that
345 // image ever change, these values will need to be updated. They're so
346 // custom it's not really worth defining constants for.
347 // These values are correct for regular and USE_ASH versions of the image.
348 mask->moveTo(0, v_offset + 1);
349 mask->lineTo(w - 7, v_offset + 1);
350 mask->lineTo(w - 4, v_offset + 4);
351 mask->lineTo(w, v_offset + 16);
352 mask->lineTo(w - 1, v_offset + 17);
353 mask->lineTo(7, v_offset + 17);
354 mask->lineTo(4, v_offset + 13);
355 mask->lineTo(0, v_offset + 1);
356 mask->close();
358 return true;
361 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
362 return GetWidget() &&
363 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
366 gfx::ImageSkia NewTabButton::GetBackgroundImage(
367 views::CustomButton::ButtonState state,
368 float scale) const {
369 int background_id = 0;
370 if (ShouldWindowContentsBeTransparent()) {
371 background_id = IDR_THEME_TAB_BACKGROUND_V;
372 } else if (tab_strip_->controller()->IsIncognito()) {
373 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
374 } else {
375 background_id = IDR_THEME_TAB_BACKGROUND;
378 int alpha = 0;
379 switch (state) {
380 case views::CustomButton::STATE_NORMAL:
381 case views::CustomButton::STATE_HOVERED:
382 alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
383 : 255;
384 break;
385 case views::CustomButton::STATE_PRESSED:
386 alpha = 145;
387 break;
388 default:
389 NOTREACHED();
390 break;
393 gfx::ImageSkia* mask =
394 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
395 int height = mask->height();
396 int width = mask->width();
397 // The canvas and mask has to use the same scale factor.
398 if (!mask->HasRepresentation(scale))
399 scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
401 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
403 // For custom images the background starts at the top of the tab strip.
404 // Otherwise the background starts at the top of the frame.
405 gfx::ImageSkia* background =
406 GetThemeProvider()->GetImageSkiaNamed(background_id);
407 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
408 0 : background_offset_.y();
410 // The new tab background is mirrored in RTL mode, but the theme background
411 // should never be mirrored. Mirror it here to compensate.
412 float x_scale = 1.0f;
413 int x = GetMirroredX() + background_offset_.x();
414 if (base::i18n::IsRTL()) {
415 x_scale = -1.0f;
416 // Offset by |width| such that the same region is painted as if there was no
417 // flip.
418 x += width;
420 canvas.TileImageInt(*background, x,
421 TabStrip::kNewTabButtonVerticalOffset + offset_y,
422 x_scale, 1.0f, 0, 0, width, height);
424 if (alpha != 255) {
425 SkPaint paint;
426 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
427 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
428 paint.setStyle(SkPaint::kFill_Style);
429 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
432 // White highlight on hover.
433 if (state == views::CustomButton::STATE_HOVERED)
434 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
436 return gfx::ImageSkiaOperations::CreateMaskedImage(
437 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
440 gfx::ImageSkia NewTabButton::GetImageForState(
441 views::CustomButton::ButtonState state,
442 float scale) const {
443 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
444 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
445 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
447 gfx::Canvas canvas(
448 gfx::Size(overlay->width(), overlay->height()),
449 scale,
450 false);
451 canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
453 // Draw the button border with a slight alpha.
454 const uint8_t kGlassFrameOverlayAlpha = 178;
455 const uint8_t kOpaqueFrameOverlayAlpha = 230;
456 uint8_t alpha = ShouldWindowContentsBeTransparent()
457 ? kGlassFrameOverlayAlpha
458 : kOpaqueFrameOverlayAlpha;
459 canvas.DrawImageInt(*overlay, 0, 0, alpha);
461 return gfx::ImageSkia(canvas.ExtractImageRep());
464 gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
465 if (!hover_animation_->is_animating())
466 return GetImageForState(state(), scale);
467 return gfx::ImageSkiaOperations::CreateBlendedImage(
468 GetImageForState(views::CustomButton::STATE_NORMAL, scale),
469 GetImageForState(views::CustomButton::STATE_HOVERED, scale),
470 hover_animation_->GetCurrentValue());
473 ///////////////////////////////////////////////////////////////////////////////
474 // TabStrip::RemoveTabDelegate
476 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
477 // done.
478 class TabStrip::RemoveTabDelegate : public TabAnimationDelegate {
479 public:
480 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
482 void AnimationEnded(const gfx::Animation* animation) override;
483 void AnimationCanceled(const gfx::Animation* animation) override;
485 private:
486 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
489 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
490 Tab* tab)
491 : TabAnimationDelegate(tab_strip, tab) {
494 void TabStrip::RemoveTabDelegate::AnimationEnded(
495 const gfx::Animation* animation) {
496 DCHECK(tab()->closing());
497 tab_strip()->RemoveAndDeleteTab(tab());
499 // Send the Container a message to simulate a mouse moved event at the current
500 // mouse position. This tickles the Tab the mouse is currently over to show
501 // the "hot" state of the close button. Note that this is not required (and
502 // indeed may crash!) for removes spawned by non-mouse closes and
503 // drag-detaches.
504 if (!tab_strip()->IsDragSessionActive() &&
505 tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
506 // The widget can apparently be null during shutdown.
507 views::Widget* widget = tab_strip()->GetWidget();
508 if (widget)
509 widget->SynthesizeMouseMoveEvent();
513 void TabStrip::RemoveTabDelegate::AnimationCanceled(
514 const gfx::Animation* animation) {
515 AnimationEnded(animation);
518 ///////////////////////////////////////////////////////////////////////////////
519 // TabStrip, public:
521 // static
522 const char TabStrip::kViewClassName[] = "TabStrip";
523 const int TabStrip::kNewTabButtonVerticalOffset = 7;
524 const int TabStrip::kNewTabButtonAssetWidth = 34;
525 const int TabStrip::kNewTabButtonAssetHeight = 18;
526 #if defined(OS_MACOSX)
527 const int TabStrip::kNewTabButtonHorizontalOffset = -8;
528 const int TabStrip::kPinnedToNonPinnedGap = 2;
529 #else
530 const int TabStrip::kNewTabButtonHorizontalOffset = -11;
531 const int TabStrip::kPinnedToNonPinnedGap = 3;
532 #endif
534 TabStrip::TabStrip(TabStripController* controller)
535 : controller_(controller),
536 newtab_button_(NULL),
537 current_unselected_width_(Tab::GetStandardSize().width()),
538 current_selected_width_(Tab::GetStandardSize().width()),
539 available_width_for_tabs_(-1),
540 in_tab_close_(false),
541 animation_container_(new gfx::AnimationContainer()),
542 bounds_animator_(this),
543 stacked_layout_(false),
544 adjust_layout_(false),
545 reset_to_shrink_on_exit_(false),
546 mouse_move_count_(0),
547 immersive_style_(false) {
548 Init();
549 SetEventTargeter(
550 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
553 TabStrip::~TabStrip() {
554 FOR_EACH_OBSERVER(TabStripObserver, observers_,
555 TabStripDeleted(this));
557 // The animations may reference the tabs. Shut down the animation before we
558 // delete the tabs.
559 StopAnimating(false);
561 DestroyDragController();
563 // Make sure we unhook ourselves as a message loop observer so that we don't
564 // crash in the case where the user closes the window after closing a tab
565 // but before moving the mouse.
566 RemoveMessageLoopObserver();
568 // The children (tabs) may callback to us from their destructor. Delete them
569 // so that if they call back we aren't in a weird state.
570 RemoveAllChildViews(true);
573 void TabStrip::AddObserver(TabStripObserver* observer) {
574 observers_.AddObserver(observer);
577 void TabStrip::RemoveObserver(TabStripObserver* observer) {
578 observers_.RemoveObserver(observer);
581 void TabStrip::SetStackedLayout(bool stacked_layout) {
582 if (stacked_layout == stacked_layout_)
583 return;
585 const int active_index = controller_->GetActiveIndex();
586 int active_center = 0;
587 if (active_index != -1) {
588 active_center = ideal_bounds(active_index).x() +
589 ideal_bounds(active_index).width() / 2;
591 stacked_layout_ = stacked_layout;
592 SetResetToShrinkOnExit(false);
593 SwapLayoutIfNecessary();
594 // When transitioning to stacked try to keep the active tab centered.
595 if (touch_layout_ && active_index != -1) {
596 touch_layout_->SetActiveTabLocation(
597 active_center - ideal_bounds(active_index).width() / 2);
598 AnimateToIdealBounds();
602 gfx::Rect TabStrip::GetNewTabButtonBounds() {
603 return newtab_button_->bounds();
606 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
607 // Extend the button to the screen edge in maximized and immersive fullscreen.
608 views::Widget* widget = GetWidget();
609 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
610 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
613 void TabStrip::StartHighlight(int model_index) {
614 tab_at(model_index)->StartPulse();
617 void TabStrip::StopAllHighlighting() {
618 for (int i = 0; i < tab_count(); ++i)
619 tab_at(i)->StopPulse();
622 void TabStrip::AddTabAt(int model_index,
623 const TabRendererData& data,
624 bool is_active) {
625 Tab* tab = CreateTab();
626 AddChildView(tab);
627 tab->SetData(data);
628 UpdateTabsClosingMap(model_index, 1);
629 tabs_.Add(tab, model_index);
631 if (touch_layout_) {
632 GenerateIdealBoundsForPinnedTabs(NULL);
633 int add_types = 0;
634 if (data.pinned)
635 add_types |= StackedTabStripLayout::kAddTypePinned;
636 if (is_active)
637 add_types |= StackedTabStripLayout::kAddTypeActive;
638 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
641 // Don't animate the first tab, it looks weird, and don't animate anything
642 // if the containing window isn't visible yet.
643 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
644 StartInsertTabAnimation(model_index);
645 else
646 DoLayout();
648 SwapLayoutIfNecessary();
650 FOR_EACH_OBSERVER(TabStripObserver, observers_,
651 TabStripAddedTabAt(this, model_index));
653 // Stop dragging when a new tab is added and dragging a window. Doing
654 // otherwise results in a confusing state if the user attempts to reattach. We
655 // could allow this and make TabDragController update itself during the add,
656 // but this comes up infrequently enough that it's not worth the complexity.
658 // At the start of AddTabAt() the model and tabs are out sync. Any queries to
659 // find a tab given a model index can go off the end of |tabs_|. As such, it
660 // is important that we complete the drag *after* adding the tab so that the
661 // model and tabstrip are in sync.
662 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
663 drag_controller_->is_dragging_window()) {
664 EndDrag(END_DRAG_COMPLETE);
668 void TabStrip::MoveTab(int from_model_index,
669 int to_model_index,
670 const TabRendererData& data) {
671 DCHECK_GT(tabs_.view_size(), 0);
672 const Tab* last_tab = GetLastVisibleTab();
673 tab_at(from_model_index)->SetData(data);
674 if (touch_layout_) {
675 tabs_.MoveViewOnly(from_model_index, to_model_index);
676 int pinned_count = 0;
677 GenerateIdealBoundsForPinnedTabs(&pinned_count);
678 touch_layout_->MoveTab(
679 from_model_index, to_model_index, controller_->GetActiveIndex(),
680 GetStartXForNormalTabs(), pinned_count);
681 } else {
682 tabs_.Move(from_model_index, to_model_index);
684 StartMoveTabAnimation();
685 if (TabDragController::IsAttachedTo(this) &&
686 (last_tab != GetLastVisibleTab() || last_tab->dragging())) {
687 newtab_button_->SetVisible(false);
689 SwapLayoutIfNecessary();
691 FOR_EACH_OBSERVER(TabStripObserver, observers_,
692 TabStripMovedTab(this, from_model_index, to_model_index));
695 void TabStrip::RemoveTabAt(int model_index) {
696 if (touch_layout_) {
697 Tab* tab = tab_at(model_index);
698 tab->set_closing(true);
699 int old_x = tabs_.ideal_bounds(model_index).x();
700 // We still need to paint the tab until we actually remove it. Put it in
701 // tabs_closing_map_ so we can find it.
702 RemoveTabFromViewModel(model_index);
703 touch_layout_->RemoveTab(model_index,
704 GenerateIdealBoundsForPinnedTabs(NULL), old_x);
705 ScheduleRemoveTabAnimation(tab);
706 } else if (in_tab_close_ && model_index != GetModelCount()) {
707 StartMouseInitiatedRemoveTabAnimation(model_index);
708 } else {
709 StartRemoveTabAnimation(model_index);
711 SwapLayoutIfNecessary();
713 FOR_EACH_OBSERVER(TabStripObserver, observers_,
714 TabStripRemovedTabAt(this, model_index));
717 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
718 Tab* tab = tab_at(model_index);
719 bool pinned_state_changed = tab->data().pinned != data.pinned;
720 tab->SetData(data);
722 if (pinned_state_changed) {
723 if (touch_layout_) {
724 int pinned_tab_count = 0;
725 int start_x = GenerateIdealBoundsForPinnedTabs(&pinned_tab_count);
726 touch_layout_->SetXAndPinnedCount(start_x, pinned_tab_count);
728 if (GetWidget() && GetWidget()->IsVisible())
729 StartPinnedTabAnimation();
730 else
731 DoLayout();
733 SwapLayoutIfNecessary();
736 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
737 // Detached tabs should always be invisible (as they close).
738 if (tab->detached())
739 return false;
741 // When stacking tabs, all tabs should always be visible.
742 if (stacked_layout_)
743 return true;
745 // If the tab is currently clipped, it shouldn't be visible. Note that we
746 // allow dragged tabs to draw over the "New Tab button" region as well,
747 // because either the New Tab button will be hidden, or the dragged tabs will
748 // be animating back to their normal positions and we don't want to hide them
749 // in the New Tab button region in case they re-appear after leaving it.
750 // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab
751 // button area, even when the button is invisible, so that they don't appear
752 // to "pop in" when the button disappears.
753 // TODO: Probably doesn't work for RTL
754 int right_edge = tab->bounds().right();
755 const int visible_width = tab->dragging() ? width() : tab_area_width();
756 if (right_edge > visible_width)
757 return false;
759 // Non-clipped dragging tabs should always be visible.
760 if (tab->dragging())
761 return true;
763 // Let all non-clipped closing tabs be visible. These will probably finish
764 // closing before the user changes the active tab, so there's little reason to
765 // try and make the more complex logic below apply.
766 if (tab->closing())
767 return true;
769 // Now we need to check whether the tab isn't currently clipped, but could
770 // become clipped if we changed the active tab, widening either this tab or
771 // the tabstrip portion before it.
773 // Pinned tabs don't change size when activated, so any tab in the pinned tab
774 // region is safe.
775 if (tab->data().pinned)
776 return true;
778 // If the active tab is on or before this tab, we're safe.
779 if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab))
780 return true;
782 // We need to check what would happen if the active tab were to move to this
783 // tab or before.
784 return (right_edge + current_selected_width_ - current_unselected_width_) <=
785 tab_area_width();
788 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
789 if (!in_tab_close_ && IsAnimating()) {
790 // Cancel any current animations. We do this as remove uses the current
791 // ideal bounds and we need to know ideal bounds is in a good state.
792 StopAnimating(true);
795 if (!GetWidget())
796 return;
798 int model_count = GetModelCount();
799 if (model_count > 1 && model_index != model_count - 1) {
800 // The user is about to close a tab other than the last tab. Set
801 // available_width_for_tabs_ so that if we do a layout we don't position a
802 // tab past the end of the second to last tab. We do this so that as the
803 // user closes tabs with the mouse a tab continues to fall under the mouse.
804 Tab* last_tab = tab_at(model_count - 1);
805 Tab* tab_being_removed = tab_at(model_index);
806 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
807 tab_being_removed->width() + GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
808 if (model_index == 0 && tab_being_removed->data().pinned &&
809 !tab_at(1)->data().pinned) {
810 available_width_for_tabs_ -= kPinnedToNonPinnedGap;
814 in_tab_close_ = true;
815 resize_layout_timer_.Stop();
816 if (source == CLOSE_TAB_FROM_TOUCH) {
817 StartResizeLayoutTabsFromTouchTimer();
818 } else {
819 AddMessageLoopObserver();
823 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
824 const ui::ListSelectionModel& new_selection) {
825 if (old_selection.active() != new_selection.active()) {
826 if (old_selection.active() >= 0)
827 tab_at(old_selection.active())->ActiveStateChanged();
828 if (new_selection.active() >= 0)
829 tab_at(new_selection.active())->ActiveStateChanged();
832 if (touch_layout_) {
833 touch_layout_->SetActiveIndex(new_selection.active());
834 // Only start an animation if we need to. Otherwise clicking on an
835 // unselected tab and dragging won't work because dragging is only allowed
836 // if not animating.
837 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
838 AnimateToIdealBounds();
839 SchedulePaint();
840 } else {
841 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
842 // a different size to the selected ones.
843 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
844 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
845 DoLayout();
846 } else {
847 SchedulePaint();
851 // Use STLSetDifference to get the indices of elements newly selected
852 // and no longer selected, since selected_indices() is always sorted.
853 ui::ListSelectionModel::SelectedIndices no_longer_selected =
854 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
855 old_selection.selected_indices(),
856 new_selection.selected_indices());
857 ui::ListSelectionModel::SelectedIndices newly_selected =
858 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
859 new_selection.selected_indices(),
860 old_selection.selected_indices());
862 // Fire accessibility events that reflect the changes to selection, and
863 // stop the pinned tab title animation on tabs no longer selected.
864 for (size_t i = 0; i < no_longer_selected.size(); ++i) {
865 tab_at(no_longer_selected[i])->StopPinnedTabTitleAnimation();
866 tab_at(no_longer_selected[i])->NotifyAccessibilityEvent(
867 ui::AX_EVENT_SELECTION_REMOVE, true);
869 for (size_t i = 0; i < newly_selected.size(); ++i) {
870 tab_at(newly_selected[i])->NotifyAccessibilityEvent(
871 ui::AX_EVENT_SELECTION_ADD, true);
873 tab_at(new_selection.active())->NotifyAccessibilityEvent(
874 ui::AX_EVENT_SELECTION, true);
877 void TabStrip::TabTitleChangedNotLoading(int model_index) {
878 Tab* tab = tab_at(model_index);
879 if (tab->data().pinned && !tab->IsActive())
880 tab->StartPinnedTabTitleAnimation();
883 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
884 return tabs_.GetIndexOfView(tab);
887 int TabStrip::GetModelCount() const {
888 return controller_->GetCount();
891 bool TabStrip::IsValidModelIndex(int model_index) const {
892 return controller_->IsValidIndex(model_index);
895 bool TabStrip::IsDragSessionActive() const {
896 return drag_controller_.get() != NULL;
899 bool TabStrip::IsActiveDropTarget() const {
900 for (int i = 0; i < tab_count(); ++i) {
901 Tab* tab = tab_at(i);
902 if (tab->dragging())
903 return true;
905 return false;
908 bool TabStrip::IsTabStripEditable() const {
909 return !IsDragSessionActive() && !IsActiveDropTarget();
912 bool TabStrip::IsTabStripCloseable() const {
913 return !IsDragSessionActive();
916 void TabStrip::UpdateLoadingAnimations() {
917 controller_->UpdateLoadingAnimations();
920 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
921 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
924 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
925 views::View* v = GetEventHandlerForRect(rect);
927 // If there is no control at this location, claim the hit was in the title
928 // bar to get a move action.
929 if (v == this)
930 return true;
932 // Check to see if the rect intersects the non-button parts of the new tab
933 // button. The button has a non-rectangular shape, so if it's not in the
934 // visual portions of the button we treat it as a click to the caption.
935 gfx::RectF rect_in_newtab_coords_f(rect);
936 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
937 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
938 rect_in_newtab_coords_f);
939 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
940 !newtab_button_->HitTestRect(rect_in_newtab_coords))
941 return true;
943 // All other regions, including the new Tab button, should be considered part
944 // of the containing Window's client area so that regular events can be
945 // processed for them.
946 return false;
949 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
950 for (int i = 0; i < tab_count(); ++i)
951 tab_at(i)->set_background_offset(offset);
952 newtab_button_->set_background_offset(offset);
955 void TabStrip::SetImmersiveStyle(bool enable) {
956 if (immersive_style_ == enable)
957 return;
958 immersive_style_ = enable;
961 bool TabStrip::IsAnimating() const {
962 return bounds_animator_.IsAnimating();
965 void TabStrip::StopAnimating(bool layout) {
966 if (!IsAnimating())
967 return;
969 bounds_animator_.Cancel();
971 if (layout)
972 DoLayout();
975 void TabStrip::FileSupported(const GURL& url, bool supported) {
976 if (drop_info_.get() && drop_info_->url == url)
977 drop_info_->file_supported = supported;
980 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
981 return controller_->GetSelectionModel();
984 bool TabStrip::SupportsMultipleSelection() {
985 // TODO: currently only allow single selection in touch layout mode.
986 return touch_layout_ == NULL;
989 bool TabStrip::ShouldHideCloseButtonForInactiveTabs() {
990 if (!touch_layout_)
991 return false;
993 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
994 switches::kDisableHideInactiveStackedTabCloseButtons);
997 void TabStrip::SelectTab(Tab* tab) {
998 int model_index = GetModelIndexOfTab(tab);
999 if (IsValidModelIndex(model_index))
1000 controller_->SelectTab(model_index);
1003 void TabStrip::ExtendSelectionTo(Tab* tab) {
1004 int model_index = GetModelIndexOfTab(tab);
1005 if (IsValidModelIndex(model_index))
1006 controller_->ExtendSelectionTo(model_index);
1009 void TabStrip::ToggleSelected(Tab* tab) {
1010 int model_index = GetModelIndexOfTab(tab);
1011 if (IsValidModelIndex(model_index))
1012 controller_->ToggleSelected(model_index);
1015 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
1016 int model_index = GetModelIndexOfTab(tab);
1017 if (IsValidModelIndex(model_index))
1018 controller_->AddSelectionFromAnchorTo(model_index);
1021 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
1022 if (tab->closing()) {
1023 // If the tab is already closing, close the next tab. We do this so that the
1024 // user can rapidly close tabs by clicking the close button and not have
1025 // the animations interfere with that.
1026 const int closed_tab_index = FindClosingTab(tab).first->first;
1027 if (closed_tab_index < GetModelCount())
1028 controller_->CloseTab(closed_tab_index, source);
1029 return;
1031 int model_index = GetModelIndexOfTab(tab);
1032 if (IsValidModelIndex(model_index))
1033 controller_->CloseTab(model_index, source);
1036 void TabStrip::ToggleTabAudioMute(Tab* tab) {
1037 int model_index = GetModelIndexOfTab(tab);
1038 if (IsValidModelIndex(model_index))
1039 controller_->ToggleTabAudioMute(model_index);
1042 void TabStrip::ShowContextMenuForTab(Tab* tab,
1043 const gfx::Point& p,
1044 ui::MenuSourceType source_type) {
1045 controller_->ShowContextMenuForTab(tab, p, source_type);
1048 bool TabStrip::IsActiveTab(const Tab* tab) const {
1049 int model_index = GetModelIndexOfTab(tab);
1050 return IsValidModelIndex(model_index) &&
1051 controller_->IsActiveTab(model_index);
1054 bool TabStrip::IsTabSelected(const Tab* tab) const {
1055 int model_index = GetModelIndexOfTab(tab);
1056 return IsValidModelIndex(model_index) &&
1057 controller_->IsTabSelected(model_index);
1060 bool TabStrip::IsTabPinned(const Tab* tab) const {
1061 if (tab->closing())
1062 return false;
1064 int model_index = GetModelIndexOfTab(tab);
1065 return IsValidModelIndex(model_index) &&
1066 controller_->IsTabPinned(model_index);
1069 void TabStrip::MaybeStartDrag(
1070 Tab* tab,
1071 const ui::LocatedEvent& event,
1072 const ui::ListSelectionModel& original_selection) {
1073 // Don't accidentally start any drag operations during animations if the
1074 // mouse is down... during an animation tabs are being resized automatically,
1075 // so the View system can misinterpret this easily if the mouse is down that
1076 // the user is dragging.
1077 if (IsAnimating() || tab->closing() ||
1078 controller_->HasAvailableDragActions() == 0) {
1079 return;
1082 // Do not do any dragging of tabs when using the super short immersive style.
1083 if (IsImmersiveStyle())
1084 return;
1086 int model_index = GetModelIndexOfTab(tab);
1087 if (!IsValidModelIndex(model_index)) {
1088 CHECK(false);
1089 return;
1091 Tabs tabs;
1092 int size_to_selected = 0;
1093 int x = tab->GetMirroredXInView(event.x());
1094 int y = event.y();
1095 // Build the set of selected tabs to drag and calculate the offset from the
1096 // first selected tab.
1097 for (int i = 0; i < tab_count(); ++i) {
1098 Tab* other_tab = tab_at(i);
1099 if (IsTabSelected(other_tab)) {
1100 tabs.push_back(other_tab);
1101 if (other_tab == tab) {
1102 size_to_selected = GetSizeNeededForTabs(tabs);
1103 x = size_to_selected - tab->width() + x;
1107 DCHECK(!tabs.empty());
1108 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1109 ui::ListSelectionModel selection_model;
1110 if (!original_selection.IsSelected(model_index))
1111 selection_model.Copy(original_selection);
1112 // Delete the existing DragController before creating a new one. We do this as
1113 // creating the DragController remembers the WebContents delegates and we need
1114 // to make sure the existing DragController isn't still a delegate.
1115 drag_controller_.reset();
1116 TabDragController::MoveBehavior move_behavior =
1117 TabDragController::REORDER;
1118 // Use MOVE_VISIBILE_TABS in the following conditions:
1119 // . Mouse event generated from touch and the left button is down (the right
1120 // button corresponds to a long press, which we want to reorder).
1121 // . Gesture tap down and control key isn't down.
1122 // . Real mouse event and control is down. This is mostly for testing.
1123 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1124 event.type() == ui::ET_GESTURE_TAP_DOWN);
1125 if (touch_layout_ &&
1126 ((event.type() == ui::ET_MOUSE_PRESSED &&
1127 (((event.flags() & ui::EF_FROM_TOUCH) &&
1128 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1129 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1130 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1131 (event.type() == ui::ET_GESTURE_TAP_DOWN && !event.IsControlDown()))) {
1132 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1135 drag_controller_.reset(new TabDragController);
1136 drag_controller_->Init(
1137 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1138 move_behavior, EventSourceFromEvent(event));
1141 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1142 if (drag_controller_.get() &&
1143 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1144 gfx::Point screen_location(event.location());
1145 views::View::ConvertPointToScreen(view, &screen_location);
1146 drag_controller_->Drag(screen_location);
1150 bool TabStrip::EndDrag(EndDragReason reason) {
1151 if (!drag_controller_.get())
1152 return false;
1153 bool started_drag = drag_controller_->started_drag();
1154 drag_controller_->EndDrag(reason);
1155 return started_drag;
1158 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1159 gfx::Point local_point = tab_in_tab_coordinates;
1160 ConvertPointToTarget(tab, this, &local_point);
1162 views::View* view = GetEventHandlerForPoint(local_point);
1163 if (!view)
1164 return NULL; // No tab contains the point.
1166 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1167 while (view && view != this && view->id() != VIEW_ID_TAB)
1168 view = view->parent();
1170 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1173 void TabStrip::OnMouseEventInTab(views::View* source,
1174 const ui::MouseEvent& event) {
1175 UpdateStackedLayoutFromMouseEvent(source, event);
1178 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1179 // Only touch layout needs to restrict the clip.
1180 if (!touch_layout_ && !IsStackingDraggedTabs())
1181 return true;
1183 int index = GetModelIndexOfTab(tab);
1184 if (index == -1)
1185 return true; // Tab is closing, paint it all.
1187 int active_index = IsStackingDraggedTabs() ?
1188 controller_->GetActiveIndex() : touch_layout_->active_index();
1189 if (active_index == tab_count())
1190 active_index--;
1192 if (index < active_index) {
1193 if (tab_at(index)->x() == tab_at(index + 1)->x())
1194 return false;
1196 if (tab_at(index)->x() > tab_at(index + 1)->x())
1197 return true; // Can happen during dragging.
1199 clip->SetRect(
1200 0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + kStackedTabLeftClip,
1201 tab_at(index)->height());
1202 } else if (index > active_index && index > 0) {
1203 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1204 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1205 if (tab_bounds.x() == previous_tab_bounds.x())
1206 return false;
1208 if (tab_bounds.x() < previous_tab_bounds.x())
1209 return true; // Can happen during dragging.
1211 if (previous_tab_bounds.right() - GetLayoutConstant(TABSTRIP_TAB_OVERLAP) !=
1212 tab_bounds.x()) {
1213 int x = previous_tab_bounds.right() - tab_bounds.x() -
1214 kStackedTabRightClip;
1215 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1218 return true;
1221 bool TabStrip::IsImmersiveStyle() const {
1222 return immersive_style_;
1225 void TabStrip::UpdateTabAccessibilityState(const Tab* tab,
1226 ui::AXViewState* state) {
1227 state->count = tab_count();
1228 state->index = GetModelIndexOfTab(tab);
1231 void TabStrip::MouseMovedOutOfHost() {
1232 ResizeLayoutTabs();
1233 if (reset_to_shrink_on_exit_) {
1234 reset_to_shrink_on_exit_ = false;
1235 SetStackedLayout(false);
1236 controller_->StackedLayoutMaybeChanged();
1240 ///////////////////////////////////////////////////////////////////////////////
1241 // TabStrip, views::View overrides:
1243 void TabStrip::Layout() {
1244 // Only do a layout if our size changed.
1245 if (last_layout_size_ == size())
1246 return;
1247 if (IsDragSessionActive())
1248 return;
1249 DoLayout();
1252 void TabStrip::PaintChildren(const ui::PaintContext& context) {
1253 // The view order doesn't match the paint order (tabs_ contains the tab
1254 // ordering). Additionally we need to paint the tabs that are closing in
1255 // |tabs_closing_map_|.
1256 Tab* active_tab = NULL;
1257 Tabs tabs_dragging;
1258 Tabs selected_tabs;
1259 int selected_tab_count = 0;
1260 bool is_dragging = false;
1261 int active_tab_index = -1;
1263 const chrome::HostDesktopType host_desktop_type =
1264 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1265 const uint8_t inactive_tab_alpha =
1266 (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH)
1267 ? kInactiveTabAndNewTabButtonAlphaAsh
1268 : kInactiveTabAndNewTabButtonAlpha;
1271 ui::CompositingRecorder opacity_recorder(context, inactive_tab_alpha);
1273 PaintClosingTabs(tab_count(), context);
1275 for (int i = tab_count() - 1; i >= 0; --i) {
1276 Tab* tab = tab_at(i);
1277 if (tab->IsSelected())
1278 selected_tab_count++;
1279 if (tab->dragging() && !stacked_layout_) {
1280 is_dragging = true;
1281 if (tab->IsActive()) {
1282 active_tab = tab;
1283 active_tab_index = i;
1284 } else {
1285 tabs_dragging.push_back(tab);
1287 } else if (!tab->IsActive()) {
1288 if (!tab->IsSelected()) {
1289 if (!stacked_layout_)
1290 tab->Paint(context);
1291 } else {
1292 selected_tabs.push_back(tab);
1294 } else {
1295 active_tab = tab;
1296 active_tab_index = i;
1298 PaintClosingTabs(i, context);
1301 // Draw from the left and then the right if we're in touch mode.
1302 if (stacked_layout_ && active_tab_index >= 0) {
1303 for (int i = 0; i < active_tab_index; ++i) {
1304 Tab* tab = tab_at(i);
1305 tab->Paint(context);
1308 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1309 Tab* tab = tab_at(i);
1310 tab->Paint(context);
1315 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1316 ui::PaintRecorder recorder(context, size());
1317 // Make sure non-active tabs are somewhat transparent.
1318 SkPaint paint;
1319 // If there are multiple tabs selected, fade non-selected tabs more to make
1320 // the selected tabs more noticable.
1321 uint8_t alpha = selected_tab_count > 1
1322 ? kGlassFrameInactiveTabAlphaMultiSelection
1323 : kGlassFrameInactiveTabAlpha;
1324 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1325 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1326 paint.setStyle(SkPaint::kFill_Style);
1328 gfx::Rect bounds(GetLocalBounds());
1329 // The tab graphics include some shadows at the top, plus a 1 pixel top
1330 // stroke. Exclude this region when trying to make tabs transparent as it's
1331 // transparent enough already, and drawing in this region can overlap the
1332 // avatar button, leading to visual artifacts. Also exclude the toolbar
1333 // overlap region at the bottom.
1334 bounds.Inset(0, GetLayoutConstant(TABSTRIP_TOP_SHADOW_HEIGHT) + 1, 0,
1335 GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP));
1336 recorder.canvas()->DrawRect(bounds, paint);
1339 // Now selected but not active. We don't want these dimmed if using native
1340 // frame, so they're painted after initial pass.
1341 for (size_t i = 0; i < selected_tabs.size(); ++i)
1342 selected_tabs[i]->Paint(context);
1344 // Next comes the active tab.
1345 if (active_tab && !is_dragging)
1346 active_tab->Paint(context);
1348 // Paint the New Tab button.
1350 ui::CompositingRecorder opacity_recorder(context, inactive_tab_alpha);
1351 newtab_button_->Paint(context);
1354 // And the dragged tabs.
1355 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1356 tabs_dragging[i]->Paint(context);
1358 // If the active tab is being dragged, it goes last.
1359 if (active_tab && is_dragging)
1360 active_tab->Paint(context);
1363 const char* TabStrip::GetClassName() const {
1364 return kViewClassName;
1367 gfx::Size TabStrip::GetPreferredSize() const {
1368 int needed_tab_width;
1369 if (touch_layout_ || adjust_layout_) {
1370 // For stacked tabs the minimum size is calculated as the size needed to
1371 // handle showing any number of tabs.
1372 needed_tab_width =
1373 Tab::GetTouchWidth() + (2 * kStackedPadding * kMaxStackedCount);
1374 } else {
1375 // Otherwise the minimum width is based on the actual number of tabs.
1376 const int pinned_tab_count = GetPinnedTabCount();
1377 needed_tab_width = pinned_tab_count * Tab::GetPinnedWidth();
1378 const int remaining_tab_count = tab_count() - pinned_tab_count;
1379 const int min_selected_width = Tab::GetMinimumSelectedSize().width();
1380 const int min_unselected_width = Tab::GetMinimumUnselectedSize().width();
1381 if (remaining_tab_count > 0) {
1382 needed_tab_width += kPinnedToNonPinnedGap + min_selected_width +
1383 ((remaining_tab_count - 1) * min_unselected_width);
1385 const int tab_overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
1386 if (tab_count() > 1)
1387 needed_tab_width -= (tab_count() - 1) * tab_overlap;
1389 // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1390 // and don't force it to be larger than is necessary to show 20 tabs.
1391 const int largest_min_tab_width =
1392 min_selected_width + 19 * (min_unselected_width - tab_overlap);
1393 needed_tab_width = std::min(
1394 std::max(needed_tab_width, min_selected_width), largest_min_tab_width);
1396 return gfx::Size(
1397 needed_tab_width + new_tab_button_width(),
1398 immersive_style_ ?
1399 Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1402 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1403 // Force animations to stop, otherwise it makes the index calculation tricky.
1404 StopAnimating(true);
1406 UpdateDropIndex(event);
1408 GURL url;
1409 base::string16 title;
1411 // Check whether the event data includes supported drop data.
1412 if (event.data().GetURLAndTitle(
1413 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1414 url.is_valid()) {
1415 drop_info_->url = url;
1417 // For file:// URLs, kick off a MIME type request in case they're dropped.
1418 if (url.SchemeIsFile())
1419 controller()->CheckFileSupported(url);
1423 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1424 // Update the drop index even if the file is unsupported, to allow
1425 // dragging a file to the contents of another tab.
1426 UpdateDropIndex(event);
1428 if (!drop_info_->file_supported)
1429 return ui::DragDropTypes::DRAG_NONE;
1431 return GetDropEffect(event);
1434 void TabStrip::OnDragExited() {
1435 SetDropIndex(-1, false);
1438 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1439 if (!drop_info_.get())
1440 return ui::DragDropTypes::DRAG_NONE;
1442 const int drop_index = drop_info_->drop_index;
1443 const bool drop_before = drop_info_->drop_before;
1444 const bool file_supported = drop_info_->file_supported;
1446 // Hide the drop indicator.
1447 SetDropIndex(-1, false);
1449 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1450 // have been changed after |drop_info_| was created.
1451 GURL url;
1452 base::string16 title;
1453 if (!file_supported ||
1454 !event.data().GetURLAndTitle(
1455 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1456 !url.is_valid())
1457 return ui::DragDropTypes::DRAG_NONE;
1459 controller()->PerformDrop(drop_before, drop_index, url);
1461 return GetDropEffect(event);
1464 void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1465 state->role = ui::AX_ROLE_TAB_LIST;
1468 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1469 if (!HitTestPoint(point))
1470 return NULL;
1472 if (!touch_layout_) {
1473 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1474 // want to interfere.
1475 views::View* v = View::GetTooltipHandlerForPoint(point);
1476 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1477 return v;
1479 views::View* tab = FindTabHitByPoint(point);
1480 if (tab)
1481 return tab;
1482 } else {
1483 if (newtab_button_->visible()) {
1484 views::View* view =
1485 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1486 if (view)
1487 return view;
1489 Tab* tab = FindTabForEvent(point);
1490 if (tab)
1491 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1493 return this;
1496 // static
1497 int TabStrip::GetImmersiveHeight() {
1498 return Tab::GetImmersiveHeight();
1501 ///////////////////////////////////////////////////////////////////////////////
1502 // TabStrip, private:
1504 void TabStrip::Init() {
1505 set_id(VIEW_ID_TAB_STRIP);
1506 // So we get enter/exit on children to switch stacked layout on and off.
1507 set_notify_enter_exit_on_child(true);
1508 newtab_button_bounds_.SetRect(0,
1510 kNewTabButtonAssetWidth,
1511 kNewTabButtonAssetHeight +
1512 kNewTabButtonVerticalOffset);
1513 newtab_button_ = new NewTabButton(this, this);
1514 newtab_button_->SetTooltipText(
1515 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1516 newtab_button_->SetAccessibleName(
1517 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1518 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1519 views::ImageButton::ALIGN_BOTTOM);
1520 newtab_button_->SetEventTargeter(
1521 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(newtab_button_)));
1522 AddChildView(newtab_button_);
1524 if (drop_indicator_width == 0) {
1525 // Direction doesn't matter, both images are the same size.
1526 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1527 drop_indicator_width = drop_image->width();
1528 drop_indicator_height = drop_image->height();
1532 Tab* TabStrip::CreateTab() {
1533 Tab* tab = new Tab(this);
1534 tab->set_animation_container(animation_container_.get());
1535 return tab;
1538 void TabStrip::StartInsertTabAnimation(int model_index) {
1539 PrepareForAnimation();
1541 // The TabStrip can now use its entire width to lay out Tabs.
1542 in_tab_close_ = false;
1543 available_width_for_tabs_ = -1;
1545 GenerateIdealBounds();
1547 Tab* tab = tab_at(model_index);
1548 if (model_index == 0) {
1549 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1550 ideal_bounds(model_index).height());
1551 } else {
1552 Tab* last_tab = tab_at(model_index - 1);
1553 tab->SetBounds(
1554 last_tab->bounds().right() - GetLayoutConstant(TABSTRIP_TAB_OVERLAP),
1555 ideal_bounds(model_index).y(), 0, ideal_bounds(model_index).height());
1558 AnimateToIdealBounds();
1561 void TabStrip::StartMoveTabAnimation() {
1562 PrepareForAnimation();
1563 GenerateIdealBounds();
1564 AnimateToIdealBounds();
1567 void TabStrip::StartRemoveTabAnimation(int model_index) {
1568 PrepareForAnimation();
1570 // Mark the tab as closing.
1571 Tab* tab = tab_at(model_index);
1572 tab->set_closing(true);
1574 RemoveTabFromViewModel(model_index);
1576 ScheduleRemoveTabAnimation(tab);
1579 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1580 // Start an animation for the tabs.
1581 GenerateIdealBounds();
1582 AnimateToIdealBounds();
1584 // Animate the tab being closed to zero width.
1585 gfx::Rect tab_bounds = tab->bounds();
1586 tab_bounds.set_width(0);
1587 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1588 bounds_animator_.SetAnimationDelegate(
1589 tab,
1590 scoped_ptr<gfx::AnimationDelegate>(new RemoveTabDelegate(this, tab)));
1592 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1593 // like the new tab button magically appears from beyond the end of the tab
1594 // strip.
1595 if (TabDragController::IsAttachedTo(this)) {
1596 bounds_animator_.StopAnimatingView(newtab_button_);
1597 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1601 void TabStrip::AnimateToIdealBounds() {
1602 for (int i = 0; i < tab_count(); ++i) {
1603 Tab* tab = tab_at(i);
1604 if (!tab->dragging()) {
1605 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1606 bounds_animator_.SetAnimationDelegate(
1607 tab,
1608 scoped_ptr<gfx::AnimationDelegate>(
1609 new TabAnimationDelegate(this, tab)));
1613 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1616 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1617 return in_tab_close_;
1620 void TabStrip::DoLayout() {
1621 last_layout_size_ = size();
1623 StopAnimating(false);
1625 SwapLayoutIfNecessary();
1627 if (touch_layout_)
1628 touch_layout_->SetWidth(tab_area_width());
1630 GenerateIdealBounds();
1632 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1633 SetTabVisibility();
1635 SchedulePaint();
1637 bounds_animator_.StopAnimatingView(newtab_button_);
1638 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1641 void TabStrip::SetTabVisibility() {
1642 // We could probably be more efficient here by making use of the fact that the
1643 // tabstrip will always have any visible tabs, and then any invisible tabs, so
1644 // we could e.g. binary-search for the changeover point. But since we have to
1645 // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1646 // worth it.
1647 for (int i = 0; i < tab_count(); ++i) {
1648 Tab* tab = tab_at(i);
1649 tab->SetVisible(ShouldTabBeVisible(tab));
1651 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1652 i != tabs_closing_map_.end(); ++i) {
1653 for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) {
1654 Tab* tab = *j;
1655 tab->SetVisible(ShouldTabBeVisible(tab));
1660 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1661 int delta) {
1662 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1663 if (!touch_layout_) {
1664 StackDraggedTabs(delta);
1665 return;
1667 SetIdealBoundsFromPositions(initial_positions);
1668 touch_layout_->DragActiveTab(delta);
1669 DoLayout();
1672 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1673 if (static_cast<size_t>(tab_count()) != positions.size())
1674 return;
1676 for (int i = 0; i < tab_count(); ++i) {
1677 gfx::Rect bounds(ideal_bounds(i));
1678 bounds.set_x(positions[i]);
1679 tabs_.set_ideal_bounds(i, bounds);
1683 void TabStrip::StackDraggedTabs(int delta) {
1684 DCHECK(!touch_layout_);
1685 GenerateIdealBounds();
1686 const int active_index = controller_->GetActiveIndex();
1687 DCHECK_NE(-1, active_index);
1688 if (delta < 0) {
1689 // Drag the tabs to the left, stacking tabs before the active tab.
1690 const int adjusted_delta =
1691 std::min(ideal_bounds(active_index).x() -
1692 kStackedPadding * std::min(active_index, kMaxStackedCount),
1693 -delta);
1694 for (int i = 0; i <= active_index; ++i) {
1695 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1696 gfx::Rect new_bounds(ideal_bounds(i));
1697 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1698 tabs_.set_ideal_bounds(i, new_bounds);
1700 const bool is_active_pinned = tab_at(active_index)->data().pinned;
1701 const int active_width = ideal_bounds(active_index).width();
1702 for (int i = active_index + 1; i < tab_count(); ++i) {
1703 const int max_x = ideal_bounds(active_index).x() +
1704 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1705 gfx::Rect new_bounds(ideal_bounds(i));
1706 int new_x = std::max(new_bounds.x() + delta, max_x);
1707 if (new_x == max_x && !tab_at(i)->data().pinned && !is_active_pinned &&
1708 new_bounds.width() != active_width)
1709 new_x += (active_width - new_bounds.width());
1710 new_bounds.set_x(new_x);
1711 tabs_.set_ideal_bounds(i, new_bounds);
1713 } else {
1714 // Drag the tabs to the right, stacking tabs after the active tab.
1715 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1716 const int last_tab_x = tab_area_width() - last_tab_width;
1717 if (active_index == tab_count() - 1 &&
1718 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1719 return;
1720 const int adjusted_delta =
1721 std::min(last_tab_x -
1722 kStackedPadding * std::min(tab_count() - active_index - 1,
1723 kMaxStackedCount) -
1724 ideal_bounds(active_index).x(),
1725 delta);
1726 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1727 --i) {
1728 const int max_x = last_tab_x -
1729 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1730 gfx::Rect new_bounds(ideal_bounds(i));
1731 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1732 // Because of rounding not all tabs are the same width. Adjust the
1733 // position to accommodate this, otherwise the stacking is off.
1734 if (new_x == max_x && !tab_at(i)->data().pinned &&
1735 new_bounds.width() != last_tab_width)
1736 new_x += (last_tab_width - new_bounds.width());
1737 new_bounds.set_x(new_x);
1738 tabs_.set_ideal_bounds(i, new_bounds);
1740 for (int i = active_index - 1; i >= 0; --i) {
1741 const int min_x = ideal_bounds(active_index).x() -
1742 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1743 gfx::Rect new_bounds(ideal_bounds(i));
1744 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1745 tabs_.set_ideal_bounds(i, new_bounds);
1747 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1748 newtab_button_->SetVisible(false);
1750 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1751 SchedulePaint();
1754 bool TabStrip::IsStackingDraggedTabs() const {
1755 return drag_controller_.get() && drag_controller_->started_drag() &&
1756 (drag_controller_->move_behavior() ==
1757 TabDragController::MOVE_VISIBILE_TABS);
1760 void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs,
1761 Tab* active_tab,
1762 const gfx::Point& location,
1763 bool initial_drag) {
1764 // Immediately hide the new tab button if the last tab is being dragged.
1765 const Tab* last_visible_tab = GetLastVisibleTab();
1766 if (last_visible_tab && last_visible_tab->dragging())
1767 newtab_button_->SetVisible(false);
1768 std::vector<gfx::Rect> bounds;
1769 CalculateBoundsForDraggedTabs(tabs, &bounds);
1770 DCHECK_EQ(tabs.size(), bounds.size());
1771 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1772 int active_tab_index = static_cast<int>(
1773 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1774 for (size_t i = 0; i < tabs.size(); ++i) {
1775 Tab* tab = tabs[i];
1776 gfx::Rect new_bounds = bounds[i];
1777 new_bounds.Offset(location.x(), location.y());
1778 int consecutive_index =
1779 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1780 // If this is the initial layout during a drag and the tabs aren't
1781 // consecutive animate the view into position. Do the same if the tab is
1782 // already animating (which means we previously caused it to animate).
1783 if ((initial_drag &&
1784 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1785 bounds_animator_.IsAnimating(tabs[i])) {
1786 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1787 } else {
1788 tab->SetBoundsRect(new_bounds);
1791 SetTabVisibility();
1794 void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs,
1795 std::vector<gfx::Rect>* bounds) {
1796 int x = 0;
1797 for (size_t i = 0; i < tabs.size(); ++i) {
1798 Tab* tab = tabs[i];
1799 if (i > 0 && tab->data().pinned != tabs[i - 1]->data().pinned)
1800 x += kPinnedToNonPinnedGap;
1801 gfx::Rect new_bounds = tab->bounds();
1802 new_bounds.set_origin(gfx::Point(x, 0));
1803 bounds->push_back(new_bounds);
1804 x += tab->width() - GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
1808 int TabStrip::GetSizeNeededForTabs(const Tabs& tabs) {
1809 int width = 0;
1810 for (size_t i = 0; i < tabs.size(); ++i) {
1811 Tab* tab = tabs[i];
1812 width += tab->width();
1813 if (i > 0 && tab->data().pinned != tabs[i - 1]->data().pinned)
1814 width += kPinnedToNonPinnedGap;
1816 if (!tabs.empty())
1817 width -= GetLayoutConstant(TABSTRIP_TAB_OVERLAP) * (tabs.size() - 1);
1818 return width;
1821 int TabStrip::GetPinnedTabCount() const {
1822 int pinned_count = 0;
1823 while (pinned_count < tab_count() && tab_at(pinned_count)->data().pinned)
1824 pinned_count++;
1825 return pinned_count;
1828 const Tab* TabStrip::GetLastVisibleTab() const {
1829 for (int i = tab_count() - 1; i >= 0; --i) {
1830 const Tab* tab = tab_at(i);
1831 if (tab->visible())
1832 return tab;
1834 // While in normal use the tabstrip should always be wide enough to have at
1835 // least one visible tab, it can be zero-width in tests, meaning we get here.
1836 return NULL;
1839 void TabStrip::RemoveTabFromViewModel(int index) {
1840 // We still need to paint the tab until we actually remove it. Put it
1841 // in tabs_closing_map_ so we can find it.
1842 tabs_closing_map_[index].push_back(tab_at(index));
1843 UpdateTabsClosingMap(index + 1, -1);
1844 tabs_.Remove(index);
1847 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1848 scoped_ptr<Tab> deleter(tab);
1849 FindClosingTabResult res(FindClosingTab(tab));
1850 res.first->second.erase(res.second);
1851 if (res.first->second.empty())
1852 tabs_closing_map_.erase(res.first);
1855 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1856 if (tabs_closing_map_.empty())
1857 return;
1859 if (delta == -1 &&
1860 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1861 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1862 const Tabs& tabs(tabs_closing_map_[index]);
1863 tabs_closing_map_[index - 1].insert(
1864 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1866 TabsClosingMap updated_map;
1867 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1868 i != tabs_closing_map_.end(); ++i) {
1869 if (i->first > index)
1870 updated_map[i->first + delta] = i->second;
1871 else if (i->first < index)
1872 updated_map[i->first] = i->second;
1874 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1875 updated_map[index + delta] = tabs_closing_map_[index];
1876 tabs_closing_map_.swap(updated_map);
1879 void TabStrip::StartedDraggingTabs(const Tabs& tabs) {
1880 // Let the controller know that the user started dragging tabs.
1881 controller()->OnStartedDraggingTabs();
1883 // Hide the new tab button immediately if we didn't originate the drag.
1884 if (!drag_controller_.get())
1885 newtab_button_->SetVisible(false);
1887 PrepareForAnimation();
1889 // Reset dragging state of existing tabs.
1890 for (int i = 0; i < tab_count(); ++i)
1891 tab_at(i)->set_dragging(false);
1893 for (size_t i = 0; i < tabs.size(); ++i) {
1894 tabs[i]->set_dragging(true);
1895 bounds_animator_.StopAnimatingView(tabs[i]);
1898 // Move the dragged tabs to their ideal bounds.
1899 GenerateIdealBounds();
1901 // Sets the bounds of the dragged tabs.
1902 for (size_t i = 0; i < tabs.size(); ++i) {
1903 int tab_data_index = GetModelIndexOfTab(tabs[i]);
1904 DCHECK_NE(-1, tab_data_index);
1905 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1907 SetTabVisibility();
1908 SchedulePaint();
1911 void TabStrip::DraggedTabsDetached() {
1912 // Let the controller know that the user is not dragging this tabstrip's tabs
1913 // anymore.
1914 controller()->OnStoppedDraggingTabs();
1915 newtab_button_->SetVisible(true);
1918 void TabStrip::StoppedDraggingTabs(const Tabs& tabs,
1919 const std::vector<int>& initial_positions,
1920 bool move_only,
1921 bool completed) {
1922 // Let the controller know that the user stopped dragging tabs.
1923 controller()->OnStoppedDraggingTabs();
1925 newtab_button_->SetVisible(true);
1926 if (move_only && touch_layout_) {
1927 if (completed)
1928 touch_layout_->SizeToFit();
1929 else
1930 SetIdealBoundsFromPositions(initial_positions);
1932 bool is_first_tab = true;
1933 for (size_t i = 0; i < tabs.size(); ++i)
1934 StoppedDraggingTab(tabs[i], &is_first_tab);
1937 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1938 int tab_data_index = GetModelIndexOfTab(tab);
1939 if (tab_data_index == -1) {
1940 // The tab was removed before the drag completed. Don't do anything.
1941 return;
1944 if (*is_first_tab) {
1945 *is_first_tab = false;
1946 PrepareForAnimation();
1948 // Animate the view back to its correct position.
1949 GenerateIdealBounds();
1950 AnimateToIdealBounds();
1952 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1953 // Install a delegate to reset the dragging state when done. We have to leave
1954 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1955 bounds_animator_.SetAnimationDelegate(
1956 tab,
1957 scoped_ptr<gfx::AnimationDelegate>(
1958 new ResetDraggingStateDelegate(this, tab)));
1961 void TabStrip::OwnDragController(TabDragController* controller) {
1962 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1963 // via corresponding calls to TabDragController::Detach() and
1964 // TabDragController::Attach(). There is one exception to that rule: when a
1965 // drag might start, we create a TabDragController that is owned by the
1966 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1967 // we then call Attach() on the source tabstrip, but since the source tabstrip
1968 // already owns the TabDragController, so we don't need to do anything.
1969 if (controller != drag_controller_.get())
1970 drag_controller_.reset(controller);
1973 void TabStrip::DestroyDragController() {
1974 newtab_button_->SetVisible(true);
1975 drag_controller_.reset();
1978 TabDragController* TabStrip::ReleaseDragController() {
1979 return drag_controller_.release();
1982 TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
1983 DCHECK(tab->closing());
1984 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1985 i != tabs_closing_map_.end(); ++i) {
1986 Tabs::iterator j = std::find(i->second.begin(), i->second.end(), tab);
1987 if (j != i->second.end())
1988 return FindClosingTabResult(i, j);
1990 NOTREACHED();
1991 return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
1994 void TabStrip::PaintClosingTabs(int index, const ui::PaintContext& context) {
1995 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
1996 return;
1998 const Tabs& tabs = tabs_closing_map_[index];
1999 for (Tabs::const_reverse_iterator i(tabs.rbegin()); i != tabs.rend(); ++i)
2000 (*i)->Paint(context);
2003 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
2004 const ui::MouseEvent& event) {
2005 if (!adjust_layout_)
2006 return;
2008 // The following code attempts to switch to shrink (not stacked) layout when
2009 // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
2010 // to stacked layout when a touch device is used. This is made problematic by
2011 // windows generating mouse move events that do not clearly indicate the move
2012 // is the result of a touch device. This assumes a real mouse is used if
2013 // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
2014 // the time window |kMouseMoveTimeMS|. At the time we get a mouse press we
2015 // know whether its from a touch device or not, but we don't layout then else
2016 // everything shifts. Instead we wait for the release.
2018 // TODO(sky): revisit this when touch events are really plumbed through.
2020 switch (event.type()) {
2021 case ui::ET_MOUSE_PRESSED:
2022 mouse_move_count_ = 0;
2023 last_mouse_move_time_ = base::TimeTicks();
2024 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
2025 if (reset_to_shrink_on_exit_ && touch_layout_) {
2026 gfx::Point tab_strip_point(event.location());
2027 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
2028 Tab* tab = FindTabForEvent(tab_strip_point);
2029 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
2030 SetStackedLayout(false);
2031 controller_->StackedLayoutMaybeChanged();
2034 break;
2036 case ui::ET_MOUSE_MOVED: {
2037 #if defined(USE_ASH)
2038 // Ash does not synthesize mouse events from touch events.
2039 SetResetToShrinkOnExit(true);
2040 #else
2041 gfx::Point location(event.location());
2042 ConvertPointToTarget(source, this, &location);
2043 if (location == last_mouse_move_location_)
2044 return; // Ignore spurious moves.
2045 last_mouse_move_location_ = location;
2046 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2047 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2048 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2049 kMouseMoveTimeMS) {
2050 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2051 SetResetToShrinkOnExit(true);
2052 } else {
2053 mouse_move_count_ = 1;
2054 last_mouse_move_time_ = base::TimeTicks::Now();
2056 } else {
2057 last_mouse_move_time_ = base::TimeTicks();
2059 #endif
2060 break;
2063 case ui::ET_MOUSE_RELEASED: {
2064 gfx::Point location(event.location());
2065 ConvertPointToTarget(source, this, &location);
2066 last_mouse_move_location_ = location;
2067 mouse_move_count_ = 0;
2068 last_mouse_move_time_ = base::TimeTicks();
2069 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2070 SetStackedLayout(true);
2071 controller_->StackedLayoutMaybeChanged();
2073 break;
2076 default:
2077 break;
2081 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2082 double* selected_width) const {
2083 *unselected_width = current_unselected_width_;
2084 *selected_width = current_selected_width_;
2087 void TabStrip::GetDesiredTabWidths(int tab_count,
2088 int pinned_tab_count,
2089 double* unselected_width,
2090 double* selected_width) const {
2091 DCHECK(tab_count >= 0 && pinned_tab_count >= 0 &&
2092 pinned_tab_count <= tab_count);
2093 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2094 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2096 *unselected_width = min_unselected_width;
2097 *selected_width = min_selected_width;
2099 if (tab_count == 0) {
2100 // Return immediately to avoid divide-by-zero below.
2101 return;
2104 // Determine how much space we can actually allocate to tabs.
2105 int available_width = (available_width_for_tabs_ < 0) ?
2106 tab_area_width() : available_width_for_tabs_;
2107 const int tab_overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2108 if (pinned_tab_count > 0) {
2109 available_width -=
2110 pinned_tab_count * (Tab::GetPinnedWidth() - tab_overlap);
2111 tab_count -= pinned_tab_count;
2112 if (tab_count == 0) {
2113 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2114 return;
2116 // Account for gap between the last pinned tab and first non-pinned tab.
2117 available_width -= kPinnedToNonPinnedGap;
2120 // Calculate the desired tab widths by dividing the available space into equal
2121 // portions. Don't let tabs get larger than the "standard width" or smaller
2122 // than the minimum width for each type, respectively.
2123 const int total_overlap = tab_overlap * (tab_count - 1);
2124 const double desired_tab_width = std::min((static_cast<double>(
2125 available_width + total_overlap) / static_cast<double>(tab_count)),
2126 static_cast<double>(Tab::GetStandardSize().width()));
2127 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2128 *selected_width = std::max(desired_tab_width, min_selected_width);
2130 // When there are multiple tabs, we'll have one selected and some unselected
2131 // tabs. If the desired width was between the minimum sizes of these types,
2132 // try to shrink the tabs with the smaller minimum. For example, if we have
2133 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2134 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2135 // width of 1, the above code would set *unselected_width = 2.5,
2136 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2137 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2138 // of 10.
2139 if (tab_count > 1) {
2140 if (desired_tab_width < min_selected_width) {
2141 // Unselected width = (total width - selected width) / (num_tabs - 1)
2142 *unselected_width = std::max(static_cast<double>(
2143 available_width + total_overlap - min_selected_width) /
2144 static_cast<double>(tab_count - 1), min_unselected_width);
2149 void TabStrip::ResizeLayoutTabs() {
2150 // We've been called back after the TabStrip has been emptied out (probably
2151 // just prior to the window being destroyed). We need to do nothing here or
2152 // else GetTabAt below will crash.
2153 if (tab_count() == 0)
2154 return;
2156 // It is critically important that this is unhooked here, otherwise we will
2157 // keep spying on messages forever.
2158 RemoveMessageLoopObserver();
2160 in_tab_close_ = false;
2161 available_width_for_tabs_ = -1;
2162 int pinned_tab_count = GetPinnedTabCount();
2163 if (pinned_tab_count == tab_count()) {
2164 // Only pinned tabs, we know the tab widths won't have changed (all
2165 // pinned tabs have the same width), so there is nothing to do.
2166 return;
2168 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2169 // then the width of the active tab may not change, but other widths may
2170 // have. This is particularly important if we've overflowed (all tabs are at
2171 // the min).
2172 StartResizeLayoutAnimation();
2175 void TabStrip::ResizeLayoutTabsFromTouch() {
2176 // Don't resize if the user is interacting with the tabstrip.
2177 if (!drag_controller_.get())
2178 ResizeLayoutTabs();
2179 else
2180 StartResizeLayoutTabsFromTouchTimer();
2183 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2184 resize_layout_timer_.Stop();
2185 resize_layout_timer_.Start(
2186 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2187 this, &TabStrip::ResizeLayoutTabsFromTouch);
2190 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2191 StopAnimating(false);
2192 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2193 for (int i = 0; i < tab_count(); ++i)
2194 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2195 // Reset the layout size as we've effectively layed out a different size.
2196 // This ensures a layout happens after the drag is done.
2197 last_layout_size_ = gfx::Size();
2200 void TabStrip::AddMessageLoopObserver() {
2201 if (!mouse_watcher_.get()) {
2202 mouse_watcher_.reset(
2203 new views::MouseWatcher(
2204 new views::MouseWatcherViewHost(
2205 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2206 this));
2208 mouse_watcher_->Start();
2211 void TabStrip::RemoveMessageLoopObserver() {
2212 mouse_watcher_.reset(NULL);
2215 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2216 bool drop_before,
2217 bool* is_beneath) {
2218 DCHECK_NE(drop_index, -1);
2219 int center_x;
2220 const int tab_overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2221 if (drop_index < tab_count()) {
2222 Tab* tab = tab_at(drop_index);
2223 center_x = tab->x() + ((drop_before ? tab_overlap : tab->width()) / 2);
2224 } else {
2225 Tab* last_tab = tab_at(drop_index - 1);
2226 center_x = last_tab->x() + last_tab->width() - (tab_overlap / 2);
2229 // Mirror the center point if necessary.
2230 center_x = GetMirroredXInView(center_x);
2232 // Determine the screen bounds.
2233 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2234 -drop_indicator_height);
2235 ConvertPointToScreen(this, &drop_loc);
2236 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2237 drop_indicator_height);
2239 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2240 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2241 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2242 *is_beneath = !display.bounds().Contains(drop_bounds);
2243 if (*is_beneath)
2244 drop_bounds.Offset(0, drop_bounds.height() + height());
2246 return drop_bounds;
2249 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2250 // If the UI layout is right-to-left, we need to mirror the mouse
2251 // coordinates since we calculate the drop index based on the
2252 // original (and therefore non-mirrored) positions of the tabs.
2253 const int x = GetMirroredXInView(event.x());
2254 // We don't allow replacing the urls of pinned tabs.
2255 for (int i = GetPinnedTabCount(); i < tab_count(); ++i) {
2256 Tab* tab = tab_at(i);
2257 const int tab_max_x = tab->x() + tab->width();
2258 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2259 if (x < tab_max_x) {
2260 if (x < tab->x() + hot_width)
2261 SetDropIndex(i, true);
2262 else if (x >= tab_max_x - hot_width)
2263 SetDropIndex(i + 1, true);
2264 else
2265 SetDropIndex(i, false);
2266 return;
2270 // The drop isn't over a tab, add it to the end.
2271 SetDropIndex(tab_count(), true);
2274 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2275 // Let the controller know of the index update.
2276 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2278 if (tab_data_index == -1) {
2279 if (drop_info_.get())
2280 drop_info_.reset(NULL);
2281 return;
2284 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2285 drop_info_->drop_before == drop_before) {
2286 return;
2289 bool is_beneath;
2290 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2291 &is_beneath);
2293 if (!drop_info_.get()) {
2294 drop_info_.reset(
2295 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2296 } else {
2297 drop_info_->drop_index = tab_data_index;
2298 drop_info_->drop_before = drop_before;
2299 if (is_beneath == drop_info_->point_down) {
2300 drop_info_->point_down = !is_beneath;
2301 drop_info_->arrow_view->SetImage(
2302 GetDropArrowImage(drop_info_->point_down));
2306 // Reposition the window. Need to show it too as the window is initially
2307 // hidden.
2308 drop_info_->arrow_window->SetBounds(drop_bounds);
2309 drop_info_->arrow_window->Show();
2312 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2313 const int source_ops = event.source_operations();
2314 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2315 return ui::DragDropTypes::DRAG_COPY;
2316 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2317 return ui::DragDropTypes::DRAG_LINK;
2318 return ui::DragDropTypes::DRAG_MOVE;
2321 // static
2322 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2323 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2324 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2327 // TabStrip::DropInfo ----------------------------------------------------------
2329 TabStrip::DropInfo::DropInfo(int drop_index,
2330 bool drop_before,
2331 bool point_down,
2332 views::Widget* context)
2333 : drop_index(drop_index),
2334 drop_before(drop_before),
2335 point_down(point_down),
2336 file_supported(true) {
2337 arrow_view = new views::ImageView;
2338 arrow_view->SetImage(GetDropArrowImage(point_down));
2340 arrow_window = new views::Widget;
2341 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2342 params.keep_on_top = true;
2343 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2344 params.accept_events = false;
2345 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2346 params.context = context->GetNativeWindow();
2347 arrow_window->Init(params);
2348 arrow_window->SetContentsView(arrow_view);
2351 TabStrip::DropInfo::~DropInfo() {
2352 // Close eventually deletes the window, which deletes arrow_view too.
2353 arrow_window->Close();
2356 ///////////////////////////////////////////////////////////////////////////////
2358 void TabStrip::PrepareForAnimation() {
2359 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2360 for (int i = 0; i < tab_count(); ++i)
2361 tab_at(i)->set_dragging(false);
2365 void TabStrip::GenerateIdealBounds() {
2366 int new_tab_y = 0;
2368 if (touch_layout_) {
2369 if (tabs_.view_size() == 0)
2370 return;
2372 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2373 kNewTabButtonHorizontalOffset;
2374 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2375 return;
2378 GetDesiredTabWidths(tab_count(), GetPinnedTabCount(),
2379 &current_unselected_width_, &current_selected_width_);
2381 // NOTE: This currently assumes a tab's height doesn't differ based on
2382 // selected state or the number of tabs in the strip!
2383 int tab_height = Tab::GetStandardSize().height();
2384 int first_non_pinned_index = 0;
2385 double tab_x = GenerateIdealBoundsForPinnedTabs(&first_non_pinned_index);
2386 const int tab_overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2387 for (int i = first_non_pinned_index; i < tab_count(); ++i) {
2388 Tab* tab = tab_at(i);
2389 DCHECK(!tab->data().pinned);
2390 double tab_width =
2391 tab->IsActive() ? current_selected_width_ : current_unselected_width_;
2392 double end_of_tab = tab_x + tab_width;
2393 int rounded_tab_x = Round(tab_x);
2394 tabs_.set_ideal_bounds(
2396 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2397 tab_height));
2398 tab_x = end_of_tab - tab_overlap;
2401 // Update bounds of new tab button.
2402 int new_tab_x;
2403 if ((Tab::GetStandardSize().width() - Round(current_unselected_width_)) > 1 &&
2404 !in_tab_close_) {
2405 // We're shrinking tabs, so we need to anchor the New Tab button to the
2406 // right edge of the TabStrip's bounds, rather than the right edge of the
2407 // right-most Tab, otherwise it'll bounce when animating.
2408 new_tab_x = width() - newtab_button_bounds_.width();
2409 } else {
2410 new_tab_x = Round(tab_x + tab_overlap) +
2411 kNewTabButtonHorizontalOffset;
2413 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2416 int TabStrip::GenerateIdealBoundsForPinnedTabs(int* first_non_pinned_index) {
2417 int next_x = 0;
2418 int pinned_width = Tab::GetPinnedWidth();
2419 int tab_height = Tab::GetStandardSize().height();
2420 int index = 0;
2421 for (; index < tab_count() && tab_at(index)->data().pinned; ++index) {
2422 tabs_.set_ideal_bounds(index,
2423 gfx::Rect(next_x, 0, pinned_width, tab_height));
2424 next_x += pinned_width - GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2426 if (index > 0 && index < tab_count())
2427 next_x += kPinnedToNonPinnedGap;
2428 if (first_non_pinned_index)
2429 *first_non_pinned_index = index;
2430 return next_x;
2433 void TabStrip::StartResizeLayoutAnimation() {
2434 PrepareForAnimation();
2435 GenerateIdealBounds();
2436 AnimateToIdealBounds();
2439 void TabStrip::StartPinnedTabAnimation() {
2440 in_tab_close_ = false;
2441 available_width_for_tabs_ = -1;
2443 PrepareForAnimation();
2445 GenerateIdealBounds();
2446 AnimateToIdealBounds();
2449 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2450 // The user initiated the close. We want to persist the bounds of all the
2451 // existing tabs, so we manually shift ideal_bounds then animate.
2452 Tab* tab_closing = tab_at(model_index);
2453 int delta = tab_closing->width() - GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2454 // If the tab being closed is a pinned tab next to a non-pinned tab, be sure
2455 // to add the extra padding.
2456 DCHECK_LT(model_index, tab_count() - 1);
2457 if (tab_closing->data().pinned && !tab_at(model_index + 1)->data().pinned)
2458 delta += kPinnedToNonPinnedGap;
2460 for (int i = model_index + 1; i < tab_count(); ++i) {
2461 gfx::Rect bounds = ideal_bounds(i);
2462 bounds.set_x(bounds.x() - delta);
2463 tabs_.set_ideal_bounds(i, bounds);
2466 // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2467 // overflow tabs that will be able to animate into the strip, in which case
2468 // the new tab button should stay where it is.
2469 newtab_button_bounds_.set_x(std::min(
2470 width() - newtab_button_bounds_.width(),
2471 ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset));
2473 PrepareForAnimation();
2475 tab_closing->set_closing(true);
2477 // We still need to paint the tab until we actually remove it. Put it in
2478 // tabs_closing_map_ so we can find it.
2479 RemoveTabFromViewModel(model_index);
2481 AnimateToIdealBounds();
2483 gfx::Rect tab_bounds = tab_closing->bounds();
2484 tab_bounds.set_width(0);
2485 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2487 // Register delegate to do cleanup when done, BoundsAnimator takes
2488 // ownership of RemoveTabDelegate.
2489 bounds_animator_.SetAnimationDelegate(
2490 tab_closing,
2491 scoped_ptr<gfx::AnimationDelegate>(
2492 new RemoveTabDelegate(this, tab_closing)));
2495 bool TabStrip::IsPointInTab(Tab* tab,
2496 const gfx::Point& point_in_tabstrip_coords) {
2497 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2498 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2499 return tab->HitTestPoint(point_in_tab_coords);
2502 int TabStrip::GetStartXForNormalTabs() const {
2503 int pinned_tab_count = GetPinnedTabCount();
2504 if (pinned_tab_count == 0)
2505 return 0;
2506 const int overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2507 return pinned_tab_count * (Tab::GetPinnedWidth() - overlap) +
2508 kPinnedToNonPinnedGap;
2511 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2512 if (touch_layout_) {
2513 int active_tab_index = touch_layout_->active_index();
2514 if (active_tab_index != -1) {
2515 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2516 if (!tab)
2517 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2518 return tab;
2520 if (tab_count())
2521 return FindTabForEventFrom(point, 0, 1);
2522 } else {
2523 for (int i = 0; i < tab_count(); ++i) {
2524 if (IsPointInTab(tab_at(i), point))
2525 return tab_at(i);
2528 return NULL;
2531 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2532 int start,
2533 int delta) {
2534 // |start| equals tab_count() when there are only pinned tabs.
2535 if (start == tab_count())
2536 start += delta;
2537 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2538 if (IsPointInTab(tab_at(i), point))
2539 return tab_at(i);
2541 return NULL;
2544 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2545 // The display order doesn't necessarily match the child list order, so we
2546 // walk the display list hit-testing Tabs. Since the active tab always
2547 // renders on top of adjacent tabs, it needs to be hit-tested before any
2548 // left-adjacent Tab, so we look ahead for it as we walk.
2549 for (int i = 0; i < tab_count(); ++i) {
2550 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2551 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2552 return next_tab;
2553 if (IsPointInTab(tab_at(i), point))
2554 return tab_at(i);
2557 return NULL;
2560 std::vector<int> TabStrip::GetTabXCoordinates() {
2561 std::vector<int> results;
2562 for (int i = 0; i < tab_count(); ++i)
2563 results.push_back(ideal_bounds(i).x());
2564 return results;
2567 void TabStrip::SwapLayoutIfNecessary() {
2568 bool needs_touch = NeedsTouchLayout();
2569 bool using_touch = touch_layout_ != NULL;
2570 if (needs_touch == using_touch)
2571 return;
2573 if (needs_touch) {
2574 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2575 tab_size.set_width(Tab::GetTouchWidth());
2576 touch_layout_.reset(new StackedTabStripLayout(
2577 tab_size,
2578 GetLayoutConstant(TABSTRIP_TAB_OVERLAP),
2579 kStackedPadding,
2580 kMaxStackedCount,
2581 &tabs_));
2582 touch_layout_->SetWidth(tab_area_width());
2583 // This has to be after SetWidth() as SetWidth() is going to reset the
2584 // bounds of the pinned tabs (since StackedTabStripLayout doesn't yet know
2585 // how many pinned tabs there are).
2586 GenerateIdealBoundsForPinnedTabs(NULL);
2587 touch_layout_->SetXAndPinnedCount(GetStartXForNormalTabs(),
2588 GetPinnedTabCount());
2589 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2591 content::RecordAction(UserMetricsAction("StackedTab_EnteredStackedLayout"));
2592 } else {
2593 touch_layout_.reset();
2595 PrepareForAnimation();
2596 GenerateIdealBounds();
2597 SetTabVisibility();
2598 AnimateToIdealBounds();
2601 bool TabStrip::NeedsTouchLayout() const {
2602 if (!stacked_layout_)
2603 return false;
2605 int pinned_tab_count = GetPinnedTabCount();
2606 int normal_count = tab_count() - pinned_tab_count;
2607 if (normal_count <= 1 || normal_count == pinned_tab_count)
2608 return false;
2609 const int overlap = GetLayoutConstant(TABSTRIP_TAB_OVERLAP);
2610 return (Tab::GetTouchWidth() * normal_count - overlap * (normal_count - 1)) >
2611 tab_area_width() - GetStartXForNormalTabs();
2614 void TabStrip::SetResetToShrinkOnExit(bool value) {
2615 if (!adjust_layout_)
2616 return;
2618 if (value && !stacked_layout_)
2619 value = false; // We're already using shrink (not stacked) layout.
2621 if (value == reset_to_shrink_on_exit_)
2622 return;
2624 reset_to_shrink_on_exit_ = value;
2625 // Add an observer so we know when the mouse moves out of the tabstrip.
2626 if (reset_to_shrink_on_exit_)
2627 AddMessageLoopObserver();
2628 else
2629 RemoveMessageLoopObserver();
2632 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
2633 if (sender == newtab_button_) {
2634 content::RecordAction(UserMetricsAction("NewTab_Button"));
2635 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
2636 TabStripModel::NEW_TAB_ENUM_COUNT);
2637 if (event.IsMouseEvent()) {
2638 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
2639 if (mouse.IsOnlyMiddleMouseButton()) {
2640 base::string16 clipboard_text = GetClipboardText();
2641 if (!clipboard_text.empty())
2642 controller()->CreateNewTabWithLocation(clipboard_text);
2643 return;
2647 controller()->CreateNewTab();
2648 if (event.type() == ui::ET_GESTURE_TAP)
2649 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
2653 // Overridden to support automation. See automation_proxy_uitest.cc.
2654 const views::View* TabStrip::GetViewByID(int view_id) const {
2655 if (tab_count() > 0) {
2656 if (view_id == VIEW_ID_TAB_LAST)
2657 return tab_at(tab_count() - 1);
2658 if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
2659 int index = view_id - VIEW_ID_TAB_0;
2660 return (index >= 0 && index < tab_count()) ? tab_at(index) : NULL;
2664 return View::GetViewByID(view_id);
2667 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
2668 UpdateStackedLayoutFromMouseEvent(this, event);
2669 // We can't return true here, else clicking in an empty area won't drag the
2670 // window.
2671 return false;
2674 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
2675 ContinueDrag(this, event);
2676 return true;
2679 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
2680 EndDrag(END_DRAG_COMPLETE);
2681 UpdateStackedLayoutFromMouseEvent(this, event);
2684 void TabStrip::OnMouseCaptureLost() {
2685 EndDrag(END_DRAG_CAPTURE_LOST);
2688 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
2689 UpdateStackedLayoutFromMouseEvent(this, event);
2692 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
2693 SetResetToShrinkOnExit(true);
2696 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
2697 SetResetToShrinkOnExit(false);
2698 switch (event->type()) {
2699 case ui::ET_GESTURE_SCROLL_END:
2700 case ui::ET_SCROLL_FLING_START:
2701 case ui::ET_GESTURE_END:
2702 EndDrag(END_DRAG_COMPLETE);
2703 if (adjust_layout_) {
2704 SetStackedLayout(true);
2705 controller_->StackedLayoutMaybeChanged();
2707 break;
2709 case ui::ET_GESTURE_LONG_PRESS:
2710 if (drag_controller_.get())
2711 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
2712 break;
2714 case ui::ET_GESTURE_LONG_TAP: {
2715 EndDrag(END_DRAG_CANCEL);
2716 gfx::Point local_point = event->location();
2717 Tab* tab = FindTabForEvent(local_point);
2718 if (tab) {
2719 ConvertPointToScreen(this, &local_point);
2720 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
2722 break;
2725 case ui::ET_GESTURE_SCROLL_UPDATE:
2726 ContinueDrag(this, *event);
2727 break;
2729 case ui::ET_GESTURE_TAP_DOWN:
2730 EndDrag(END_DRAG_CANCEL);
2731 break;
2733 case ui::ET_GESTURE_TAP: {
2734 const int active_index = controller_->GetActiveIndex();
2735 DCHECK_NE(-1, active_index);
2736 Tab* active_tab = tab_at(active_index);
2737 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
2738 if (active_tab->tab_activated_with_last_tap_down())
2739 action = TouchUMA::GESTURE_TABSWITCH_TAP;
2740 TouchUMA::RecordGestureAction(action);
2741 break;
2744 default:
2745 break;
2747 event->SetHandled();
2750 views::View* TabStrip::TargetForRect(views::View* root, const gfx::Rect& rect) {
2751 CHECK_EQ(root, this);
2753 if (!views::UsePointBasedTargeting(rect))
2754 return views::ViewTargeterDelegate::TargetForRect(root, rect);
2755 const gfx::Point point(rect.CenterPoint());
2757 if (!touch_layout_) {
2758 // Return any view that isn't a Tab or this TabStrip immediately. We don't
2759 // want to interfere.
2760 views::View* v = views::ViewTargeterDelegate::TargetForRect(root, rect);
2761 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
2762 return v;
2764 views::View* tab = FindTabHitByPoint(point);
2765 if (tab)
2766 return tab;
2767 } else {
2768 if (newtab_button_->visible()) {
2769 views::View* view =
2770 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
2771 if (view)
2772 return view;
2774 Tab* tab = FindTabForEvent(point);
2775 if (tab)
2776 return ConvertPointToViewAndGetEventHandler(this, tab, point);
2778 return this;