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