Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip.cc
bloba2e8c1e7a7b7341d5fd64a2e7b8eb87cd392ffc2
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/tabs/tab_strip.h"
7 #if defined(OS_WIN)
8 #include <windowsx.h>
9 #endif
11 #include <algorithm>
12 #include <iterator>
13 #include <string>
14 #include <vector>
16 #include "base/command_line.h"
17 #include "base/compiler_specific.h"
18 #include "base/metrics/histogram.h"
19 #include "base/stl_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "chrome/browser/defaults.h"
22 #include "chrome/browser/ui/host_desktop.h"
23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
24 #include "chrome/browser/ui/view_ids.h"
25 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
26 #include "chrome/browser/ui/views/tabs/tab.h"
27 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
28 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
29 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
30 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/grit/generated_resources.h"
33 #include "content/public/browser/user_metrics.h"
34 #include "grit/theme_resources.h"
35 #include "ui/accessibility/ax_view_state.h"
36 #include "ui/base/default_theme_provider.h"
37 #include "ui/base/dragdrop/drag_drop_types.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/models/list_selection_model.h"
40 #include "ui/base/resource/resource_bundle.h"
41 #include "ui/compositor/compositing_recorder.h"
42 #include "ui/compositor/paint_recorder.h"
43 #include "ui/gfx/animation/animation_container.h"
44 #include "ui/gfx/animation/throb_animation.h"
45 #include "ui/gfx/canvas.h"
46 #include "ui/gfx/display.h"
47 #include "ui/gfx/geometry/rect_conversions.h"
48 #include "ui/gfx/geometry/size.h"
49 #include "ui/gfx/image/image_skia.h"
50 #include "ui/gfx/image/image_skia_operations.h"
51 #include "ui/gfx/path.h"
52 #include "ui/gfx/screen.h"
53 #include "ui/gfx/skia_util.h"
54 #include "ui/views/controls/image_view.h"
55 #include "ui/views/masked_targeter_delegate.h"
56 #include "ui/views/mouse_watcher_view_host.h"
57 #include "ui/views/rect_based_targeting_utils.h"
58 #include "ui/views/view_model_utils.h"
59 #include "ui/views/view_targeter.h"
60 #include "ui/views/widget/root_view.h"
61 #include "ui/views/widget/widget.h"
62 #include "ui/views/window/non_client_view.h"
64 #if defined(OS_WIN)
65 #include "ui/gfx/win/dpi.h"
66 #include "ui/gfx/win/hwnd_util.h"
67 #include "ui/views/widget/monitor_win.h"
68 #include "ui/views/win/hwnd_util.h"
69 #endif
71 using base::UserMetricsAction;
72 using ui::DropTargetEvent;
74 namespace {
76 static const int kTabStripAnimationVSlop = 40;
77 // Inactive tabs in a native frame are slightly transparent.
78 static const uint8_t kGlassFrameInactiveTabAlpha = 200;
79 // If there are multiple tabs selected then make non-selected inactive tabs
80 // even more transparent.
81 static const int kGlassFrameInactiveTabAlphaMultiSelection = 150;
83 // Alpha applied to all elements save the selected tabs.
84 static const uint8_t kInactiveTabAndNewTabButtonAlphaAsh = 230;
85 static const uint8_t kInactiveTabAndNewTabButtonAlpha = 255;
87 // Inverse ratio of the width of a tab edge to the width of the tab. When
88 // hovering over the left or right edge of a tab, the drop indicator will
89 // point between tabs.
90 static const int kTabEdgeRatioInverse = 4;
92 // Size of the drop indicator.
93 static int drop_indicator_width;
94 static int drop_indicator_height;
96 static inline int Round(double x) {
97 // Why oh why is this not in a standard header?
98 return static_cast<int>(floor(x + 0.5));
101 // Max number of stacked tabs.
102 static const int kMaxStackedCount = 4;
104 // Padding between stacked tabs.
105 static const int kStackedPadding = 6;
107 // See UpdateLayoutTypeFromMouseEvent() for a description of these.
108 #if !defined(USE_ASH)
109 const int kMouseMoveTimeMS = 200;
110 const int kMouseMoveCountBeforeConsiderReal = 3;
111 #endif
113 // Amount of time we delay before resizing after a close from a touch.
114 const int kTouchResizeLayoutTimeMS = 2000;
116 // Amount the left edge of a tab is offset from the rectangle of the tab's
117 // favicon/title/close box. Related to the width of IDR_TAB_ACTIVE_LEFT.
118 // Affects the size of the "V" between adjacent tabs.
119 #if defined(OS_MACOSX)
120 const int kTabHorizontalOffset = -19;
121 #else
122 const int kTabHorizontalOffset = -26;
123 #endif
125 // Amount to adjust the clip by when the tab is stacked before the active index.
126 const int kStackedTabLeftClip = 20;
128 // Amount to adjust the clip by when the tab is stacked after the active index.
129 const int kStackedTabRightClip = 20;
131 base::string16 GetClipboardText() {
132 if (!ui::Clipboard::IsSupportedClipboardType(ui::CLIPBOARD_TYPE_SELECTION))
133 return base::string16();
134 ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
135 CHECK(clipboard);
136 base::string16 clipboard_text;
137 clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &clipboard_text);
138 return clipboard_text;
141 // Animation delegate used for any automatic tab movement. Hides the tab if it
142 // is not fully visible within the tabstrip area, to prevent overflow clipping.
143 class TabAnimationDelegate : public gfx::AnimationDelegate {
144 public:
145 TabAnimationDelegate(TabStrip* tab_strip, Tab* tab);
146 ~TabAnimationDelegate() override;
148 void AnimationProgressed(const gfx::Animation* animation) override;
150 protected:
151 TabStrip* tab_strip() { return tab_strip_; }
152 Tab* tab() { return tab_; }
154 private:
155 TabStrip* const tab_strip_;
156 Tab* const tab_;
158 DISALLOW_COPY_AND_ASSIGN(TabAnimationDelegate);
161 TabAnimationDelegate::TabAnimationDelegate(TabStrip* tab_strip, Tab* tab)
162 : tab_strip_(tab_strip),
163 tab_(tab) {
166 TabAnimationDelegate::~TabAnimationDelegate() {
169 void TabAnimationDelegate::AnimationProgressed(
170 const gfx::Animation* animation) {
171 tab_->SetVisible(tab_strip_->ShouldTabBeVisible(tab_));
174 // Animation delegate used when a dragged tab is released. When done sets the
175 // dragging state to false.
176 class ResetDraggingStateDelegate : public TabAnimationDelegate {
177 public:
178 ResetDraggingStateDelegate(TabStrip* tab_strip, Tab* tab);
179 ~ResetDraggingStateDelegate() override;
181 void AnimationEnded(const gfx::Animation* animation) override;
182 void AnimationCanceled(const gfx::Animation* animation) override;
184 private:
185 DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
188 ResetDraggingStateDelegate::ResetDraggingStateDelegate(TabStrip* tab_strip,
189 Tab* tab)
190 : TabAnimationDelegate(tab_strip, tab) {
193 ResetDraggingStateDelegate::~ResetDraggingStateDelegate() {
196 void ResetDraggingStateDelegate::AnimationEnded(
197 const gfx::Animation* animation) {
198 tab()->set_dragging(false);
199 AnimationProgressed(animation); // Forces tab visibility to update.
202 void ResetDraggingStateDelegate::AnimationCanceled(
203 const gfx::Animation* animation) {
204 AnimationEnded(animation);
207 // If |dest| contains the point |point_in_source| the event handler from |dest|
208 // is returned. Otherwise NULL is returned.
209 views::View* ConvertPointToViewAndGetEventHandler(
210 views::View* source,
211 views::View* dest,
212 const gfx::Point& point_in_source) {
213 gfx::Point dest_point(point_in_source);
214 views::View::ConvertPointToTarget(source, dest, &dest_point);
215 return dest->HitTestPoint(dest_point) ?
216 dest->GetEventHandlerForPoint(dest_point) : NULL;
219 // Gets a tooltip handler for |point_in_source| from |dest|. Note that |dest|
220 // should return NULL if it does not contain the point.
221 views::View* ConvertPointToViewAndGetTooltipHandler(
222 views::View* source,
223 views::View* dest,
224 const gfx::Point& point_in_source) {
225 gfx::Point dest_point(point_in_source);
226 views::View::ConvertPointToTarget(source, dest, &dest_point);
227 return dest->GetTooltipHandlerForPoint(dest_point);
230 TabDragController::EventSource EventSourceFromEvent(
231 const ui::LocatedEvent& event) {
232 return event.IsGestureEvent() ? TabDragController::EVENT_SOURCE_TOUCH :
233 TabDragController::EVENT_SOURCE_MOUSE;
236 } // namespace
238 ///////////////////////////////////////////////////////////////////////////////
239 // NewTabButton
241 // A subclass of button that hit-tests to the shape of the new tab button and
242 // does custom drawing.
244 class NewTabButton : public views::ImageButton,
245 public views::MaskedTargeterDelegate {
246 public:
247 NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener);
248 ~NewTabButton() override;
250 // Set the background offset used to match the background image to the frame
251 // image.
252 void set_background_offset(const gfx::Point& offset) {
253 background_offset_ = offset;
256 protected:
257 // views::View:
258 #if defined(OS_WIN)
259 void OnMouseReleased(const ui::MouseEvent& event) override;
260 #endif
261 void OnPaint(gfx::Canvas* canvas) override;
263 // ui::EventHandler:
264 void OnGestureEvent(ui::GestureEvent* event) override;
266 private:
267 // views::MaskedTargeterDelegate:
268 bool GetHitTestMask(gfx::Path* mask) const override;
270 bool ShouldWindowContentsBeTransparent() const;
271 gfx::ImageSkia GetBackgroundImage(views::CustomButton::ButtonState state,
272 float scale) const;
273 gfx::ImageSkia GetImageForState(views::CustomButton::ButtonState state,
274 float scale) const;
275 gfx::ImageSkia GetImageForScale(float scale) const;
277 // Tab strip that contains this button.
278 TabStrip* tab_strip_;
280 // The offset used to paint the background image.
281 gfx::Point background_offset_;
283 // were we destroyed?
284 bool* destroyed_;
286 DISALLOW_COPY_AND_ASSIGN(NewTabButton);
289 NewTabButton::NewTabButton(TabStrip* tab_strip, views::ButtonListener* listener)
290 : views::ImageButton(listener),
291 tab_strip_(tab_strip),
292 destroyed_(NULL) {
293 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
294 set_triggerable_event_flags(triggerable_event_flags() |
295 ui::EF_MIDDLE_MOUSE_BUTTON);
296 #endif
299 NewTabButton::~NewTabButton() {
300 if (destroyed_)
301 *destroyed_ = true;
304 #if defined(OS_WIN)
305 void NewTabButton::OnMouseReleased(const ui::MouseEvent& event) {
306 if (event.IsOnlyRightMouseButton()) {
307 gfx::Point point = event.location();
308 views::View::ConvertPointToScreen(this, &point);
309 point = gfx::win::DIPToScreenPoint(point);
310 bool destroyed = false;
311 destroyed_ = &destroyed;
312 gfx::ShowSystemMenuAtPoint(views::HWNDForView(this), point);
313 if (destroyed)
314 return;
316 destroyed_ = NULL;
317 SetState(views::CustomButton::STATE_NORMAL);
318 return;
320 views::ImageButton::OnMouseReleased(event);
322 #endif
324 void NewTabButton::OnPaint(gfx::Canvas* canvas) {
325 gfx::ImageSkia image = GetImageForScale(canvas->image_scale());
326 canvas->DrawImageInt(image, 0, height() - image.height());
329 void NewTabButton::OnGestureEvent(ui::GestureEvent* event) {
330 // Consume all gesture events here so that the parent (Tab) does not
331 // start consuming gestures.
332 views::ImageButton::OnGestureEvent(event);
333 event->SetHandled();
336 bool NewTabButton::GetHitTestMask(gfx::Path* mask) const {
337 DCHECK(mask);
339 // When the button is sized to the top of the tab strip, we want the hit
340 // test mask to be defined as the complete (rectangular) bounds of the
341 // button.
342 if (tab_strip_->SizeTabButtonToTopOfTabStrip()) {
343 gfx::Rect button_bounds(GetContentsBounds());
344 button_bounds.set_x(GetMirroredXForRect(button_bounds));
345 mask->addRect(RectToSkRect(button_bounds));
346 return true;
349 SkScalar w = SkIntToScalar(width());
350 SkScalar v_offset = SkIntToScalar(TabStrip::kNewTabButtonVerticalOffset);
352 // These values are defined by the shape of the new tab image. Should that
353 // image ever change, these values will need to be updated. They're so
354 // custom it's not really worth defining constants for.
355 // These values are correct for regular and USE_ASH versions of the image.
356 mask->moveTo(0, v_offset + 1);
357 mask->lineTo(w - 7, v_offset + 1);
358 mask->lineTo(w - 4, v_offset + 4);
359 mask->lineTo(w, v_offset + 16);
360 mask->lineTo(w - 1, v_offset + 17);
361 mask->lineTo(7, v_offset + 17);
362 mask->lineTo(4, v_offset + 13);
363 mask->lineTo(0, v_offset + 1);
364 mask->close();
366 return true;
369 bool NewTabButton::ShouldWindowContentsBeTransparent() const {
370 return GetWidget() &&
371 GetWidget()->GetTopLevelWidget()->ShouldWindowContentsBeTransparent();
374 gfx::ImageSkia NewTabButton::GetBackgroundImage(
375 views::CustomButton::ButtonState state,
376 float scale) const {
377 int background_id = 0;
378 if (ShouldWindowContentsBeTransparent()) {
379 background_id = IDR_THEME_TAB_BACKGROUND_V;
380 } else if (tab_strip_->controller()->IsIncognito()) {
381 background_id = IDR_THEME_TAB_BACKGROUND_INCOGNITO;
382 } else {
383 background_id = IDR_THEME_TAB_BACKGROUND;
386 int alpha = 0;
387 switch (state) {
388 case views::CustomButton::STATE_NORMAL:
389 case views::CustomButton::STATE_HOVERED:
390 alpha = ShouldWindowContentsBeTransparent() ? kGlassFrameInactiveTabAlpha
391 : 255;
392 break;
393 case views::CustomButton::STATE_PRESSED:
394 alpha = 145;
395 break;
396 default:
397 NOTREACHED();
398 break;
401 gfx::ImageSkia* mask =
402 GetThemeProvider()->GetImageSkiaNamed(IDR_NEWTAB_BUTTON_MASK);
403 int height = mask->height();
404 int width = mask->width();
405 // The canvas and mask has to use the same scale factor.
406 if (!mask->HasRepresentation(scale))
407 scale = ui::GetScaleForScaleFactor(ui::SCALE_FACTOR_100P);
409 gfx::Canvas canvas(gfx::Size(width, height), scale, false);
411 // For custom images the background starts at the top of the tab strip.
412 // Otherwise the background starts at the top of the frame.
413 gfx::ImageSkia* background =
414 GetThemeProvider()->GetImageSkiaNamed(background_id);
415 int offset_y = GetThemeProvider()->HasCustomImage(background_id) ?
416 0 : background_offset_.y();
418 // The new tab background is mirrored in RTL mode, but the theme background
419 // should never be mirrored. Mirror it here to compensate.
420 float x_scale = 1.0f;
421 int x = GetMirroredX() + background_offset_.x();
422 if (base::i18n::IsRTL()) {
423 x_scale = -1.0f;
424 // Offset by |width| such that the same region is painted as if there was no
425 // flip.
426 x += width;
428 canvas.TileImageInt(*background, x,
429 TabStrip::kNewTabButtonVerticalOffset + offset_y,
430 x_scale, 1.0f, 0, 0, width, height);
432 if (alpha != 255) {
433 SkPaint paint;
434 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
435 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
436 paint.setStyle(SkPaint::kFill_Style);
437 canvas.DrawRect(gfx::Rect(0, 0, width, height), paint);
440 // White highlight on hover.
441 if (state == views::CustomButton::STATE_HOVERED)
442 canvas.FillRect(GetLocalBounds(), SkColorSetARGB(64, 255, 255, 255));
444 return gfx::ImageSkiaOperations::CreateMaskedImage(
445 gfx::ImageSkia(canvas.ExtractImageRep()), *mask);
448 gfx::ImageSkia NewTabButton::GetImageForState(
449 views::CustomButton::ButtonState state,
450 float scale) const {
451 const int overlay_id = state == views::CustomButton::STATE_PRESSED ?
452 IDR_NEWTAB_BUTTON_P : IDR_NEWTAB_BUTTON;
453 gfx::ImageSkia* overlay = GetThemeProvider()->GetImageSkiaNamed(overlay_id);
455 gfx::Canvas canvas(
456 gfx::Size(overlay->width(), overlay->height()),
457 scale,
458 false);
459 canvas.DrawImageInt(GetBackgroundImage(state, scale), 0, 0);
461 // Draw the button border with a slight alpha.
462 const uint8_t kGlassFrameOverlayAlpha = 178;
463 const uint8_t kOpaqueFrameOverlayAlpha = 230;
464 uint8_t alpha = ShouldWindowContentsBeTransparent()
465 ? kGlassFrameOverlayAlpha
466 : kOpaqueFrameOverlayAlpha;
467 canvas.DrawImageInt(*overlay, 0, 0, alpha);
469 return gfx::ImageSkia(canvas.ExtractImageRep());
472 gfx::ImageSkia NewTabButton::GetImageForScale(float scale) const {
473 if (!hover_animation_->is_animating())
474 return GetImageForState(state(), scale);
475 return gfx::ImageSkiaOperations::CreateBlendedImage(
476 GetImageForState(views::CustomButton::STATE_NORMAL, scale),
477 GetImageForState(views::CustomButton::STATE_HOVERED, scale),
478 hover_animation_->GetCurrentValue());
481 ///////////////////////////////////////////////////////////////////////////////
482 // TabStrip::RemoveTabDelegate
484 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
485 // done.
486 class TabStrip::RemoveTabDelegate : public TabAnimationDelegate {
487 public:
488 RemoveTabDelegate(TabStrip* tab_strip, Tab* tab);
490 void AnimationEnded(const gfx::Animation* animation) override;
491 void AnimationCanceled(const gfx::Animation* animation) override;
493 private:
494 DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
497 TabStrip::RemoveTabDelegate::RemoveTabDelegate(TabStrip* tab_strip,
498 Tab* tab)
499 : TabAnimationDelegate(tab_strip, tab) {
502 void TabStrip::RemoveTabDelegate::AnimationEnded(
503 const gfx::Animation* animation) {
504 DCHECK(tab()->closing());
505 tab_strip()->RemoveAndDeleteTab(tab());
507 // Send the Container a message to simulate a mouse moved event at the current
508 // mouse position. This tickles the Tab the mouse is currently over to show
509 // the "hot" state of the close button. Note that this is not required (and
510 // indeed may crash!) for removes spawned by non-mouse closes and
511 // drag-detaches.
512 if (!tab_strip()->IsDragSessionActive() &&
513 tab_strip()->ShouldHighlightCloseButtonAfterRemove()) {
514 // The widget can apparently be null during shutdown.
515 views::Widget* widget = tab_strip()->GetWidget();
516 if (widget)
517 widget->SynthesizeMouseMoveEvent();
521 void TabStrip::RemoveTabDelegate::AnimationCanceled(
522 const gfx::Animation* animation) {
523 AnimationEnded(animation);
526 ///////////////////////////////////////////////////////////////////////////////
527 // TabStrip, public:
529 // static
530 const char TabStrip::kViewClassName[] = "TabStrip";
531 const int TabStrip::kNewTabButtonVerticalOffset = 7;
532 const int TabStrip::kNewTabButtonAssetWidth = 34;
533 const int TabStrip::kNewTabButtonAssetHeight = 18;
534 #if defined(OS_MACOSX)
535 const int TabStrip::kNewTabButtonHorizontalOffset = -8;
536 const int TabStrip::kPinnedToNonPinnedGap = 2;
537 #else
538 const int TabStrip::kNewTabButtonHorizontalOffset = -11;
539 const int TabStrip::kPinnedToNonPinnedGap = 3;
540 #endif
542 TabStrip::TabStrip(TabStripController* controller)
543 : controller_(controller),
544 newtab_button_(NULL),
545 current_unselected_width_(Tab::GetStandardSize().width()),
546 current_selected_width_(Tab::GetStandardSize().width()),
547 available_width_for_tabs_(-1),
548 in_tab_close_(false),
549 animation_container_(new gfx::AnimationContainer()),
550 bounds_animator_(this),
551 stacked_layout_(false),
552 adjust_layout_(false),
553 reset_to_shrink_on_exit_(false),
554 mouse_move_count_(0),
555 immersive_style_(false) {
556 Init();
557 SetEventTargeter(
558 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
561 TabStrip::~TabStrip() {
562 FOR_EACH_OBSERVER(TabStripObserver, observers_,
563 TabStripDeleted(this));
565 // The animations may reference the tabs. Shut down the animation before we
566 // delete the tabs.
567 StopAnimating(false);
569 DestroyDragController();
571 // Make sure we unhook ourselves as a message loop observer so that we don't
572 // crash in the case where the user closes the window after closing a tab
573 // but before moving the mouse.
574 RemoveMessageLoopObserver();
576 // The children (tabs) may callback to us from their destructor. Delete them
577 // so that if they call back we aren't in a weird state.
578 RemoveAllChildViews(true);
581 void TabStrip::AddObserver(TabStripObserver* observer) {
582 observers_.AddObserver(observer);
585 void TabStrip::RemoveObserver(TabStripObserver* observer) {
586 observers_.RemoveObserver(observer);
589 void TabStrip::SetStackedLayout(bool stacked_layout) {
590 if (stacked_layout == stacked_layout_)
591 return;
593 const int active_index = controller_->GetActiveIndex();
594 int active_center = 0;
595 if (active_index != -1) {
596 active_center = ideal_bounds(active_index).x() +
597 ideal_bounds(active_index).width() / 2;
599 stacked_layout_ = stacked_layout;
600 SetResetToShrinkOnExit(false);
601 SwapLayoutIfNecessary();
602 // When transitioning to stacked try to keep the active tab centered.
603 if (touch_layout_ && active_index != -1) {
604 touch_layout_->SetActiveTabLocation(
605 active_center - ideal_bounds(active_index).width() / 2);
606 AnimateToIdealBounds();
610 gfx::Rect TabStrip::GetNewTabButtonBounds() {
611 return newtab_button_->bounds();
614 bool TabStrip::SizeTabButtonToTopOfTabStrip() {
615 // Extend the button to the screen edge in maximized and immersive fullscreen.
616 views::Widget* widget = GetWidget();
617 return browser_defaults::kSizeTabButtonToTopOfTabStrip ||
618 (widget && (widget->IsMaximized() || widget->IsFullscreen()));
621 void TabStrip::StartHighlight(int model_index) {
622 tab_at(model_index)->StartPulse();
625 void TabStrip::StopAllHighlighting() {
626 for (int i = 0; i < tab_count(); ++i)
627 tab_at(i)->StopPulse();
630 void TabStrip::AddTabAt(int model_index,
631 const TabRendererData& data,
632 bool is_active) {
633 Tab* tab = CreateTab();
634 AddChildView(tab);
635 tab->SetData(data);
636 UpdateTabsClosingMap(model_index, 1);
637 tabs_.Add(tab, model_index);
639 if (touch_layout_) {
640 GenerateIdealBoundsForPinnedTabs(NULL);
641 int add_types = 0;
642 if (data.pinned)
643 add_types |= StackedTabStripLayout::kAddTypePinned;
644 if (is_active)
645 add_types |= StackedTabStripLayout::kAddTypeActive;
646 touch_layout_->AddTab(model_index, add_types, GetStartXForNormalTabs());
649 // Don't animate the first tab, it looks weird, and don't animate anything
650 // if the containing window isn't visible yet.
651 if (tab_count() > 1 && GetWidget() && GetWidget()->IsVisible())
652 StartInsertTabAnimation(model_index);
653 else
654 DoLayout();
656 SwapLayoutIfNecessary();
658 FOR_EACH_OBSERVER(TabStripObserver, observers_,
659 TabStripAddedTabAt(this, model_index));
661 // Stop dragging when a new tab is added and dragging a window. Doing
662 // otherwise results in a confusing state if the user attempts to reattach. We
663 // could allow this and make TabDragController update itself during the add,
664 // but this comes up infrequently enough that it's not worth the complexity.
666 // At the start of AddTabAt() the model and tabs are out sync. Any queries to
667 // find a tab given a model index can go off the end of |tabs_|. As such, it
668 // is important that we complete the drag *after* adding the tab so that the
669 // model and tabstrip are in sync.
670 if (drag_controller_.get() && !drag_controller_->is_mutating() &&
671 drag_controller_->is_dragging_window()) {
672 EndDrag(END_DRAG_COMPLETE);
676 void TabStrip::MoveTab(int from_model_index,
677 int to_model_index,
678 const TabRendererData& data) {
679 DCHECK_GT(tabs_.view_size(), 0);
680 const Tab* last_tab = GetLastVisibleTab();
681 tab_at(from_model_index)->SetData(data);
682 if (touch_layout_) {
683 tabs_.MoveViewOnly(from_model_index, to_model_index);
684 int pinned_count = 0;
685 GenerateIdealBoundsForPinnedTabs(&pinned_count);
686 touch_layout_->MoveTab(
687 from_model_index, to_model_index, controller_->GetActiveIndex(),
688 GetStartXForNormalTabs(), pinned_count);
689 } else {
690 tabs_.Move(from_model_index, to_model_index);
692 StartMoveTabAnimation();
693 if (TabDragController::IsAttachedTo(this) &&
694 (last_tab != GetLastVisibleTab() || last_tab->dragging())) {
695 newtab_button_->SetVisible(false);
697 SwapLayoutIfNecessary();
699 FOR_EACH_OBSERVER(TabStripObserver, observers_,
700 TabStripMovedTab(this, from_model_index, to_model_index));
703 void TabStrip::RemoveTabAt(int model_index) {
704 if (touch_layout_) {
705 Tab* tab = tab_at(model_index);
706 tab->set_closing(true);
707 int old_x = tabs_.ideal_bounds(model_index).x();
708 // We still need to paint the tab until we actually remove it. Put it in
709 // tabs_closing_map_ so we can find it.
710 RemoveTabFromViewModel(model_index);
711 touch_layout_->RemoveTab(model_index,
712 GenerateIdealBoundsForPinnedTabs(NULL), old_x);
713 ScheduleRemoveTabAnimation(tab);
714 } else if (in_tab_close_ && model_index != GetModelCount()) {
715 StartMouseInitiatedRemoveTabAnimation(model_index);
716 } else {
717 StartRemoveTabAnimation(model_index);
719 SwapLayoutIfNecessary();
721 FOR_EACH_OBSERVER(TabStripObserver, observers_,
722 TabStripRemovedTabAt(this, model_index));
725 void TabStrip::SetTabData(int model_index, const TabRendererData& data) {
726 Tab* tab = tab_at(model_index);
727 bool pinned_state_changed = tab->data().pinned != data.pinned;
728 tab->SetData(data);
730 if (pinned_state_changed) {
731 if (touch_layout_) {
732 int pinned_tab_count = 0;
733 int start_x = GenerateIdealBoundsForPinnedTabs(&pinned_tab_count);
734 touch_layout_->SetXAndPinnedCount(start_x, pinned_tab_count);
736 if (GetWidget() && GetWidget()->IsVisible())
737 StartPinnedTabAnimation();
738 else
739 DoLayout();
741 SwapLayoutIfNecessary();
744 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
745 // Detached tabs should always be invisible (as they close).
746 if (tab->detached())
747 return false;
749 // When stacking tabs, all tabs should always be visible.
750 if (stacked_layout_)
751 return true;
753 // If the tab is currently clipped, it shouldn't be visible. Note that we
754 // allow dragged tabs to draw over the "New Tab button" region as well,
755 // because either the New Tab button will be hidden, or the dragged tabs will
756 // be animating back to their normal positions and we don't want to hide them
757 // in the New Tab button region in case they re-appear after leaving it.
758 // (This prevents flickeriness.) We never draw non-dragged tabs in New Tab
759 // button area, even when the button is invisible, so that they don't appear
760 // to "pop in" when the button disappears.
761 // TODO: Probably doesn't work for RTL
762 int right_edge = tab->bounds().right();
763 const int visible_width = tab->dragging() ? width() : tab_area_width();
764 if (right_edge > visible_width)
765 return false;
767 // Non-clipped dragging tabs should always be visible.
768 if (tab->dragging())
769 return true;
771 // Let all non-clipped closing tabs be visible. These will probably finish
772 // closing before the user changes the active tab, so there's little reason to
773 // try and make the more complex logic below apply.
774 if (tab->closing())
775 return true;
777 // Now we need to check whether the tab isn't currently clipped, but could
778 // become clipped if we changed the active tab, widening either this tab or
779 // the tabstrip portion before it.
781 // Pinned tabs don't change size when activated, so any tab in the pinned tab
782 // region is safe.
783 if (tab->data().pinned)
784 return true;
786 // If the active tab is on or before this tab, we're safe.
787 if (controller_->GetActiveIndex() <= GetModelIndexOfTab(tab))
788 return true;
790 // We need to check what would happen if the active tab were to move to this
791 // tab or before.
792 return (right_edge + current_selected_width_ - current_unselected_width_) <=
793 tab_area_width();
796 void TabStrip::PrepareForCloseAt(int model_index, CloseTabSource source) {
797 if (!in_tab_close_ && IsAnimating()) {
798 // Cancel any current animations. We do this as remove uses the current
799 // ideal bounds and we need to know ideal bounds is in a good state.
800 StopAnimating(true);
803 if (!GetWidget())
804 return;
806 int model_count = GetModelCount();
807 if (model_count > 1 && model_index != model_count - 1) {
808 // The user is about to close a tab other than the last tab. Set
809 // available_width_for_tabs_ so that if we do a layout we don't position a
810 // tab past the end of the second to last tab. We do this so that as the
811 // user closes tabs with the mouse a tab continues to fall under the mouse.
812 Tab* last_tab = tab_at(model_count - 1);
813 Tab* tab_being_removed = tab_at(model_index);
814 available_width_for_tabs_ = last_tab->x() + last_tab->width() -
815 tab_being_removed->width() - kTabHorizontalOffset;
816 if (model_index == 0 && tab_being_removed->data().pinned &&
817 !tab_at(1)->data().pinned) {
818 available_width_for_tabs_ -= kPinnedToNonPinnedGap;
822 in_tab_close_ = true;
823 resize_layout_timer_.Stop();
824 if (source == CLOSE_TAB_FROM_TOUCH) {
825 StartResizeLayoutTabsFromTouchTimer();
826 } else {
827 AddMessageLoopObserver();
831 void TabStrip::SetSelection(const ui::ListSelectionModel& old_selection,
832 const ui::ListSelectionModel& new_selection) {
833 if (old_selection.active() != new_selection.active()) {
834 if (old_selection.active() >= 0)
835 tab_at(old_selection.active())->ActiveStateChanged();
836 if (new_selection.active() >= 0)
837 tab_at(new_selection.active())->ActiveStateChanged();
840 if (touch_layout_) {
841 touch_layout_->SetActiveIndex(new_selection.active());
842 // Only start an animation if we need to. Otherwise clicking on an
843 // unselected tab and dragging won't work because dragging is only allowed
844 // if not animating.
845 if (!views::ViewModelUtils::IsAtIdealBounds(tabs_))
846 AnimateToIdealBounds();
847 SchedulePaint();
848 } else {
849 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are
850 // a different size to the selected ones.
851 bool tiny_tabs = current_unselected_width_ != current_selected_width_;
852 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) {
853 DoLayout();
854 } else {
855 SchedulePaint();
859 // Use STLSetDifference to get the indices of elements newly selected
860 // and no longer selected, since selected_indices() is always sorted.
861 ui::ListSelectionModel::SelectedIndices no_longer_selected =
862 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
863 old_selection.selected_indices(),
864 new_selection.selected_indices());
865 ui::ListSelectionModel::SelectedIndices newly_selected =
866 base::STLSetDifference<ui::ListSelectionModel::SelectedIndices>(
867 new_selection.selected_indices(),
868 old_selection.selected_indices());
870 // Fire accessibility events that reflect the changes to selection, and
871 // stop the pinned tab title animation on tabs no longer selected.
872 for (size_t i = 0; i < no_longer_selected.size(); ++i) {
873 tab_at(no_longer_selected[i])->StopPinnedTabTitleAnimation();
874 tab_at(no_longer_selected[i])->NotifyAccessibilityEvent(
875 ui::AX_EVENT_SELECTION_REMOVE, true);
877 for (size_t i = 0; i < newly_selected.size(); ++i) {
878 tab_at(newly_selected[i])->NotifyAccessibilityEvent(
879 ui::AX_EVENT_SELECTION_ADD, true);
881 tab_at(new_selection.active())->NotifyAccessibilityEvent(
882 ui::AX_EVENT_SELECTION, true);
885 void TabStrip::TabTitleChangedNotLoading(int model_index) {
886 Tab* tab = tab_at(model_index);
887 if (tab->data().pinned && !tab->IsActive())
888 tab->StartPinnedTabTitleAnimation();
891 int TabStrip::GetModelIndexOfTab(const Tab* tab) const {
892 return tabs_.GetIndexOfView(tab);
895 int TabStrip::GetModelCount() const {
896 return controller_->GetCount();
899 bool TabStrip::IsValidModelIndex(int model_index) const {
900 return controller_->IsValidIndex(model_index);
903 bool TabStrip::IsDragSessionActive() const {
904 return drag_controller_.get() != NULL;
907 bool TabStrip::IsActiveDropTarget() const {
908 for (int i = 0; i < tab_count(); ++i) {
909 Tab* tab = tab_at(i);
910 if (tab->dragging())
911 return true;
913 return false;
916 bool TabStrip::IsTabStripEditable() const {
917 return !IsDragSessionActive() && !IsActiveDropTarget();
920 bool TabStrip::IsTabStripCloseable() const {
921 return !IsDragSessionActive();
924 void TabStrip::UpdateLoadingAnimations() {
925 controller_->UpdateLoadingAnimations();
928 bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) {
929 return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
932 bool TabStrip::IsRectInWindowCaption(const gfx::Rect& rect) {
933 views::View* v = GetEventHandlerForRect(rect);
935 // If there is no control at this location, claim the hit was in the title
936 // bar to get a move action.
937 if (v == this)
938 return true;
940 // Check to see if the rect intersects the non-button parts of the new tab
941 // button. The button has a non-rectangular shape, so if it's not in the
942 // visual portions of the button we treat it as a click to the caption.
943 gfx::RectF rect_in_newtab_coords_f(rect);
944 View::ConvertRectToTarget(this, newtab_button_, &rect_in_newtab_coords_f);
945 gfx::Rect rect_in_newtab_coords = gfx::ToEnclosingRect(
946 rect_in_newtab_coords_f);
947 if (newtab_button_->GetLocalBounds().Intersects(rect_in_newtab_coords) &&
948 !newtab_button_->HitTestRect(rect_in_newtab_coords))
949 return true;
951 // All other regions, including the new Tab button, should be considered part
952 // of the containing Window's client area so that regular events can be
953 // processed for them.
954 return false;
957 void TabStrip::SetBackgroundOffset(const gfx::Point& offset) {
958 for (int i = 0; i < tab_count(); ++i)
959 tab_at(i)->set_background_offset(offset);
960 newtab_button_->set_background_offset(offset);
963 void TabStrip::SetImmersiveStyle(bool enable) {
964 if (immersive_style_ == enable)
965 return;
966 immersive_style_ = enable;
969 bool TabStrip::IsAnimating() const {
970 return bounds_animator_.IsAnimating();
973 void TabStrip::StopAnimating(bool layout) {
974 if (!IsAnimating())
975 return;
977 bounds_animator_.Cancel();
979 if (layout)
980 DoLayout();
983 void TabStrip::FileSupported(const GURL& url, bool supported) {
984 if (drop_info_.get() && drop_info_->url == url)
985 drop_info_->file_supported = supported;
988 const ui::ListSelectionModel& TabStrip::GetSelectionModel() {
989 return controller_->GetSelectionModel();
992 bool TabStrip::SupportsMultipleSelection() {
993 // TODO: currently only allow single selection in touch layout mode.
994 return touch_layout_ == NULL;
997 bool TabStrip::ShouldHideCloseButtonForInactiveTabs() {
998 if (!touch_layout_)
999 return false;
1001 return !base::CommandLine::ForCurrentProcess()->HasSwitch(
1002 switches::kDisableHideInactiveStackedTabCloseButtons);
1005 void TabStrip::SelectTab(Tab* tab) {
1006 int model_index = GetModelIndexOfTab(tab);
1007 if (IsValidModelIndex(model_index))
1008 controller_->SelectTab(model_index);
1011 void TabStrip::ExtendSelectionTo(Tab* tab) {
1012 int model_index = GetModelIndexOfTab(tab);
1013 if (IsValidModelIndex(model_index))
1014 controller_->ExtendSelectionTo(model_index);
1017 void TabStrip::ToggleSelected(Tab* tab) {
1018 int model_index = GetModelIndexOfTab(tab);
1019 if (IsValidModelIndex(model_index))
1020 controller_->ToggleSelected(model_index);
1023 void TabStrip::AddSelectionFromAnchorTo(Tab* tab) {
1024 int model_index = GetModelIndexOfTab(tab);
1025 if (IsValidModelIndex(model_index))
1026 controller_->AddSelectionFromAnchorTo(model_index);
1029 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
1030 if (tab->closing()) {
1031 // If the tab is already closing, close the next tab. We do this so that the
1032 // user can rapidly close tabs by clicking the close button and not have
1033 // the animations interfere with that.
1034 const int closed_tab_index = FindClosingTab(tab).first->first;
1035 if (closed_tab_index < GetModelCount())
1036 controller_->CloseTab(closed_tab_index, source);
1037 return;
1039 int model_index = GetModelIndexOfTab(tab);
1040 if (IsValidModelIndex(model_index))
1041 controller_->CloseTab(model_index, source);
1044 void TabStrip::ToggleTabAudioMute(Tab* tab) {
1045 int model_index = GetModelIndexOfTab(tab);
1046 if (IsValidModelIndex(model_index))
1047 controller_->ToggleTabAudioMute(model_index);
1050 void TabStrip::ShowContextMenuForTab(Tab* tab,
1051 const gfx::Point& p,
1052 ui::MenuSourceType source_type) {
1053 controller_->ShowContextMenuForTab(tab, p, source_type);
1056 bool TabStrip::IsActiveTab(const Tab* tab) const {
1057 int model_index = GetModelIndexOfTab(tab);
1058 return IsValidModelIndex(model_index) &&
1059 controller_->IsActiveTab(model_index);
1062 bool TabStrip::IsTabSelected(const Tab* tab) const {
1063 int model_index = GetModelIndexOfTab(tab);
1064 return IsValidModelIndex(model_index) &&
1065 controller_->IsTabSelected(model_index);
1068 bool TabStrip::IsTabPinned(const Tab* tab) const {
1069 if (tab->closing())
1070 return false;
1072 int model_index = GetModelIndexOfTab(tab);
1073 return IsValidModelIndex(model_index) &&
1074 controller_->IsTabPinned(model_index);
1077 void TabStrip::MaybeStartDrag(
1078 Tab* tab,
1079 const ui::LocatedEvent& event,
1080 const ui::ListSelectionModel& original_selection) {
1081 // Don't accidentally start any drag operations during animations if the
1082 // mouse is down... during an animation tabs are being resized automatically,
1083 // so the View system can misinterpret this easily if the mouse is down that
1084 // the user is dragging.
1085 if (IsAnimating() || tab->closing() ||
1086 controller_->HasAvailableDragActions() == 0) {
1087 return;
1090 // Do not do any dragging of tabs when using the super short immersive style.
1091 if (IsImmersiveStyle())
1092 return;
1094 int model_index = GetModelIndexOfTab(tab);
1095 if (!IsValidModelIndex(model_index)) {
1096 CHECK(false);
1097 return;
1099 Tabs tabs;
1100 int size_to_selected = 0;
1101 int x = tab->GetMirroredXInView(event.x());
1102 int y = event.y();
1103 // Build the set of selected tabs to drag and calculate the offset from the
1104 // first selected tab.
1105 for (int i = 0; i < tab_count(); ++i) {
1106 Tab* other_tab = tab_at(i);
1107 if (IsTabSelected(other_tab)) {
1108 tabs.push_back(other_tab);
1109 if (other_tab == tab) {
1110 size_to_selected = GetSizeNeededForTabs(tabs);
1111 x = size_to_selected - tab->width() + x;
1115 DCHECK(!tabs.empty());
1116 DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
1117 ui::ListSelectionModel selection_model;
1118 if (!original_selection.IsSelected(model_index))
1119 selection_model.Copy(original_selection);
1120 // Delete the existing DragController before creating a new one. We do this as
1121 // creating the DragController remembers the WebContents delegates and we need
1122 // to make sure the existing DragController isn't still a delegate.
1123 drag_controller_.reset();
1124 TabDragController::MoveBehavior move_behavior =
1125 TabDragController::REORDER;
1126 // Use MOVE_VISIBILE_TABS in the following conditions:
1127 // . Mouse event generated from touch and the left button is down (the right
1128 // button corresponds to a long press, which we want to reorder).
1129 // . Gesture tap down and control key isn't down.
1130 // . Real mouse event and control is down. This is mostly for testing.
1131 DCHECK(event.type() == ui::ET_MOUSE_PRESSED ||
1132 event.type() == ui::ET_GESTURE_TAP_DOWN);
1133 if (touch_layout_ &&
1134 ((event.type() == ui::ET_MOUSE_PRESSED &&
1135 (((event.flags() & ui::EF_FROM_TOUCH) &&
1136 static_cast<const ui::MouseEvent&>(event).IsLeftMouseButton()) ||
1137 (!(event.flags() & ui::EF_FROM_TOUCH) &&
1138 static_cast<const ui::MouseEvent&>(event).IsControlDown()))) ||
1139 (event.type() == ui::ET_GESTURE_TAP_DOWN && !event.IsControlDown()))) {
1140 move_behavior = TabDragController::MOVE_VISIBILE_TABS;
1143 drag_controller_.reset(new TabDragController);
1144 drag_controller_->Init(
1145 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model,
1146 move_behavior, EventSourceFromEvent(event));
1149 void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
1150 if (drag_controller_.get() &&
1151 drag_controller_->event_source() == EventSourceFromEvent(event)) {
1152 gfx::Point screen_location(event.location());
1153 views::View::ConvertPointToScreen(view, &screen_location);
1154 drag_controller_->Drag(screen_location);
1158 bool TabStrip::EndDrag(EndDragReason reason) {
1159 if (!drag_controller_.get())
1160 return false;
1161 bool started_drag = drag_controller_->started_drag();
1162 drag_controller_->EndDrag(reason);
1163 return started_drag;
1166 Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) {
1167 gfx::Point local_point = tab_in_tab_coordinates;
1168 ConvertPointToTarget(tab, this, &local_point);
1170 views::View* view = GetEventHandlerForPoint(local_point);
1171 if (!view)
1172 return NULL; // No tab contains the point.
1174 // Walk up the view hierarchy until we find a tab, or the TabStrip.
1175 while (view && view != this && view->id() != VIEW_ID_TAB)
1176 view = view->parent();
1178 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL;
1181 void TabStrip::OnMouseEventInTab(views::View* source,
1182 const ui::MouseEvent& event) {
1183 UpdateStackedLayoutFromMouseEvent(source, event);
1186 bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) {
1187 // Only touch layout needs to restrict the clip.
1188 if (!touch_layout_ && !IsStackingDraggedTabs())
1189 return true;
1191 int index = GetModelIndexOfTab(tab);
1192 if (index == -1)
1193 return true; // Tab is closing, paint it all.
1195 int active_index = IsStackingDraggedTabs() ?
1196 controller_->GetActiveIndex() : touch_layout_->active_index();
1197 if (active_index == tab_count())
1198 active_index--;
1200 if (index < active_index) {
1201 if (tab_at(index)->x() == tab_at(index + 1)->x())
1202 return false;
1204 if (tab_at(index)->x() > tab_at(index + 1)->x())
1205 return true; // Can happen during dragging.
1207 clip->SetRect(
1208 0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + kStackedTabLeftClip,
1209 tab_at(index)->height());
1210 } else if (index > active_index && index > 0) {
1211 const gfx::Rect& tab_bounds(tab_at(index)->bounds());
1212 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds());
1213 if (tab_bounds.x() == previous_tab_bounds.x())
1214 return false;
1216 if (tab_bounds.x() < previous_tab_bounds.x())
1217 return true; // Can happen during dragging.
1219 if (previous_tab_bounds.right() + kTabHorizontalOffset != tab_bounds.x()) {
1220 int x = previous_tab_bounds.right() - tab_bounds.x() -
1221 kStackedTabRightClip;
1222 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height());
1225 return true;
1228 bool TabStrip::IsImmersiveStyle() const {
1229 return immersive_style_;
1232 void TabStrip::UpdateTabAccessibilityState(const Tab* tab,
1233 ui::AXViewState* state) {
1234 state->count = tab_count();
1235 state->index = GetModelIndexOfTab(tab);
1238 void TabStrip::MouseMovedOutOfHost() {
1239 ResizeLayoutTabs();
1240 if (reset_to_shrink_on_exit_) {
1241 reset_to_shrink_on_exit_ = false;
1242 SetStackedLayout(false);
1243 controller_->StackedLayoutMaybeChanged();
1247 ///////////////////////////////////////////////////////////////////////////////
1248 // TabStrip, views::View overrides:
1250 void TabStrip::Layout() {
1251 // Only do a layout if our size changed.
1252 if (last_layout_size_ == size())
1253 return;
1254 if (IsDragSessionActive())
1255 return;
1256 DoLayout();
1259 void TabStrip::PaintChildren(const ui::PaintContext& context) {
1260 // The view order doesn't match the paint order (tabs_ contains the tab
1261 // ordering). Additionally we need to paint the tabs that are closing in
1262 // |tabs_closing_map_|.
1263 Tab* active_tab = NULL;
1264 Tabs tabs_dragging;
1265 Tabs selected_tabs;
1266 int selected_tab_count = 0;
1267 bool is_dragging = false;
1268 int active_tab_index = -1;
1270 const chrome::HostDesktopType host_desktop_type =
1271 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView());
1272 const uint8_t inactive_tab_alpha =
1273 (host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH)
1274 ? kInactiveTabAndNewTabButtonAlphaAsh
1275 : kInactiveTabAndNewTabButtonAlpha;
1278 ui::CompositingRecorder opacity_recorder(context, inactive_tab_alpha);
1280 PaintClosingTabs(tab_count(), context);
1282 for (int i = tab_count() - 1; i >= 0; --i) {
1283 Tab* tab = tab_at(i);
1284 if (tab->IsSelected())
1285 selected_tab_count++;
1286 if (tab->dragging() && !stacked_layout_) {
1287 is_dragging = true;
1288 if (tab->IsActive()) {
1289 active_tab = tab;
1290 active_tab_index = i;
1291 } else {
1292 tabs_dragging.push_back(tab);
1294 } else if (!tab->IsActive()) {
1295 if (!tab->IsSelected()) {
1296 if (!stacked_layout_)
1297 tab->Paint(context);
1298 } else {
1299 selected_tabs.push_back(tab);
1301 } else {
1302 active_tab = tab;
1303 active_tab_index = i;
1305 PaintClosingTabs(i, context);
1308 // Draw from the left and then the right if we're in touch mode.
1309 if (stacked_layout_ && active_tab_index >= 0) {
1310 for (int i = 0; i < active_tab_index; ++i) {
1311 Tab* tab = tab_at(i);
1312 tab->Paint(context);
1315 for (int i = tab_count() - 1; i > active_tab_index; --i) {
1316 Tab* tab = tab_at(i);
1317 tab->Paint(context);
1322 if (GetWidget()->ShouldWindowContentsBeTransparent()) {
1323 ui::PaintRecorder recorder(context, size());
1324 // Make sure non-active tabs are somewhat transparent.
1325 SkPaint paint;
1326 // If there are multiple tabs selected, fade non-selected tabs more to make
1327 // the selected tabs more noticable.
1328 uint8_t alpha = selected_tab_count > 1
1329 ? kGlassFrameInactiveTabAlphaMultiSelection
1330 : kGlassFrameInactiveTabAlpha;
1331 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255));
1332 paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
1333 paint.setStyle(SkPaint::kFill_Style);
1335 // The tab graphics include some shadows at the top, so the actual
1336 // tabstrip top is 4 px. above the apparent top of the tab, to provide room
1337 // to draw these. Exclude this region when trying to make tabs transparent
1338 // as it's transparent enough already, and drawing in this region can
1339 // overlap the avatar button, leading to visual artifacts.
1340 const int kTopOffset = 4;
1341 // The tabstrip area overlaps the toolbar area by 2 px.
1342 recorder.canvas()->DrawRect(
1343 gfx::Rect(0, kTopOffset, width(), height() - kTopOffset - 2), paint);
1346 // Now selected but not active. We don't want these dimmed if using native
1347 // frame, so they're painted after initial pass.
1348 for (size_t i = 0; i < selected_tabs.size(); ++i)
1349 selected_tabs[i]->Paint(context);
1351 // Next comes the active tab.
1352 if (active_tab && !is_dragging)
1353 active_tab->Paint(context);
1355 // Paint the New Tab button.
1357 ui::CompositingRecorder opacity_recorder(context, inactive_tab_alpha);
1358 newtab_button_->Paint(context);
1361 // And the dragged tabs.
1362 for (size_t i = 0; i < tabs_dragging.size(); ++i)
1363 tabs_dragging[i]->Paint(context);
1365 // If the active tab is being dragged, it goes last.
1366 if (active_tab && is_dragging)
1367 active_tab->Paint(context);
1370 const char* TabStrip::GetClassName() const {
1371 return kViewClassName;
1374 gfx::Size TabStrip::GetPreferredSize() const {
1375 int needed_tab_width;
1376 if (touch_layout_ || adjust_layout_) {
1377 // For stacked tabs the minimum size is calculated as the size needed to
1378 // handle showing any number of tabs.
1379 needed_tab_width =
1380 Tab::GetTouchWidth() + (2 * kStackedPadding * kMaxStackedCount);
1381 } else {
1382 // Otherwise the minimum width is based on the actual number of tabs.
1383 const int pinned_tab_count = GetPinnedTabCount();
1384 needed_tab_width = pinned_tab_count * Tab::GetPinnedWidth();
1385 const int remaining_tab_count = tab_count() - pinned_tab_count;
1386 const int min_selected_width = Tab::GetMinimumSelectedSize().width();
1387 const int min_unselected_width = Tab::GetMinimumUnselectedSize().width();
1388 if (remaining_tab_count > 0) {
1389 needed_tab_width += kPinnedToNonPinnedGap + min_selected_width +
1390 ((remaining_tab_count - 1) * min_unselected_width);
1392 if (tab_count() > 1)
1393 needed_tab_width += (tab_count() - 1) * kTabHorizontalOffset;
1395 // Don't let the tabstrip shrink smaller than is necessary to show one tab,
1396 // and don't force it to be larger than is necessary to show 20 tabs.
1397 const int largest_min_tab_width =
1398 min_selected_width + 19 * (min_unselected_width + kTabHorizontalOffset);
1399 needed_tab_width = std::min(
1400 std::max(needed_tab_width, min_selected_width), largest_min_tab_width);
1402 return gfx::Size(
1403 needed_tab_width + new_tab_button_width(),
1404 immersive_style_ ?
1405 Tab::GetImmersiveHeight() : Tab::GetMinimumUnselectedSize().height());
1408 void TabStrip::OnDragEntered(const DropTargetEvent& event) {
1409 // Force animations to stop, otherwise it makes the index calculation tricky.
1410 StopAnimating(true);
1412 UpdateDropIndex(event);
1414 GURL url;
1415 base::string16 title;
1417 // Check whether the event data includes supported drop data.
1418 if (event.data().GetURLAndTitle(
1419 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) &&
1420 url.is_valid()) {
1421 drop_info_->url = url;
1423 // For file:// URLs, kick off a MIME type request in case they're dropped.
1424 if (url.SchemeIsFile())
1425 controller()->CheckFileSupported(url);
1429 int TabStrip::OnDragUpdated(const DropTargetEvent& event) {
1430 // Update the drop index even if the file is unsupported, to allow
1431 // dragging a file to the contents of another tab.
1432 UpdateDropIndex(event);
1434 if (!drop_info_->file_supported)
1435 return ui::DragDropTypes::DRAG_NONE;
1437 return GetDropEffect(event);
1440 void TabStrip::OnDragExited() {
1441 SetDropIndex(-1, false);
1444 int TabStrip::OnPerformDrop(const DropTargetEvent& event) {
1445 if (!drop_info_.get())
1446 return ui::DragDropTypes::DRAG_NONE;
1448 const int drop_index = drop_info_->drop_index;
1449 const bool drop_before = drop_info_->drop_before;
1450 const bool file_supported = drop_info_->file_supported;
1452 // Hide the drop indicator.
1453 SetDropIndex(-1, false);
1455 // Do nothing if the file was unsupported or the URL is invalid. The URL may
1456 // have been changed after |drop_info_| was created.
1457 GURL url;
1458 base::string16 title;
1459 if (!file_supported ||
1460 !event.data().GetURLAndTitle(
1461 ui::OSExchangeData::CONVERT_FILENAMES, &url, &title) ||
1462 !url.is_valid())
1463 return ui::DragDropTypes::DRAG_NONE;
1465 controller()->PerformDrop(drop_before, drop_index, url);
1467 return GetDropEffect(event);
1470 void TabStrip::GetAccessibleState(ui::AXViewState* state) {
1471 state->role = ui::AX_ROLE_TAB_LIST;
1474 views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) {
1475 if (!HitTestPoint(point))
1476 return NULL;
1478 if (!touch_layout_) {
1479 // Return any view that isn't a Tab or this TabStrip immediately. We don't
1480 // want to interfere.
1481 views::View* v = View::GetTooltipHandlerForPoint(point);
1482 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
1483 return v;
1485 views::View* tab = FindTabHitByPoint(point);
1486 if (tab)
1487 return tab;
1488 } else {
1489 if (newtab_button_->visible()) {
1490 views::View* view =
1491 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point);
1492 if (view)
1493 return view;
1495 Tab* tab = FindTabForEvent(point);
1496 if (tab)
1497 return ConvertPointToViewAndGetTooltipHandler(this, tab, point);
1499 return this;
1502 // static
1503 int TabStrip::GetImmersiveHeight() {
1504 return Tab::GetImmersiveHeight();
1507 ///////////////////////////////////////////////////////////////////////////////
1508 // TabStrip, private:
1510 void TabStrip::Init() {
1511 set_id(VIEW_ID_TAB_STRIP);
1512 // So we get enter/exit on children to switch stacked layout on and off.
1513 set_notify_enter_exit_on_child(true);
1514 newtab_button_bounds_.SetRect(0,
1516 kNewTabButtonAssetWidth,
1517 kNewTabButtonAssetHeight +
1518 kNewTabButtonVerticalOffset);
1519 newtab_button_ = new NewTabButton(this, this);
1520 newtab_button_->SetTooltipText(
1521 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB));
1522 newtab_button_->SetAccessibleName(
1523 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB));
1524 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT,
1525 views::ImageButton::ALIGN_BOTTOM);
1526 newtab_button_->SetEventTargeter(
1527 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(newtab_button_)));
1528 AddChildView(newtab_button_);
1530 if (drop_indicator_width == 0) {
1531 // Direction doesn't matter, both images are the same size.
1532 gfx::ImageSkia* drop_image = GetDropArrowImage(true);
1533 drop_indicator_width = drop_image->width();
1534 drop_indicator_height = drop_image->height();
1538 Tab* TabStrip::CreateTab() {
1539 Tab* tab = new Tab(this);
1540 tab->set_animation_container(animation_container_.get());
1541 return tab;
1544 void TabStrip::StartInsertTabAnimation(int model_index) {
1545 PrepareForAnimation();
1547 // The TabStrip can now use its entire width to lay out Tabs.
1548 in_tab_close_ = false;
1549 available_width_for_tabs_ = -1;
1551 GenerateIdealBounds();
1553 Tab* tab = tab_at(model_index);
1554 if (model_index == 0) {
1555 tab->SetBounds(0, ideal_bounds(model_index).y(), 0,
1556 ideal_bounds(model_index).height());
1557 } else {
1558 Tab* last_tab = tab_at(model_index - 1);
1559 tab->SetBounds(last_tab->bounds().right() + kTabHorizontalOffset,
1560 ideal_bounds(model_index).y(), 0,
1561 ideal_bounds(model_index).height());
1564 AnimateToIdealBounds();
1567 void TabStrip::StartMoveTabAnimation() {
1568 PrepareForAnimation();
1569 GenerateIdealBounds();
1570 AnimateToIdealBounds();
1573 void TabStrip::StartRemoveTabAnimation(int model_index) {
1574 PrepareForAnimation();
1576 // Mark the tab as closing.
1577 Tab* tab = tab_at(model_index);
1578 tab->set_closing(true);
1580 RemoveTabFromViewModel(model_index);
1582 ScheduleRemoveTabAnimation(tab);
1585 void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) {
1586 // Start an animation for the tabs.
1587 GenerateIdealBounds();
1588 AnimateToIdealBounds();
1590 // Animate the tab being closed to zero width.
1591 gfx::Rect tab_bounds = tab->bounds();
1592 tab_bounds.set_width(0);
1593 bounds_animator_.AnimateViewTo(tab, tab_bounds);
1594 bounds_animator_.SetAnimationDelegate(
1595 tab,
1596 scoped_ptr<gfx::AnimationDelegate>(new RemoveTabDelegate(this, tab)));
1598 // Don't animate the new tab button when dragging tabs. Otherwise it looks
1599 // like the new tab button magically appears from beyond the end of the tab
1600 // strip.
1601 if (TabDragController::IsAttachedTo(this)) {
1602 bounds_animator_.StopAnimatingView(newtab_button_);
1603 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1607 void TabStrip::AnimateToIdealBounds() {
1608 for (int i = 0; i < tab_count(); ++i) {
1609 Tab* tab = tab_at(i);
1610 if (!tab->dragging()) {
1611 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i));
1612 bounds_animator_.SetAnimationDelegate(
1613 tab,
1614 scoped_ptr<gfx::AnimationDelegate>(
1615 new TabAnimationDelegate(this, tab)));
1619 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_);
1622 bool TabStrip::ShouldHighlightCloseButtonAfterRemove() {
1623 return in_tab_close_;
1626 void TabStrip::DoLayout() {
1627 last_layout_size_ = size();
1629 StopAnimating(false);
1631 SwapLayoutIfNecessary();
1633 if (touch_layout_)
1634 touch_layout_->SetWidth(tab_area_width());
1636 GenerateIdealBounds();
1638 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1639 SetTabVisibility();
1641 SchedulePaint();
1643 bounds_animator_.StopAnimatingView(newtab_button_);
1644 newtab_button_->SetBoundsRect(newtab_button_bounds_);
1647 void TabStrip::SetTabVisibility() {
1648 // We could probably be more efficient here by making use of the fact that the
1649 // tabstrip will always have any visible tabs, and then any invisible tabs, so
1650 // we could e.g. binary-search for the changeover point. But since we have to
1651 // iterate through all the tabs to call SetVisible() anyway, it doesn't seem
1652 // worth it.
1653 for (int i = 0; i < tab_count(); ++i) {
1654 Tab* tab = tab_at(i);
1655 tab->SetVisible(ShouldTabBeVisible(tab));
1657 for (TabsClosingMap::const_iterator i(tabs_closing_map_.begin());
1658 i != tabs_closing_map_.end(); ++i) {
1659 for (Tabs::const_iterator j(i->second.begin()); j != i->second.end(); ++j) {
1660 Tab* tab = *j;
1661 tab->SetVisible(ShouldTabBeVisible(tab));
1666 void TabStrip::DragActiveTab(const std::vector<int>& initial_positions,
1667 int delta) {
1668 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size()));
1669 if (!touch_layout_) {
1670 StackDraggedTabs(delta);
1671 return;
1673 SetIdealBoundsFromPositions(initial_positions);
1674 touch_layout_->DragActiveTab(delta);
1675 DoLayout();
1678 void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) {
1679 if (static_cast<size_t>(tab_count()) != positions.size())
1680 return;
1682 for (int i = 0; i < tab_count(); ++i) {
1683 gfx::Rect bounds(ideal_bounds(i));
1684 bounds.set_x(positions[i]);
1685 tabs_.set_ideal_bounds(i, bounds);
1689 void TabStrip::StackDraggedTabs(int delta) {
1690 DCHECK(!touch_layout_);
1691 GenerateIdealBounds();
1692 const int active_index = controller_->GetActiveIndex();
1693 DCHECK_NE(-1, active_index);
1694 if (delta < 0) {
1695 // Drag the tabs to the left, stacking tabs before the active tab.
1696 const int adjusted_delta =
1697 std::min(ideal_bounds(active_index).x() -
1698 kStackedPadding * std::min(active_index, kMaxStackedCount),
1699 -delta);
1700 for (int i = 0; i <= active_index; ++i) {
1701 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding;
1702 gfx::Rect new_bounds(ideal_bounds(i));
1703 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta));
1704 tabs_.set_ideal_bounds(i, new_bounds);
1706 const bool is_active_pinned = tab_at(active_index)->data().pinned;
1707 const int active_width = ideal_bounds(active_index).width();
1708 for (int i = active_index + 1; i < tab_count(); ++i) {
1709 const int max_x = ideal_bounds(active_index).x() +
1710 (kStackedPadding * std::min(i - active_index, kMaxStackedCount));
1711 gfx::Rect new_bounds(ideal_bounds(i));
1712 int new_x = std::max(new_bounds.x() + delta, max_x);
1713 if (new_x == max_x && !tab_at(i)->data().pinned && !is_active_pinned &&
1714 new_bounds.width() != active_width)
1715 new_x += (active_width - new_bounds.width());
1716 new_bounds.set_x(new_x);
1717 tabs_.set_ideal_bounds(i, new_bounds);
1719 } else {
1720 // Drag the tabs to the right, stacking tabs after the active tab.
1721 const int last_tab_width = ideal_bounds(tab_count() - 1).width();
1722 const int last_tab_x = tab_area_width() - last_tab_width;
1723 if (active_index == tab_count() - 1 &&
1724 ideal_bounds(tab_count() - 1).x() == last_tab_x)
1725 return;
1726 const int adjusted_delta =
1727 std::min(last_tab_x -
1728 kStackedPadding * std::min(tab_count() - active_index - 1,
1729 kMaxStackedCount) -
1730 ideal_bounds(active_index).x(),
1731 delta);
1732 for (int last_index = tab_count() - 1, i = last_index; i >= active_index;
1733 --i) {
1734 const int max_x = last_tab_x -
1735 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding;
1736 gfx::Rect new_bounds(ideal_bounds(i));
1737 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta);
1738 // Because of rounding not all tabs are the same width. Adjust the
1739 // position to accommodate this, otherwise the stacking is off.
1740 if (new_x == max_x && !tab_at(i)->data().pinned &&
1741 new_bounds.width() != last_tab_width)
1742 new_x += (last_tab_width - new_bounds.width());
1743 new_bounds.set_x(new_x);
1744 tabs_.set_ideal_bounds(i, new_bounds);
1746 for (int i = active_index - 1; i >= 0; --i) {
1747 const int min_x = ideal_bounds(active_index).x() -
1748 std::min(active_index - i, kMaxStackedCount) * kStackedPadding;
1749 gfx::Rect new_bounds(ideal_bounds(i));
1750 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta));
1751 tabs_.set_ideal_bounds(i, new_bounds);
1753 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x())
1754 newtab_button_->SetVisible(false);
1756 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_);
1757 SchedulePaint();
1760 bool TabStrip::IsStackingDraggedTabs() const {
1761 return drag_controller_.get() && drag_controller_->started_drag() &&
1762 (drag_controller_->move_behavior() ==
1763 TabDragController::MOVE_VISIBILE_TABS);
1766 void TabStrip::LayoutDraggedTabsAt(const Tabs& tabs,
1767 Tab* active_tab,
1768 const gfx::Point& location,
1769 bool initial_drag) {
1770 // Immediately hide the new tab button if the last tab is being dragged.
1771 const Tab* last_visible_tab = GetLastVisibleTab();
1772 if (last_visible_tab && last_visible_tab->dragging())
1773 newtab_button_->SetVisible(false);
1774 std::vector<gfx::Rect> bounds;
1775 CalculateBoundsForDraggedTabs(tabs, &bounds);
1776 DCHECK_EQ(tabs.size(), bounds.size());
1777 int active_tab_model_index = GetModelIndexOfTab(active_tab);
1778 int active_tab_index = static_cast<int>(
1779 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin());
1780 for (size_t i = 0; i < tabs.size(); ++i) {
1781 Tab* tab = tabs[i];
1782 gfx::Rect new_bounds = bounds[i];
1783 new_bounds.Offset(location.x(), location.y());
1784 int consecutive_index =
1785 active_tab_model_index - (active_tab_index - static_cast<int>(i));
1786 // If this is the initial layout during a drag and the tabs aren't
1787 // consecutive animate the view into position. Do the same if the tab is
1788 // already animating (which means we previously caused it to animate).
1789 if ((initial_drag &&
1790 GetModelIndexOfTab(tabs[i]) != consecutive_index) ||
1791 bounds_animator_.IsAnimating(tabs[i])) {
1792 bounds_animator_.SetTargetBounds(tabs[i], new_bounds);
1793 } else {
1794 tab->SetBoundsRect(new_bounds);
1797 SetTabVisibility();
1800 void TabStrip::CalculateBoundsForDraggedTabs(const Tabs& tabs,
1801 std::vector<gfx::Rect>* bounds) {
1802 int x = 0;
1803 for (size_t i = 0; i < tabs.size(); ++i) {
1804 Tab* tab = tabs[i];
1805 if (i > 0 && tab->data().pinned != tabs[i - 1]->data().pinned)
1806 x += kPinnedToNonPinnedGap;
1807 gfx::Rect new_bounds = tab->bounds();
1808 new_bounds.set_origin(gfx::Point(x, 0));
1809 bounds->push_back(new_bounds);
1810 x += tab->width() + kTabHorizontalOffset;
1814 int TabStrip::GetSizeNeededForTabs(const Tabs& tabs) {
1815 int width = 0;
1816 for (size_t i = 0; i < tabs.size(); ++i) {
1817 Tab* tab = tabs[i];
1818 width += tab->width();
1819 if (i > 0 && tab->data().pinned != tabs[i - 1]->data().pinned)
1820 width += kPinnedToNonPinnedGap;
1822 if (tabs.size() > 0)
1823 width += kTabHorizontalOffset * static_cast<int>(tabs.size() - 1);
1824 return width;
1827 int TabStrip::GetPinnedTabCount() const {
1828 int pinned_count = 0;
1829 while (pinned_count < tab_count() && tab_at(pinned_count)->data().pinned)
1830 pinned_count++;
1831 return pinned_count;
1834 const Tab* TabStrip::GetLastVisibleTab() const {
1835 for (int i = tab_count() - 1; i >= 0; --i) {
1836 const Tab* tab = tab_at(i);
1837 if (tab->visible())
1838 return tab;
1840 // While in normal use the tabstrip should always be wide enough to have at
1841 // least one visible tab, it can be zero-width in tests, meaning we get here.
1842 return NULL;
1845 void TabStrip::RemoveTabFromViewModel(int index) {
1846 // We still need to paint the tab until we actually remove it. Put it
1847 // in tabs_closing_map_ so we can find it.
1848 tabs_closing_map_[index].push_back(tab_at(index));
1849 UpdateTabsClosingMap(index + 1, -1);
1850 tabs_.Remove(index);
1853 void TabStrip::RemoveAndDeleteTab(Tab* tab) {
1854 scoped_ptr<Tab> deleter(tab);
1855 FindClosingTabResult res(FindClosingTab(tab));
1856 res.first->second.erase(res.second);
1857 if (res.first->second.empty())
1858 tabs_closing_map_.erase(res.first);
1861 void TabStrip::UpdateTabsClosingMap(int index, int delta) {
1862 if (tabs_closing_map_.empty())
1863 return;
1865 if (delta == -1 &&
1866 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() &&
1867 tabs_closing_map_.find(index) != tabs_closing_map_.end()) {
1868 const Tabs& tabs(tabs_closing_map_[index]);
1869 tabs_closing_map_[index - 1].insert(
1870 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end());
1872 TabsClosingMap updated_map;
1873 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1874 i != tabs_closing_map_.end(); ++i) {
1875 if (i->first > index)
1876 updated_map[i->first + delta] = i->second;
1877 else if (i->first < index)
1878 updated_map[i->first] = i->second;
1880 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end())
1881 updated_map[index + delta] = tabs_closing_map_[index];
1882 tabs_closing_map_.swap(updated_map);
1885 void TabStrip::StartedDraggingTabs(const Tabs& tabs) {
1886 // Let the controller know that the user started dragging tabs.
1887 controller()->OnStartedDraggingTabs();
1889 // Hide the new tab button immediately if we didn't originate the drag.
1890 if (!drag_controller_.get())
1891 newtab_button_->SetVisible(false);
1893 PrepareForAnimation();
1895 // Reset dragging state of existing tabs.
1896 for (int i = 0; i < tab_count(); ++i)
1897 tab_at(i)->set_dragging(false);
1899 for (size_t i = 0; i < tabs.size(); ++i) {
1900 tabs[i]->set_dragging(true);
1901 bounds_animator_.StopAnimatingView(tabs[i]);
1904 // Move the dragged tabs to their ideal bounds.
1905 GenerateIdealBounds();
1907 // Sets the bounds of the dragged tabs.
1908 for (size_t i = 0; i < tabs.size(); ++i) {
1909 int tab_data_index = GetModelIndexOfTab(tabs[i]);
1910 DCHECK_NE(-1, tab_data_index);
1911 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
1913 SetTabVisibility();
1914 SchedulePaint();
1917 void TabStrip::DraggedTabsDetached() {
1918 // Let the controller know that the user is not dragging this tabstrip's tabs
1919 // anymore.
1920 controller()->OnStoppedDraggingTabs();
1921 newtab_button_->SetVisible(true);
1924 void TabStrip::StoppedDraggingTabs(const Tabs& tabs,
1925 const std::vector<int>& initial_positions,
1926 bool move_only,
1927 bool completed) {
1928 // Let the controller know that the user stopped dragging tabs.
1929 controller()->OnStoppedDraggingTabs();
1931 newtab_button_->SetVisible(true);
1932 if (move_only && touch_layout_) {
1933 if (completed)
1934 touch_layout_->SizeToFit();
1935 else
1936 SetIdealBoundsFromPositions(initial_positions);
1938 bool is_first_tab = true;
1939 for (size_t i = 0; i < tabs.size(); ++i)
1940 StoppedDraggingTab(tabs[i], &is_first_tab);
1943 void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) {
1944 int tab_data_index = GetModelIndexOfTab(tab);
1945 if (tab_data_index == -1) {
1946 // The tab was removed before the drag completed. Don't do anything.
1947 return;
1950 if (*is_first_tab) {
1951 *is_first_tab = false;
1952 PrepareForAnimation();
1954 // Animate the view back to its correct position.
1955 GenerateIdealBounds();
1956 AnimateToIdealBounds();
1958 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index));
1959 // Install a delegate to reset the dragging state when done. We have to leave
1960 // dragging true for the tab otherwise it'll draw beneath the new tab button.
1961 bounds_animator_.SetAnimationDelegate(
1962 tab,
1963 scoped_ptr<gfx::AnimationDelegate>(
1964 new ResetDraggingStateDelegate(this, tab)));
1967 void TabStrip::OwnDragController(TabDragController* controller) {
1968 // Typically, ReleaseDragController() and OwnDragController() calls are paired
1969 // via corresponding calls to TabDragController::Detach() and
1970 // TabDragController::Attach(). There is one exception to that rule: when a
1971 // drag might start, we create a TabDragController that is owned by the
1972 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts,
1973 // we then call Attach() on the source tabstrip, but since the source tabstrip
1974 // already owns the TabDragController, so we don't need to do anything.
1975 if (controller != drag_controller_.get())
1976 drag_controller_.reset(controller);
1979 void TabStrip::DestroyDragController() {
1980 newtab_button_->SetVisible(true);
1981 drag_controller_.reset();
1984 TabDragController* TabStrip::ReleaseDragController() {
1985 return drag_controller_.release();
1988 TabStrip::FindClosingTabResult TabStrip::FindClosingTab(const Tab* tab) {
1989 DCHECK(tab->closing());
1990 for (TabsClosingMap::iterator i(tabs_closing_map_.begin());
1991 i != tabs_closing_map_.end(); ++i) {
1992 Tabs::iterator j = std::find(i->second.begin(), i->second.end(), tab);
1993 if (j != i->second.end())
1994 return FindClosingTabResult(i, j);
1996 NOTREACHED();
1997 return FindClosingTabResult(tabs_closing_map_.end(), Tabs::iterator());
2000 void TabStrip::PaintClosingTabs(int index, const ui::PaintContext& context) {
2001 if (tabs_closing_map_.find(index) == tabs_closing_map_.end())
2002 return;
2004 const Tabs& tabs = tabs_closing_map_[index];
2005 for (Tabs::const_reverse_iterator i(tabs.rbegin()); i != tabs.rend(); ++i)
2006 (*i)->Paint(context);
2009 void TabStrip::UpdateStackedLayoutFromMouseEvent(views::View* source,
2010 const ui::MouseEvent& event) {
2011 if (!adjust_layout_)
2012 return;
2014 // The following code attempts to switch to shrink (not stacked) layout when
2015 // the mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and
2016 // to stacked layout when a touch device is used. This is made problematic by
2017 // windows generating mouse move events that do not clearly indicate the move
2018 // is the result of a touch device. This assumes a real mouse is used if
2019 // |kMouseMoveCountBeforeConsiderReal| mouse move events are received within
2020 // the time window |kMouseMoveTimeMS|. At the time we get a mouse press we
2021 // know whether its from a touch device or not, but we don't layout then else
2022 // everything shifts. Instead we wait for the release.
2024 // TODO(sky): revisit this when touch events are really plumbed through.
2026 switch (event.type()) {
2027 case ui::ET_MOUSE_PRESSED:
2028 mouse_move_count_ = 0;
2029 last_mouse_move_time_ = base::TimeTicks();
2030 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0);
2031 if (reset_to_shrink_on_exit_ && touch_layout_) {
2032 gfx::Point tab_strip_point(event.location());
2033 views::View::ConvertPointToTarget(source, this, &tab_strip_point);
2034 Tab* tab = FindTabForEvent(tab_strip_point);
2035 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) {
2036 SetStackedLayout(false);
2037 controller_->StackedLayoutMaybeChanged();
2040 break;
2042 case ui::ET_MOUSE_MOVED: {
2043 #if defined(USE_ASH)
2044 // Ash does not synthesize mouse events from touch events.
2045 SetResetToShrinkOnExit(true);
2046 #else
2047 gfx::Point location(event.location());
2048 ConvertPointToTarget(source, this, &location);
2049 if (location == last_mouse_move_location_)
2050 return; // Ignore spurious moves.
2051 last_mouse_move_location_ = location;
2052 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 &&
2053 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) {
2054 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() <
2055 kMouseMoveTimeMS) {
2056 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal)
2057 SetResetToShrinkOnExit(true);
2058 } else {
2059 mouse_move_count_ = 1;
2060 last_mouse_move_time_ = base::TimeTicks::Now();
2062 } else {
2063 last_mouse_move_time_ = base::TimeTicks();
2065 #endif
2066 break;
2069 case ui::ET_MOUSE_RELEASED: {
2070 gfx::Point location(event.location());
2071 ConvertPointToTarget(source, this, &location);
2072 last_mouse_move_location_ = location;
2073 mouse_move_count_ = 0;
2074 last_mouse_move_time_ = base::TimeTicks();
2075 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) {
2076 SetStackedLayout(true);
2077 controller_->StackedLayoutMaybeChanged();
2079 break;
2082 default:
2083 break;
2087 void TabStrip::GetCurrentTabWidths(double* unselected_width,
2088 double* selected_width) const {
2089 *unselected_width = current_unselected_width_;
2090 *selected_width = current_selected_width_;
2093 void TabStrip::GetDesiredTabWidths(int tab_count,
2094 int pinned_tab_count,
2095 double* unselected_width,
2096 double* selected_width) const {
2097 DCHECK(tab_count >= 0 && pinned_tab_count >= 0 &&
2098 pinned_tab_count <= tab_count);
2099 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width();
2100 const double min_selected_width = Tab::GetMinimumSelectedSize().width();
2102 *unselected_width = min_unselected_width;
2103 *selected_width = min_selected_width;
2105 if (tab_count == 0) {
2106 // Return immediately to avoid divide-by-zero below.
2107 return;
2110 // Determine how much space we can actually allocate to tabs.
2111 int available_width = (available_width_for_tabs_ < 0) ?
2112 tab_area_width() : available_width_for_tabs_;
2113 if (pinned_tab_count > 0) {
2114 available_width -=
2115 pinned_tab_count * (Tab::GetPinnedWidth() + kTabHorizontalOffset);
2116 tab_count -= pinned_tab_count;
2117 if (tab_count == 0) {
2118 *selected_width = *unselected_width = Tab::GetStandardSize().width();
2119 return;
2121 // Account for gap between the last pinned tab and first non-pinned tab.
2122 available_width -= kPinnedToNonPinnedGap;
2125 // Calculate the desired tab widths by dividing the available space into equal
2126 // portions. Don't let tabs get larger than the "standard width" or smaller
2127 // than the minimum width for each type, respectively.
2128 const int total_offset = kTabHorizontalOffset * (tab_count - 1);
2129 const double desired_tab_width = std::min((static_cast<double>(
2130 available_width - total_offset) / static_cast<double>(tab_count)),
2131 static_cast<double>(Tab::GetStandardSize().width()));
2132 *unselected_width = std::max(desired_tab_width, min_unselected_width);
2133 *selected_width = std::max(desired_tab_width, min_selected_width);
2135 // When there are multiple tabs, we'll have one selected and some unselected
2136 // tabs. If the desired width was between the minimum sizes of these types,
2137 // try to shrink the tabs with the smaller minimum. For example, if we have
2138 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If
2139 // selected tabs have a minimum width of 4 and unselected tabs have a minimum
2140 // width of 1, the above code would set *unselected_width = 2.5,
2141 // *selected_width = 4, which results in a total width of 11.5. Instead, we
2142 // want to set *unselected_width = 2, *selected_width = 4, for a total width
2143 // of 10.
2144 if (tab_count > 1) {
2145 if (desired_tab_width < min_selected_width) {
2146 // Unselected width = (total width - selected width) / (num_tabs - 1)
2147 *unselected_width = std::max(static_cast<double>(
2148 available_width - total_offset - min_selected_width) /
2149 static_cast<double>(tab_count - 1), min_unselected_width);
2154 void TabStrip::ResizeLayoutTabs() {
2155 // We've been called back after the TabStrip has been emptied out (probably
2156 // just prior to the window being destroyed). We need to do nothing here or
2157 // else GetTabAt below will crash.
2158 if (tab_count() == 0)
2159 return;
2161 // It is critically important that this is unhooked here, otherwise we will
2162 // keep spying on messages forever.
2163 RemoveMessageLoopObserver();
2165 in_tab_close_ = false;
2166 available_width_for_tabs_ = -1;
2167 int pinned_tab_count = GetPinnedTabCount();
2168 if (pinned_tab_count == tab_count()) {
2169 // Only pinned tabs, we know the tab widths won't have changed (all
2170 // pinned tabs have the same width), so there is nothing to do.
2171 return;
2173 // Don't try and avoid layout based on tab sizes. If tabs are small enough
2174 // then the width of the active tab may not change, but other widths may
2175 // have. This is particularly important if we've overflowed (all tabs are at
2176 // the min).
2177 StartResizeLayoutAnimation();
2180 void TabStrip::ResizeLayoutTabsFromTouch() {
2181 // Don't resize if the user is interacting with the tabstrip.
2182 if (!drag_controller_.get())
2183 ResizeLayoutTabs();
2184 else
2185 StartResizeLayoutTabsFromTouchTimer();
2188 void TabStrip::StartResizeLayoutTabsFromTouchTimer() {
2189 resize_layout_timer_.Stop();
2190 resize_layout_timer_.Start(
2191 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS),
2192 this, &TabStrip::ResizeLayoutTabsFromTouch);
2195 void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) {
2196 StopAnimating(false);
2197 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size()));
2198 for (int i = 0; i < tab_count(); ++i)
2199 tab_at(i)->SetBoundsRect(tab_bounds[i]);
2200 // Reset the layout size as we've effectively layed out a different size.
2201 // This ensures a layout happens after the drag is done.
2202 last_layout_size_ = gfx::Size();
2205 void TabStrip::AddMessageLoopObserver() {
2206 if (!mouse_watcher_.get()) {
2207 mouse_watcher_.reset(
2208 new views::MouseWatcher(
2209 new views::MouseWatcherViewHost(
2210 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)),
2211 this));
2213 mouse_watcher_->Start();
2216 void TabStrip::RemoveMessageLoopObserver() {
2217 mouse_watcher_.reset(NULL);
2220 gfx::Rect TabStrip::GetDropBounds(int drop_index,
2221 bool drop_before,
2222 bool* is_beneath) {
2223 DCHECK_NE(drop_index, -1);
2224 int center_x;
2225 if (drop_index < tab_count()) {
2226 Tab* tab = tab_at(drop_index);
2227 if (drop_before)
2228 center_x = tab->x() - (kTabHorizontalOffset / 2);
2229 else
2230 center_x = tab->x() + (tab->width() / 2);
2231 } else {
2232 Tab* last_tab = tab_at(drop_index - 1);
2233 center_x = last_tab->x() + last_tab->width() + (kTabHorizontalOffset / 2);
2236 // Mirror the center point if necessary.
2237 center_x = GetMirroredXInView(center_x);
2239 // Determine the screen bounds.
2240 gfx::Point drop_loc(center_x - drop_indicator_width / 2,
2241 -drop_indicator_height);
2242 ConvertPointToScreen(this, &drop_loc);
2243 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width,
2244 drop_indicator_height);
2246 // If the rect doesn't fit on the monitor, push the arrow to the bottom.
2247 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView());
2248 gfx::Display display = screen->GetDisplayMatching(drop_bounds);
2249 *is_beneath = !display.bounds().Contains(drop_bounds);
2250 if (*is_beneath)
2251 drop_bounds.Offset(0, drop_bounds.height() + height());
2253 return drop_bounds;
2256 void TabStrip::UpdateDropIndex(const DropTargetEvent& event) {
2257 // If the UI layout is right-to-left, we need to mirror the mouse
2258 // coordinates since we calculate the drop index based on the
2259 // original (and therefore non-mirrored) positions of the tabs.
2260 const int x = GetMirroredXInView(event.x());
2261 // We don't allow replacing the urls of pinned tabs.
2262 for (int i = GetPinnedTabCount(); i < tab_count(); ++i) {
2263 Tab* tab = tab_at(i);
2264 const int tab_max_x = tab->x() + tab->width();
2265 const int hot_width = tab->width() / kTabEdgeRatioInverse;
2266 if (x < tab_max_x) {
2267 if (x < tab->x() + hot_width)
2268 SetDropIndex(i, true);
2269 else if (x >= tab_max_x - hot_width)
2270 SetDropIndex(i + 1, true);
2271 else
2272 SetDropIndex(i, false);
2273 return;
2277 // The drop isn't over a tab, add it to the end.
2278 SetDropIndex(tab_count(), true);
2281 void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) {
2282 // Let the controller know of the index update.
2283 controller()->OnDropIndexUpdate(tab_data_index, drop_before);
2285 if (tab_data_index == -1) {
2286 if (drop_info_.get())
2287 drop_info_.reset(NULL);
2288 return;
2291 if (drop_info_.get() && drop_info_->drop_index == tab_data_index &&
2292 drop_info_->drop_before == drop_before) {
2293 return;
2296 bool is_beneath;
2297 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before,
2298 &is_beneath);
2300 if (!drop_info_.get()) {
2301 drop_info_.reset(
2302 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget()));
2303 } else {
2304 drop_info_->drop_index = tab_data_index;
2305 drop_info_->drop_before = drop_before;
2306 if (is_beneath == drop_info_->point_down) {
2307 drop_info_->point_down = !is_beneath;
2308 drop_info_->arrow_view->SetImage(
2309 GetDropArrowImage(drop_info_->point_down));
2313 // Reposition the window. Need to show it too as the window is initially
2314 // hidden.
2315 drop_info_->arrow_window->SetBounds(drop_bounds);
2316 drop_info_->arrow_window->Show();
2319 int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) {
2320 const int source_ops = event.source_operations();
2321 if (source_ops & ui::DragDropTypes::DRAG_COPY)
2322 return ui::DragDropTypes::DRAG_COPY;
2323 if (source_ops & ui::DragDropTypes::DRAG_LINK)
2324 return ui::DragDropTypes::DRAG_LINK;
2325 return ui::DragDropTypes::DRAG_MOVE;
2328 // static
2329 gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) {
2330 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
2331 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP);
2334 // TabStrip::DropInfo ----------------------------------------------------------
2336 TabStrip::DropInfo::DropInfo(int drop_index,
2337 bool drop_before,
2338 bool point_down,
2339 views::Widget* context)
2340 : drop_index(drop_index),
2341 drop_before(drop_before),
2342 point_down(point_down),
2343 file_supported(true) {
2344 arrow_view = new views::ImageView;
2345 arrow_view->SetImage(GetDropArrowImage(point_down));
2347 arrow_window = new views::Widget;
2348 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
2349 params.keep_on_top = true;
2350 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
2351 params.accept_events = false;
2352 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height);
2353 params.context = context->GetNativeWindow();
2354 arrow_window->Init(params);
2355 arrow_window->SetContentsView(arrow_view);
2358 TabStrip::DropInfo::~DropInfo() {
2359 // Close eventually deletes the window, which deletes arrow_view too.
2360 arrow_window->Close();
2363 ///////////////////////////////////////////////////////////////////////////////
2365 void TabStrip::PrepareForAnimation() {
2366 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) {
2367 for (int i = 0; i < tab_count(); ++i)
2368 tab_at(i)->set_dragging(false);
2372 void TabStrip::GenerateIdealBounds() {
2373 int new_tab_y = 0;
2375 if (touch_layout_) {
2376 if (tabs_.view_size() == 0)
2377 return;
2379 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() +
2380 kNewTabButtonHorizontalOffset;
2381 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2382 return;
2385 GetDesiredTabWidths(tab_count(), GetPinnedTabCount(),
2386 &current_unselected_width_, &current_selected_width_);
2388 // NOTE: This currently assumes a tab's height doesn't differ based on
2389 // selected state or the number of tabs in the strip!
2390 int tab_height = Tab::GetStandardSize().height();
2391 int first_non_pinned_index = 0;
2392 double tab_x = GenerateIdealBoundsForPinnedTabs(&first_non_pinned_index);
2393 for (int i = first_non_pinned_index; i < tab_count(); ++i) {
2394 Tab* tab = tab_at(i);
2395 DCHECK(!tab->data().pinned);
2396 double tab_width =
2397 tab->IsActive() ? current_selected_width_ : current_unselected_width_;
2398 double end_of_tab = tab_x + tab_width;
2399 int rounded_tab_x = Round(tab_x);
2400 tabs_.set_ideal_bounds(
2402 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x,
2403 tab_height));
2404 tab_x = end_of_tab + kTabHorizontalOffset;
2407 // Update bounds of new tab button.
2408 int new_tab_x;
2409 if ((Tab::GetStandardSize().width() - Round(current_unselected_width_)) > 1 &&
2410 !in_tab_close_) {
2411 // We're shrinking tabs, so we need to anchor the New Tab button to the
2412 // right edge of the TabStrip's bounds, rather than the right edge of the
2413 // right-most Tab, otherwise it'll bounce when animating.
2414 new_tab_x = width() - newtab_button_bounds_.width();
2415 } else {
2416 new_tab_x = Round(tab_x - kTabHorizontalOffset) +
2417 kNewTabButtonHorizontalOffset;
2419 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y));
2422 int TabStrip::GenerateIdealBoundsForPinnedTabs(int* first_non_pinned_index) {
2423 int next_x = 0;
2424 int pinned_width = Tab::GetPinnedWidth();
2425 int tab_height = Tab::GetStandardSize().height();
2426 int index = 0;
2427 for (; index < tab_count() && tab_at(index)->data().pinned; ++index) {
2428 tabs_.set_ideal_bounds(index,
2429 gfx::Rect(next_x, 0, pinned_width, tab_height));
2430 next_x += pinned_width + kTabHorizontalOffset;
2432 if (index > 0 && index < tab_count())
2433 next_x += kPinnedToNonPinnedGap;
2434 if (first_non_pinned_index)
2435 *first_non_pinned_index = index;
2436 return next_x;
2439 void TabStrip::StartResizeLayoutAnimation() {
2440 PrepareForAnimation();
2441 GenerateIdealBounds();
2442 AnimateToIdealBounds();
2445 void TabStrip::StartPinnedTabAnimation() {
2446 in_tab_close_ = false;
2447 available_width_for_tabs_ = -1;
2449 PrepareForAnimation();
2451 GenerateIdealBounds();
2452 AnimateToIdealBounds();
2455 void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) {
2456 // The user initiated the close. We want to persist the bounds of all the
2457 // existing tabs, so we manually shift ideal_bounds then animate.
2458 Tab* tab_closing = tab_at(model_index);
2459 int delta = tab_closing->width() + kTabHorizontalOffset;
2460 // If the tab being closed is a pinned tab next to a non-pinned tab, be sure
2461 // to add the extra padding.
2462 DCHECK_LT(model_index, tab_count() - 1);
2463 if (tab_closing->data().pinned && !tab_at(model_index + 1)->data().pinned)
2464 delta += kPinnedToNonPinnedGap;
2466 for (int i = model_index + 1; i < tab_count(); ++i) {
2467 gfx::Rect bounds = ideal_bounds(i);
2468 bounds.set_x(bounds.x() - delta);
2469 tabs_.set_ideal_bounds(i, bounds);
2472 // Don't just subtract |delta| from the New Tab x-coordinate, as we might have
2473 // overflow tabs that will be able to animate into the strip, in which case
2474 // the new tab button should stay where it is.
2475 newtab_button_bounds_.set_x(std::min(
2476 width() - newtab_button_bounds_.width(),
2477 ideal_bounds(tab_count() - 1).right() + kNewTabButtonHorizontalOffset));
2479 PrepareForAnimation();
2481 tab_closing->set_closing(true);
2483 // We still need to paint the tab until we actually remove it. Put it in
2484 // tabs_closing_map_ so we can find it.
2485 RemoveTabFromViewModel(model_index);
2487 AnimateToIdealBounds();
2489 gfx::Rect tab_bounds = tab_closing->bounds();
2490 tab_bounds.set_width(0);
2491 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds);
2493 // Register delegate to do cleanup when done, BoundsAnimator takes
2494 // ownership of RemoveTabDelegate.
2495 bounds_animator_.SetAnimationDelegate(
2496 tab_closing,
2497 scoped_ptr<gfx::AnimationDelegate>(
2498 new RemoveTabDelegate(this, tab_closing)));
2501 bool TabStrip::IsPointInTab(Tab* tab,
2502 const gfx::Point& point_in_tabstrip_coords) {
2503 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
2504 View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
2505 return tab->HitTestPoint(point_in_tab_coords);
2508 int TabStrip::GetStartXForNormalTabs() const {
2509 int pinned_tab_count = GetPinnedTabCount();
2510 if (pinned_tab_count == 0)
2511 return 0;
2512 return pinned_tab_count * (Tab::GetPinnedWidth() + kTabHorizontalOffset) +
2513 kPinnedToNonPinnedGap;
2516 Tab* TabStrip::FindTabForEvent(const gfx::Point& point) {
2517 if (touch_layout_) {
2518 int active_tab_index = touch_layout_->active_index();
2519 if (active_tab_index != -1) {
2520 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1);
2521 if (!tab)
2522 tab = FindTabForEventFrom(point, active_tab_index + 1, 1);
2523 return tab;
2525 if (tab_count())
2526 return FindTabForEventFrom(point, 0, 1);
2527 } else {
2528 for (int i = 0; i < tab_count(); ++i) {
2529 if (IsPointInTab(tab_at(i), point))
2530 return tab_at(i);
2533 return NULL;
2536 Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point,
2537 int start,
2538 int delta) {
2539 // |start| equals tab_count() when there are only pinned tabs.
2540 if (start == tab_count())
2541 start += delta;
2542 for (int i = start; i >= 0 && i < tab_count(); i += delta) {
2543 if (IsPointInTab(tab_at(i), point))
2544 return tab_at(i);
2546 return NULL;
2549 views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) {
2550 // The display order doesn't necessarily match the child list order, so we
2551 // walk the display list hit-testing Tabs. Since the active tab always
2552 // renders on top of adjacent tabs, it needs to be hit-tested before any
2553 // left-adjacent Tab, so we look ahead for it as we walk.
2554 for (int i = 0; i < tab_count(); ++i) {
2555 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL;
2556 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point))
2557 return next_tab;
2558 if (IsPointInTab(tab_at(i), point))
2559 return tab_at(i);
2562 return NULL;
2565 std::vector<int> TabStrip::GetTabXCoordinates() {
2566 std::vector<int> results;
2567 for (int i = 0; i < tab_count(); ++i)
2568 results.push_back(ideal_bounds(i).x());
2569 return results;
2572 void TabStrip::SwapLayoutIfNecessary() {
2573 bool needs_touch = NeedsTouchLayout();
2574 bool using_touch = touch_layout_ != NULL;
2575 if (needs_touch == using_touch)
2576 return;
2578 if (needs_touch) {
2579 gfx::Size tab_size(Tab::GetMinimumSelectedSize());
2580 tab_size.set_width(Tab::GetTouchWidth());
2581 touch_layout_.reset(new StackedTabStripLayout(
2582 tab_size,
2583 kTabHorizontalOffset,
2584 kStackedPadding,
2585 kMaxStackedCount,
2586 &tabs_));
2587 touch_layout_->SetWidth(tab_area_width());
2588 // This has to be after SetWidth() as SetWidth() is going to reset the
2589 // bounds of the pinned tabs (since StackedTabStripLayout doesn't yet know
2590 // how many pinned tabs there are).
2591 GenerateIdealBoundsForPinnedTabs(NULL);
2592 touch_layout_->SetXAndPinnedCount(GetStartXForNormalTabs(),
2593 GetPinnedTabCount());
2594 touch_layout_->SetActiveIndex(controller_->GetActiveIndex());
2596 content::RecordAction(UserMetricsAction("StackedTab_EnteredStackedLayout"));
2597 } else {
2598 touch_layout_.reset();
2600 PrepareForAnimation();
2601 GenerateIdealBounds();
2602 SetTabVisibility();
2603 AnimateToIdealBounds();
2606 bool TabStrip::NeedsTouchLayout() const {
2607 if (!stacked_layout_)
2608 return false;
2610 int pinned_tab_count = GetPinnedTabCount();
2611 int normal_count = tab_count() - pinned_tab_count;
2612 if (normal_count <= 1 || normal_count == pinned_tab_count)
2613 return false;
2614 int x = GetStartXForNormalTabs();
2615 int available_width = tab_area_width() - x;
2616 return (Tab::GetTouchWidth() * normal_count +
2617 kTabHorizontalOffset * (normal_count - 1)) > available_width;
2620 void TabStrip::SetResetToShrinkOnExit(bool value) {
2621 if (!adjust_layout_)
2622 return;
2624 if (value && !stacked_layout_)
2625 value = false; // We're already using shrink (not stacked) layout.
2627 if (value == reset_to_shrink_on_exit_)
2628 return;
2630 reset_to_shrink_on_exit_ = value;
2631 // Add an observer so we know when the mouse moves out of the tabstrip.
2632 if (reset_to_shrink_on_exit_)
2633 AddMessageLoopObserver();
2634 else
2635 RemoveMessageLoopObserver();
2638 void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) {
2639 if (sender == newtab_button_) {
2640 content::RecordAction(UserMetricsAction("NewTab_Button"));
2641 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON,
2642 TabStripModel::NEW_TAB_ENUM_COUNT);
2643 if (event.IsMouseEvent()) {
2644 const ui::MouseEvent& mouse = static_cast<const ui::MouseEvent&>(event);
2645 if (mouse.IsOnlyMiddleMouseButton()) {
2646 base::string16 clipboard_text = GetClipboardText();
2647 if (!clipboard_text.empty())
2648 controller()->CreateNewTabWithLocation(clipboard_text);
2649 return;
2653 controller()->CreateNewTab();
2654 if (event.type() == ui::ET_GESTURE_TAP)
2655 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP);
2659 // Overridden to support automation. See automation_proxy_uitest.cc.
2660 const views::View* TabStrip::GetViewByID(int view_id) const {
2661 if (tab_count() > 0) {
2662 if (view_id == VIEW_ID_TAB_LAST)
2663 return tab_at(tab_count() - 1);
2664 if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) {
2665 int index = view_id - VIEW_ID_TAB_0;
2666 return (index >= 0 && index < tab_count()) ? tab_at(index) : NULL;
2670 return View::GetViewByID(view_id);
2673 bool TabStrip::OnMousePressed(const ui::MouseEvent& event) {
2674 UpdateStackedLayoutFromMouseEvent(this, event);
2675 // We can't return true here, else clicking in an empty area won't drag the
2676 // window.
2677 return false;
2680 bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
2681 ContinueDrag(this, event);
2682 return true;
2685 void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
2686 EndDrag(END_DRAG_COMPLETE);
2687 UpdateStackedLayoutFromMouseEvent(this, event);
2690 void TabStrip::OnMouseCaptureLost() {
2691 EndDrag(END_DRAG_CAPTURE_LOST);
2694 void TabStrip::OnMouseMoved(const ui::MouseEvent& event) {
2695 UpdateStackedLayoutFromMouseEvent(this, event);
2698 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
2699 SetResetToShrinkOnExit(true);
2702 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
2703 SetResetToShrinkOnExit(false);
2704 switch (event->type()) {
2705 case ui::ET_GESTURE_SCROLL_END:
2706 case ui::ET_SCROLL_FLING_START:
2707 case ui::ET_GESTURE_END:
2708 EndDrag(END_DRAG_COMPLETE);
2709 if (adjust_layout_) {
2710 SetStackedLayout(true);
2711 controller_->StackedLayoutMaybeChanged();
2713 break;
2715 case ui::ET_GESTURE_LONG_PRESS:
2716 if (drag_controller_.get())
2717 drag_controller_->SetMoveBehavior(TabDragController::REORDER);
2718 break;
2720 case ui::ET_GESTURE_LONG_TAP: {
2721 EndDrag(END_DRAG_CANCEL);
2722 gfx::Point local_point = event->location();
2723 Tab* tab = FindTabForEvent(local_point);
2724 if (tab) {
2725 ConvertPointToScreen(this, &local_point);
2726 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH);
2728 break;
2731 case ui::ET_GESTURE_SCROLL_UPDATE:
2732 ContinueDrag(this, *event);
2733 break;
2735 case ui::ET_GESTURE_TAP_DOWN:
2736 EndDrag(END_DRAG_CANCEL);
2737 break;
2739 case ui::ET_GESTURE_TAP: {
2740 const int active_index = controller_->GetActiveIndex();
2741 DCHECK_NE(-1, active_index);
2742 Tab* active_tab = tab_at(active_index);
2743 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP;
2744 if (active_tab->tab_activated_with_last_tap_down())
2745 action = TouchUMA::GESTURE_TABSWITCH_TAP;
2746 TouchUMA::RecordGestureAction(action);
2747 break;
2750 default:
2751 break;
2753 event->SetHandled();
2756 views::View* TabStrip::TargetForRect(views::View* root, const gfx::Rect& rect) {
2757 CHECK_EQ(root, this);
2759 if (!views::UsePointBasedTargeting(rect))
2760 return views::ViewTargeterDelegate::TargetForRect(root, rect);
2761 const gfx::Point point(rect.CenterPoint());
2763 if (!touch_layout_) {
2764 // Return any view that isn't a Tab or this TabStrip immediately. We don't
2765 // want to interfere.
2766 views::View* v = views::ViewTargeterDelegate::TargetForRect(root, rect);
2767 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName))
2768 return v;
2770 views::View* tab = FindTabHitByPoint(point);
2771 if (tab)
2772 return tab;
2773 } else {
2774 if (newtab_button_->visible()) {
2775 views::View* view =
2776 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point);
2777 if (view)
2778 return view;
2780 Tab* tab = FindTabForEvent(point);
2781 if (tab)
2782 return ConvertPointToViewAndGetEventHandler(this, tab, point);
2784 return this;