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