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