tab_strip.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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_END: 1561 EndDrag(END_DRAG_COMPLETE); 1562 if (adjust_layout_) { 1563 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true); 1564 controller_->LayoutTypeMaybeChanged(); 1565 } 1566 break; 1567 1568 case ui::ET_GESTURE_LONG_PRESS: 1569 if (drag_controller_.get()) 1570 drag_controller_->SetMoveBehavior(TabDragController::REORDER); 1571 break; 1572 1573 case ui::ET_GESTURE_LONG_TAP: { 1574 EndDrag(END_DRAG_CANCEL); 1575 gfx::Point local_point = event->location(); 1576 Tab* tab = FindTabForEvent(local_point); 1577 if (tab) { 1578 ConvertPointToScreen(this, &local_point); 1579 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH); 1580 } 1581 break; 1582 } 1583 1584 case ui::ET_GESTURE_SCROLL_UPDATE: 1585 ContinueDrag(this, *event); 1586 break; 1587 1588 case ui::ET_GESTURE_BEGIN: 1589 EndDrag(END_DRAG_CANCEL); 1590 break; 1591 1592 case ui::ET_GESTURE_TAP: { 1593 const int active_index = controller_->GetActiveIndex(); 1594 DCHECK_NE(-1, active_index); 1595 Tab* active_tab = tab_at(active_index); 1596 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP; 1597 if (active_tab->tab_activated_with_last_gesture_begin()) 1598 action = TouchUMA::GESTURE_TABSWITCH_TAP; 1599 TouchUMA::RecordGestureAction(action); 1600 break; 1601 } 1602 1603 default: 1604 break; 1605 } 1606 event->SetHandled(); 1607} 1608 1609/////////////////////////////////////////////////////////////////////////////// 1610// TabStrip, private: 1611 1612void TabStrip::Init() { 1613 set_id(VIEW_ID_TAB_STRIP); 1614 // So we get enter/exit on children to switch layout type. 1615 set_notify_enter_exit_on_child(true); 1616 newtab_button_bounds_.SetRect(0, 1617 0, 1618 newtab_button_asset_width(), 1619 newtab_button_asset_height() + 1620 newtab_button_v_offset()); 1621 newtab_button_ = new NewTabButton(this, this); 1622 newtab_button_->SetTooltipText( 1623 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB)); 1624 newtab_button_->SetAccessibleName( 1625 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB)); 1626 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT, 1627 views::ImageButton::ALIGN_BOTTOM); 1628 AddChildView(newtab_button_); 1629 if (drop_indicator_width == 0) { 1630 // Direction doesn't matter, both images are the same size. 1631 gfx::ImageSkia* drop_image = GetDropArrowImage(true); 1632 drop_indicator_width = drop_image->width(); 1633 drop_indicator_height = drop_image->height(); 1634 } 1635} 1636 1637Tab* TabStrip::CreateTab() { 1638 Tab* tab = new Tab(this); 1639 tab->set_animation_container(animation_container_.get()); 1640 return tab; 1641} 1642 1643void TabStrip::StartInsertTabAnimation(int model_index) { 1644 PrepareForAnimation(); 1645 1646 // The TabStrip can now use its entire width to lay out Tabs. 1647 in_tab_close_ = false; 1648 available_width_for_tabs_ = -1; 1649 1650 GenerateIdealBounds(); 1651 1652 Tab* tab = tab_at(model_index); 1653 if (model_index == 0) { 1654 tab->SetBounds(0, ideal_bounds(model_index).y(), 0, 1655 ideal_bounds(model_index).height()); 1656 } else { 1657 Tab* last_tab = tab_at(model_index - 1); 1658 tab->SetBounds(last_tab->bounds().right() + tab_h_offset(), 1659 ideal_bounds(model_index).y(), 0, 1660 ideal_bounds(model_index).height()); 1661 } 1662 1663 AnimateToIdealBounds(); 1664} 1665 1666void TabStrip::StartMoveTabAnimation() { 1667 PrepareForAnimation(); 1668 GenerateIdealBounds(); 1669 AnimateToIdealBounds(); 1670} 1671 1672void TabStrip::StartRemoveTabAnimation(int model_index) { 1673 PrepareForAnimation(); 1674 1675 // Mark the tab as closing. 1676 Tab* tab = tab_at(model_index); 1677 tab->set_closing(true); 1678 1679 RemoveTabFromViewModel(model_index); 1680 1681 ScheduleRemoveTabAnimation(tab); 1682} 1683 1684void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) { 1685 // Start an animation for the tabs. 1686 GenerateIdealBounds(); 1687 AnimateToIdealBounds(); 1688 1689 // Animate the tab being closed to 0x0. 1690 gfx::Rect tab_bounds = tab->bounds(); 1691 tab_bounds.set_width(0); 1692 bounds_animator_.AnimateViewTo(tab, tab_bounds); 1693 1694 // Register delegate to do cleanup when done, BoundsAnimator takes 1695 // ownership of RemoveTabDelegate. 1696 bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab), 1697 true); 1698 1699 // Don't animate the new tab button when dragging tabs. Otherwise it looks 1700 // like the new tab button magically appears from beyond the end of the tab 1701 // strip. 1702 if (TabDragController::IsAttachedTo(this)) { 1703 bounds_animator_.StopAnimatingView(newtab_button_); 1704 newtab_button_->SetBoundsRect(newtab_button_bounds_); 1705 } 1706} 1707 1708void TabStrip::AnimateToIdealBounds() { 1709 for (int i = 0; i < tab_count(); ++i) { 1710 Tab* tab = tab_at(i); 1711 if (!tab->dragging()) 1712 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i)); 1713 } 1714 1715 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_); 1716} 1717 1718bool TabStrip::ShouldHighlightCloseButtonAfterRemove() { 1719 return in_tab_close_; 1720} 1721 1722void TabStrip::DoLayout() { 1723 last_layout_size_ = size(); 1724 1725 StopAnimating(false); 1726 1727 SwapLayoutIfNecessary(); 1728 1729 if (touch_layout_.get()) 1730 touch_layout_->SetWidth(size().width() - new_tab_button_width()); 1731 1732 GenerateIdealBounds(); 1733 1734 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_); 1735 1736 SchedulePaint(); 1737 1738 bounds_animator_.StopAnimatingView(newtab_button_); 1739 newtab_button_->SetBoundsRect(newtab_button_bounds_); 1740} 1741 1742void TabStrip::DragActiveTab(const std::vector<int>& initial_positions, 1743 int delta) { 1744 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size())); 1745 if (!touch_layout_.get()) { 1746 StackDraggedTabs(delta); 1747 return; 1748 } 1749 SetIdealBoundsFromPositions(initial_positions); 1750 touch_layout_->DragActiveTab(delta); 1751 DoLayout(); 1752} 1753 1754void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) { 1755 if (static_cast<size_t>(tab_count()) != positions.size()) 1756 return; 1757 1758 for (int i = 0; i < tab_count(); ++i) { 1759 gfx::Rect bounds(ideal_bounds(i)); 1760 bounds.set_x(positions[i]); 1761 set_ideal_bounds(i, bounds); 1762 } 1763} 1764 1765void TabStrip::StackDraggedTabs(int delta) { 1766 DCHECK(!touch_layout_.get()); 1767 GenerateIdealBounds(); 1768 const int active_index = controller_->GetActiveIndex(); 1769 DCHECK_NE(-1, active_index); 1770 if (delta < 0) { 1771 // Drag the tabs to the left, stacking tabs before the active tab. 1772 const int adjusted_delta = 1773 std::min(ideal_bounds(active_index).x() - 1774 kStackedPadding * std::min(active_index, kMaxStackedCount), 1775 -delta); 1776 for (int i = 0; i <= active_index; ++i) { 1777 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding; 1778 gfx::Rect new_bounds(ideal_bounds(i)); 1779 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta)); 1780 set_ideal_bounds(i, new_bounds); 1781 } 1782 const bool is_active_mini = tab_at(active_index)->data().mini; 1783 const int active_width = ideal_bounds(active_index).width(); 1784 for (int i = active_index + 1; i < tab_count(); ++i) { 1785 const int max_x = ideal_bounds(active_index).x() + 1786 (kStackedPadding * std::min(i - active_index, kMaxStackedCount)); 1787 gfx::Rect new_bounds(ideal_bounds(i)); 1788 int new_x = std::max(new_bounds.x() + delta, max_x); 1789 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini && 1790 new_bounds.width() != active_width) 1791 new_x += (active_width - new_bounds.width()); 1792 new_bounds.set_x(new_x); 1793 set_ideal_bounds(i, new_bounds); 1794 } 1795 } else { 1796 // Drag the tabs to the right, stacking tabs after the active tab. 1797 const int last_tab_width = ideal_bounds(tab_count() - 1).width(); 1798 const int last_tab_x = width() - new_tab_button_width() - last_tab_width; 1799 if (active_index == tab_count() - 1 && 1800 ideal_bounds(tab_count() - 1).x() == last_tab_x) 1801 return; 1802 const int adjusted_delta = 1803 std::min(last_tab_x - 1804 kStackedPadding * std::min(tab_count() - active_index - 1, 1805 kMaxStackedCount) - 1806 ideal_bounds(active_index).x(), 1807 delta); 1808 for (int last_index = tab_count() - 1, i = last_index; i >= active_index; 1809 --i) { 1810 const int max_x = last_tab_x - 1811 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding; 1812 gfx::Rect new_bounds(ideal_bounds(i)); 1813 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta); 1814 // Because of rounding not all tabs are the same width. Adjust the 1815 // position to accommodate this, otherwise the stacking is off. 1816 if (new_x == max_x && !tab_at(i)->data().mini && 1817 new_bounds.width() != last_tab_width) 1818 new_x += (last_tab_width - new_bounds.width()); 1819 new_bounds.set_x(new_x); 1820 set_ideal_bounds(i, new_bounds); 1821 } 1822 for (int i = active_index - 1; i >= 0; --i) { 1823 const int min_x = ideal_bounds(active_index).x() - 1824 std::min(active_index - i, kMaxStackedCount) * kStackedPadding; 1825 gfx::Rect new_bounds(ideal_bounds(i)); 1826 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta)); 1827 set_ideal_bounds(i, new_bounds); 1828 } 1829 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x()) 1830 newtab_button_->SetVisible(false); 1831 } 1832 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_); 1833 SchedulePaint(); 1834} 1835 1836bool TabStrip::IsStackingDraggedTabs() const { 1837 return drag_controller_.get() && drag_controller_->started_drag() && 1838 (drag_controller_->move_behavior() == 1839 TabDragController::MOVE_VISIBILE_TABS); 1840} 1841 1842void TabStrip::LayoutDraggedTabsAt(const std::vector<Tab*>& tabs, 1843 Tab* active_tab, 1844 const gfx::Point& location, 1845 bool initial_drag) { 1846 // Immediately hide the new tab button if the last tab is being dragged. 1847 if (tab_at(tab_count() - 1)->dragging()) 1848 newtab_button_->SetVisible(false); 1849 std::vector<gfx::Rect> bounds; 1850 CalculateBoundsForDraggedTabs(tabs, &bounds); 1851 DCHECK_EQ(tabs.size(), bounds.size()); 1852 int active_tab_model_index = GetModelIndexOfTab(active_tab); 1853 int active_tab_index = static_cast<int>( 1854 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin()); 1855 for (size_t i = 0; i < tabs.size(); ++i) { 1856 Tab* tab = tabs[i]; 1857 gfx::Rect new_bounds = bounds[i]; 1858 new_bounds.Offset(location.x(), location.y()); 1859 int consecutive_index = 1860 active_tab_model_index - (active_tab_index - static_cast<int>(i)); 1861 // If this is the initial layout during a drag and the tabs aren't 1862 // consecutive animate the view into position. Do the same if the tab is 1863 // already animating (which means we previously caused it to animate). 1864 if ((initial_drag && 1865 GetModelIndexOfTab(tabs[i]) != consecutive_index) || 1866 bounds_animator_.IsAnimating(tabs[i])) { 1867 bounds_animator_.SetTargetBounds(tabs[i], new_bounds); 1868 } else { 1869 tab->SetBoundsRect(new_bounds); 1870 } 1871 } 1872} 1873 1874void TabStrip::CalculateBoundsForDraggedTabs(const std::vector<Tab*>& tabs, 1875 std::vector<gfx::Rect>* bounds) { 1876 int x = 0; 1877 for (size_t i = 0; i < tabs.size(); ++i) { 1878 Tab* tab = tabs[i]; 1879 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini) 1880 x += kMiniToNonMiniGap; 1881 gfx::Rect new_bounds = tab->bounds(); 1882 new_bounds.set_origin(gfx::Point(x, 0)); 1883 bounds->push_back(new_bounds); 1884 x += tab->width() + tab_h_offset(); 1885 } 1886} 1887 1888int TabStrip::GetSizeNeededForTabs(const std::vector<Tab*>& tabs) { 1889 int width = 0; 1890 for (size_t i = 0; i < tabs.size(); ++i) { 1891 Tab* tab = tabs[i]; 1892 width += tab->width(); 1893 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini) 1894 width += kMiniToNonMiniGap; 1895 } 1896 if (tabs.size() > 0) 1897 width += tab_h_offset() * static_cast<int>(tabs.size() - 1); 1898 return width; 1899} 1900 1901void TabStrip::RemoveTabFromViewModel(int index) { 1902 // We still need to paint the tab until we actually remove it. Put it 1903 // in tabs_closing_map_ so we can find it. 1904 tabs_closing_map_[index].push_back(tab_at(index)); 1905 UpdateTabsClosingMap(index + 1, -1); 1906 tabs_.Remove(index); 1907} 1908 1909void TabStrip::RemoveAndDeleteTab(Tab* tab) { 1910 scoped_ptr<Tab> deleter(tab); 1911 for (TabsClosingMap::iterator i(tabs_closing_map_.begin()); 1912 i != tabs_closing_map_.end(); ++i) { 1913 std::vector<Tab*>::iterator j = 1914 std::find(i->second.begin(), i->second.end(), tab); 1915 if (j != i->second.end()) { 1916 i->second.erase(j); 1917 if (i->second.empty()) 1918 tabs_closing_map_.erase(i); 1919 return; 1920 } 1921 } 1922 NOTREACHED(); 1923} 1924 1925void TabStrip::UpdateTabsClosingMap(int index, int delta) { 1926 if (tabs_closing_map_.empty()) 1927 return; 1928 1929 if (delta == -1 && 1930 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() && 1931 tabs_closing_map_.find(index) != tabs_closing_map_.end()) { 1932 const std::vector<Tab*>& tabs(tabs_closing_map_[index]); 1933 tabs_closing_map_[index - 1].insert( 1934 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end()); 1935 } 1936 TabsClosingMap updated_map; 1937 for (TabsClosingMap::iterator i(tabs_closing_map_.begin()); 1938 i != tabs_closing_map_.end(); ++i) { 1939 if (i->first > index) 1940 updated_map[i->first + delta] = i->second; 1941 else if (i->first < index) 1942 updated_map[i->first] = i->second; 1943 } 1944 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end()) 1945 updated_map[index + delta] = tabs_closing_map_[index]; 1946 tabs_closing_map_.swap(updated_map); 1947} 1948 1949void TabStrip::StartedDraggingTabs(const std::vector<Tab*>& tabs) { 1950 // Let the controller know that the user started dragging tabs. 1951 controller()->OnStartedDraggingTabs(); 1952 1953 // Hide the new tab button immediately if we didn't originate the drag. 1954 if (!drag_controller_.get()) 1955 newtab_button_->SetVisible(false); 1956 1957 PrepareForAnimation(); 1958 1959 // Reset dragging state of existing tabs. 1960 for (int i = 0; i < tab_count(); ++i) 1961 tab_at(i)->set_dragging(false); 1962 1963 for (size_t i = 0; i < tabs.size(); ++i) { 1964 tabs[i]->set_dragging(true); 1965 bounds_animator_.StopAnimatingView(tabs[i]); 1966 } 1967 1968 // Move the dragged tabs to their ideal bounds. 1969 GenerateIdealBounds(); 1970 1971 // Sets the bounds of the dragged tabs. 1972 for (size_t i = 0; i < tabs.size(); ++i) { 1973 int tab_data_index = GetModelIndexOfTab(tabs[i]); 1974 DCHECK_NE(-1, tab_data_index); 1975 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index)); 1976 } 1977 SchedulePaint(); 1978} 1979 1980void TabStrip::DraggedTabsDetached() { 1981 // Let the controller know that the user is not dragging this tabstrip's tabs 1982 // anymore. 1983 controller()->OnStoppedDraggingTabs(); 1984 newtab_button_->SetVisible(true); 1985} 1986 1987void TabStrip::StoppedDraggingTabs(const std::vector<Tab*>& tabs, 1988 const std::vector<int>& initial_positions, 1989 bool move_only, 1990 bool completed) { 1991 // Let the controller know that the user stopped dragging tabs. 1992 controller()->OnStoppedDraggingTabs(); 1993 1994 newtab_button_->SetVisible(true); 1995 if (move_only && touch_layout_.get()) { 1996 if (completed) { 1997 touch_layout_->SizeToFit(); 1998 } else { 1999 SetIdealBoundsFromPositions(initial_positions); 2000 } 2001 } 2002 bool is_first_tab = true; 2003 for (size_t i = 0; i < tabs.size(); ++i) 2004 StoppedDraggingTab(tabs[i], &is_first_tab); 2005} 2006 2007void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) { 2008 int tab_data_index = GetModelIndexOfTab(tab); 2009 if (tab_data_index == -1) { 2010 // The tab was removed before the drag completed. Don't do anything. 2011 return; 2012 } 2013 2014 if (*is_first_tab) { 2015 *is_first_tab = false; 2016 PrepareForAnimation(); 2017 2018 // Animate the view back to its correct position. 2019 GenerateIdealBounds(); 2020 AnimateToIdealBounds(); 2021 } 2022 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index)); 2023 // Install a delegate to reset the dragging state when done. We have to leave 2024 // dragging true for the tab otherwise it'll draw beneath the new tab button. 2025 bounds_animator_.SetAnimationDelegate( 2026 tab, new ResetDraggingStateDelegate(tab), true); 2027} 2028 2029void TabStrip::OwnDragController(TabDragController* controller) { 2030 // Typically, ReleaseDragController() and OwnDragController() calls are paired 2031 // via corresponding calls to TabDragController::Detach() and 2032 // TabDragController::Attach(). There is one exception to that rule: when a 2033 // drag might start, we create a TabDragController that is owned by the 2034 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts, 2035 // we then call Attach() on the source tabstrip, but since the source tabstrip 2036 // already owns the TabDragController, so we don't need to do anything. 2037 if (controller != drag_controller_.get()) 2038 drag_controller_.reset(controller); 2039} 2040 2041void TabStrip::DestroyDragController() { 2042 newtab_button_->SetVisible(true); 2043 drag_controller_.reset(); 2044} 2045 2046TabDragController* TabStrip::ReleaseDragController() { 2047 return drag_controller_.release(); 2048} 2049 2050void TabStrip::PaintClosingTabs(gfx::Canvas* canvas, int index) { 2051 if (tabs_closing_map_.find(index) == tabs_closing_map_.end()) 2052 return; 2053 2054 const std::vector<Tab*>& tabs = tabs_closing_map_[index]; 2055 for (std::vector<Tab*>::const_reverse_iterator i(tabs.rbegin()); 2056 i != tabs.rend(); ++i) { 2057 (*i)->Paint(canvas); 2058 } 2059} 2060 2061void TabStrip::UpdateLayoutTypeFromMouseEvent(views::View* source, 2062 const ui::MouseEvent& event) { 2063 if (!GetAdjustLayout()) 2064 return; 2065 2066 // The following code attempts to switch to TAB_STRIP_LAYOUT_SHRINK when the 2067 // mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and 2068 // TAB_STRIP_LAYOUT_STACKED when a touch device is used. This is made 2069 // problematic by windows generating mouse move events that do not clearly 2070 // indicate the move is the result of a touch device. This assumes a real 2071 // mouse is used if |kMouseMoveCountBeforeConsiderReal| mouse move events are 2072 // received within the time window |kMouseMoveTimeMS|. At the time we get a 2073 // mouse press we know whether its from a touch device or not, but we don't 2074 // layout then else everything shifts. Instead we wait for the release. 2075 // 2076 // TODO(sky): revisit this when touch events are really plumbed through. 2077 2078 switch (event.type()) { 2079 case ui::ET_MOUSE_PRESSED: 2080 mouse_move_count_ = 0; 2081 last_mouse_move_time_ = base::TimeTicks(); 2082 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0); 2083 if (reset_to_shrink_on_exit_ && touch_layout_.get()) { 2084 gfx::Point tab_strip_point(event.location()); 2085 views::View::ConvertPointToTarget(source, this, &tab_strip_point); 2086 Tab* tab = FindTabForEvent(tab_strip_point); 2087 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) { 2088 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true); 2089 controller_->LayoutTypeMaybeChanged(); 2090 } 2091 } 2092 break; 2093 2094 case ui::ET_MOUSE_MOVED: { 2095#if defined(USE_ASH) 2096 // Ash does not synthesize mouse events from touch events. 2097 SetResetToShrinkOnExit(true); 2098#else 2099 gfx::Point location(event.location()); 2100 ConvertPointToTarget(source, this, &location); 2101 if (location == last_mouse_move_location_) 2102 return; // Ignore spurious moves. 2103 last_mouse_move_location_ = location; 2104 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 && 2105 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) { 2106 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() < 2107 kMouseMoveTimeMS) { 2108 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal) 2109 SetResetToShrinkOnExit(true); 2110 } else { 2111 mouse_move_count_ = 1; 2112 last_mouse_move_time_ = base::TimeTicks::Now(); 2113 } 2114 } else { 2115 last_mouse_move_time_ = base::TimeTicks(); 2116 } 2117#endif 2118 break; 2119 } 2120 2121 case ui::ET_MOUSE_RELEASED: { 2122 gfx::Point location(event.location()); 2123 ConvertPointToTarget(source, this, &location); 2124 last_mouse_move_location_ = location; 2125 mouse_move_count_ = 0; 2126 last_mouse_move_time_ = base::TimeTicks(); 2127 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) { 2128 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true); 2129 controller_->LayoutTypeMaybeChanged(); 2130 } 2131 break; 2132 } 2133 2134 default: 2135 break; 2136 } 2137} 2138 2139void TabStrip::GetCurrentTabWidths(double* unselected_width, 2140 double* selected_width) const { 2141 *unselected_width = current_unselected_width_; 2142 *selected_width = current_selected_width_; 2143} 2144 2145void TabStrip::GetDesiredTabWidths(int tab_count, 2146 int mini_tab_count, 2147 double* unselected_width, 2148 double* selected_width) const { 2149 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count); 2150 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width(); 2151 const double min_selected_width = Tab::GetMinimumSelectedSize().width(); 2152 2153 *unselected_width = min_unselected_width; 2154 *selected_width = min_selected_width; 2155 2156 if (tab_count == 0) { 2157 // Return immediately to avoid divide-by-zero below. 2158 return; 2159 } 2160 2161 // Determine how much space we can actually allocate to tabs. 2162 int available_width; 2163 if (available_width_for_tabs_ < 0) { 2164 available_width = width() - new_tab_button_width(); 2165 } else { 2166 // Interesting corner case: if |available_width_for_tabs_| > the result 2167 // of the calculation in the conditional arm above, the strip is in 2168 // overflow. We can either use the specified width or the true available 2169 // width here; the first preserves the consistent "leave the last tab under 2170 // the user's mouse so they can close many tabs" behavior at the cost of 2171 // prolonging the glitchy appearance of the overflow state, while the second 2172 // gets us out of overflow as soon as possible but forces the user to move 2173 // their mouse for a few tabs' worth of closing. We choose visual 2174 // imperfection over behavioral imperfection and select the first option. 2175 available_width = available_width_for_tabs_; 2176 } 2177 2178 if (mini_tab_count > 0) { 2179 available_width -= mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset()); 2180 tab_count -= mini_tab_count; 2181 if (tab_count == 0) { 2182 *selected_width = *unselected_width = Tab::GetStandardSize().width(); 2183 return; 2184 } 2185 // Account for gap between the last mini-tab and first non-mini-tab. 2186 available_width -= kMiniToNonMiniGap; 2187 } 2188 2189 // Calculate the desired tab widths by dividing the available space into equal 2190 // portions. Don't let tabs get larger than the "standard width" or smaller 2191 // than the minimum width for each type, respectively. 2192 const int total_offset = tab_h_offset() * (tab_count - 1); 2193 const double desired_tab_width = std::min((static_cast<double>( 2194 available_width - total_offset) / static_cast<double>(tab_count)), 2195 static_cast<double>(Tab::GetStandardSize().width())); 2196 *unselected_width = std::max(desired_tab_width, min_unselected_width); 2197 *selected_width = std::max(desired_tab_width, min_selected_width); 2198 2199 // When there are multiple tabs, we'll have one selected and some unselected 2200 // tabs. If the desired width was between the minimum sizes of these types, 2201 // try to shrink the tabs with the smaller minimum. For example, if we have 2202 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If 2203 // selected tabs have a minimum width of 4 and unselected tabs have a minimum 2204 // width of 1, the above code would set *unselected_width = 2.5, 2205 // *selected_width = 4, which results in a total width of 11.5. Instead, we 2206 // want to set *unselected_width = 2, *selected_width = 4, for a total width 2207 // of 10. 2208 if (tab_count > 1) { 2209 if ((min_unselected_width < min_selected_width) && 2210 (desired_tab_width < min_selected_width)) { 2211 // Unselected width = (total width - selected width) / (num_tabs - 1) 2212 *unselected_width = std::max(static_cast<double>( 2213 available_width - total_offset - min_selected_width) / 2214 static_cast<double>(tab_count - 1), min_unselected_width); 2215 } else if ((min_unselected_width > min_selected_width) && 2216 (desired_tab_width < min_unselected_width)) { 2217 // Selected width = (total width - (unselected width * (num_tabs - 1))) 2218 *selected_width = std::max(available_width - total_offset - 2219 (min_unselected_width * (tab_count - 1)), min_selected_width); 2220 } 2221 } 2222} 2223 2224void TabStrip::ResizeLayoutTabs() { 2225 // We've been called back after the TabStrip has been emptied out (probably 2226 // just prior to the window being destroyed). We need to do nothing here or 2227 // else GetTabAt below will crash. 2228 if (tab_count() == 0) 2229 return; 2230 2231 // It is critically important that this is unhooked here, otherwise we will 2232 // keep spying on messages forever. 2233 RemoveMessageLoopObserver(); 2234 2235 in_tab_close_ = false; 2236 available_width_for_tabs_ = -1; 2237 int mini_tab_count = GetMiniTabCount(); 2238 if (mini_tab_count == tab_count()) { 2239 // Only mini-tabs, we know the tab widths won't have changed (all 2240 // mini-tabs have the same width), so there is nothing to do. 2241 return; 2242 } 2243 // Don't try and avoid layout based on tab sizes. If tabs are small enough 2244 // then the width of the active tab may not change, but other widths may 2245 // have. This is particularly important if we've overflowed (all tabs are at 2246 // the min). 2247 StartResizeLayoutAnimation(); 2248} 2249 2250void TabStrip::ResizeLayoutTabsFromTouch() { 2251 // Don't resize if the user is interacting with the tabstrip. 2252 if (!drag_controller_.get()) 2253 ResizeLayoutTabs(); 2254 else 2255 StartResizeLayoutTabsFromTouchTimer(); 2256} 2257 2258void TabStrip::StartResizeLayoutTabsFromTouchTimer() { 2259 resize_layout_timer_.Stop(); 2260 resize_layout_timer_.Start( 2261 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS), 2262 this, &TabStrip::ResizeLayoutTabsFromTouch); 2263} 2264 2265void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) { 2266 StopAnimating(false); 2267 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size())); 2268 for (int i = 0; i < tab_count(); ++i) 2269 tab_at(i)->SetBoundsRect(tab_bounds[i]); 2270} 2271 2272void TabStrip::AddMessageLoopObserver() { 2273 if (!mouse_watcher_.get()) { 2274 mouse_watcher_.reset( 2275 new views::MouseWatcher( 2276 new views::MouseWatcherViewHost( 2277 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)), 2278 this)); 2279 } 2280 mouse_watcher_->Start(); 2281} 2282 2283void TabStrip::RemoveMessageLoopObserver() { 2284 mouse_watcher_.reset(NULL); 2285} 2286 2287gfx::Rect TabStrip::GetDropBounds(int drop_index, 2288 bool drop_before, 2289 bool* is_beneath) { 2290 DCHECK_NE(drop_index, -1); 2291 int center_x; 2292 if (drop_index < tab_count()) { 2293 Tab* tab = tab_at(drop_index); 2294 if (drop_before) 2295 center_x = tab->x() - (tab_h_offset() / 2); 2296 else 2297 center_x = tab->x() + (tab->width() / 2); 2298 } else { 2299 Tab* last_tab = tab_at(drop_index - 1); 2300 center_x = last_tab->x() + last_tab->width() + (tab_h_offset() / 2); 2301 } 2302 2303 // Mirror the center point if necessary. 2304 center_x = GetMirroredXInView(center_x); 2305 2306 // Determine the screen bounds. 2307 gfx::Point drop_loc(center_x - drop_indicator_width / 2, 2308 -drop_indicator_height); 2309 ConvertPointToScreen(this, &drop_loc); 2310 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, 2311 drop_indicator_height); 2312 2313 // If the rect doesn't fit on the monitor, push the arrow to the bottom. 2314 gfx::Screen* screen = gfx::Screen::GetScreenFor(GetWidget()->GetNativeView()); 2315 gfx::Display display = screen->GetDisplayMatching(drop_bounds); 2316 *is_beneath = !display.bounds().Contains(drop_bounds); 2317 if (*is_beneath) 2318 drop_bounds.Offset(0, drop_bounds.height() + height()); 2319 2320 return drop_bounds; 2321} 2322 2323void TabStrip::UpdateDropIndex(const DropTargetEvent& event) { 2324 // If the UI layout is right-to-left, we need to mirror the mouse 2325 // coordinates since we calculate the drop index based on the 2326 // original (and therefore non-mirrored) positions of the tabs. 2327 const int x = GetMirroredXInView(event.x()); 2328 // We don't allow replacing the urls of mini-tabs. 2329 for (int i = GetMiniTabCount(); i < tab_count(); ++i) { 2330 Tab* tab = tab_at(i); 2331 const int tab_max_x = tab->x() + tab->width(); 2332 const int hot_width = tab->width() / kTabEdgeRatioInverse; 2333 if (x < tab_max_x) { 2334 if (x < tab->x() + hot_width) 2335 SetDropIndex(i, true); 2336 else if (x >= tab_max_x - hot_width) 2337 SetDropIndex(i + 1, true); 2338 else 2339 SetDropIndex(i, false); 2340 return; 2341 } 2342 } 2343 2344 // The drop isn't over a tab, add it to the end. 2345 SetDropIndex(tab_count(), true); 2346} 2347 2348void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) { 2349 // Let the controller know of the index update. 2350 controller()->OnDropIndexUpdate(tab_data_index, drop_before); 2351 2352 if (tab_data_index == -1) { 2353 if (drop_info_.get()) 2354 drop_info_.reset(NULL); 2355 return; 2356 } 2357 2358 if (drop_info_.get() && drop_info_->drop_index == tab_data_index && 2359 drop_info_->drop_before == drop_before) { 2360 return; 2361 } 2362 2363 bool is_beneath; 2364 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before, 2365 &is_beneath); 2366 2367 if (!drop_info_.get()) { 2368 drop_info_.reset( 2369 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget())); 2370 } else { 2371 drop_info_->drop_index = tab_data_index; 2372 drop_info_->drop_before = drop_before; 2373 if (is_beneath == drop_info_->point_down) { 2374 drop_info_->point_down = !is_beneath; 2375 drop_info_->arrow_view->SetImage( 2376 GetDropArrowImage(drop_info_->point_down)); 2377 } 2378 } 2379 2380 // Reposition the window. Need to show it too as the window is initially 2381 // hidden. 2382 drop_info_->arrow_window->SetBounds(drop_bounds); 2383 drop_info_->arrow_window->Show(); 2384} 2385 2386int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) { 2387 const int source_ops = event.source_operations(); 2388 if (source_ops & ui::DragDropTypes::DRAG_COPY) 2389 return ui::DragDropTypes::DRAG_COPY; 2390 if (source_ops & ui::DragDropTypes::DRAG_LINK) 2391 return ui::DragDropTypes::DRAG_LINK; 2392 return ui::DragDropTypes::DRAG_MOVE; 2393} 2394 2395// static 2396gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) { 2397 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 2398 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); 2399} 2400 2401// TabStrip::DropInfo ---------------------------------------------------------- 2402 2403TabStrip::DropInfo::DropInfo(int drop_index, 2404 bool drop_before, 2405 bool point_down, 2406 views::Widget* context) 2407 : drop_index(drop_index), 2408 drop_before(drop_before), 2409 point_down(point_down) { 2410 arrow_view = new views::ImageView; 2411 arrow_view->SetImage(GetDropArrowImage(point_down)); 2412 2413 arrow_window = new views::Widget; 2414 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 2415 params.keep_on_top = true; 2416 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 2417 params.accept_events = false; 2418 params.can_activate = false; 2419 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height); 2420 params.context = context->GetNativeView(); 2421 arrow_window->Init(params); 2422 arrow_window->SetContentsView(arrow_view); 2423} 2424 2425TabStrip::DropInfo::~DropInfo() { 2426 // Close eventually deletes the window, which deletes arrow_view too. 2427 arrow_window->Close(); 2428} 2429 2430/////////////////////////////////////////////////////////////////////////////// 2431 2432void TabStrip::PrepareForAnimation() { 2433 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) { 2434 for (int i = 0; i < tab_count(); ++i) 2435 tab_at(i)->set_dragging(false); 2436 } 2437} 2438 2439void TabStrip::GenerateIdealBounds() { 2440 int new_tab_y = 0; 2441 2442 if (touch_layout_.get()) { 2443 if (tabs_.view_size() == 0) 2444 return; 2445 2446 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() + 2447 newtab_button_h_offset(); 2448 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); 2449 return; 2450 } 2451 2452 double unselected, selected; 2453 GetDesiredTabWidths(tab_count(), GetMiniTabCount(), &unselected, &selected); 2454 current_unselected_width_ = unselected; 2455 current_selected_width_ = selected; 2456 2457 // NOTE: This currently assumes a tab's height doesn't differ based on 2458 // selected state or the number of tabs in the strip! 2459 int tab_height = Tab::GetStandardSize().height(); 2460 int first_non_mini_index = 0; 2461 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index); 2462 for (int i = first_non_mini_index; i < tab_count(); ++i) { 2463 Tab* tab = tab_at(i); 2464 DCHECK(!tab->data().mini); 2465 double tab_width = tab->IsActive() ? selected : unselected; 2466 double end_of_tab = tab_x + tab_width; 2467 int rounded_tab_x = Round(tab_x); 2468 set_ideal_bounds( 2469 i, 2470 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, 2471 tab_height)); 2472 tab_x = end_of_tab + tab_h_offset(); 2473 } 2474 2475 // Update bounds of new tab button. 2476 int new_tab_x; 2477 if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 && 2478 !in_tab_close_) { 2479 // We're shrinking tabs, so we need to anchor the New Tab button to the 2480 // right edge of the TabStrip's bounds, rather than the right edge of the 2481 // right-most Tab, otherwise it'll bounce when animating. 2482 new_tab_x = width() - newtab_button_bounds_.width(); 2483 } else { 2484 new_tab_x = Round(tab_x - tab_h_offset()) + newtab_button_h_offset(); 2485 } 2486 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); 2487} 2488 2489int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) { 2490 int next_x = 0; 2491 int mini_width = Tab::GetMiniWidth(); 2492 int tab_height = Tab::GetStandardSize().height(); 2493 int index = 0; 2494 for (; index < tab_count() && tab_at(index)->data().mini; ++index) { 2495 set_ideal_bounds(index, 2496 gfx::Rect(next_x, 0, mini_width, tab_height)); 2497 next_x += mini_width + tab_h_offset(); 2498 } 2499 if (index > 0 && index < tab_count()) 2500 next_x += kMiniToNonMiniGap; 2501 if (first_non_mini_index) 2502 *first_non_mini_index = index; 2503 return next_x; 2504} 2505 2506// static 2507int TabStrip::new_tab_button_width() { 2508 return newtab_button_asset_width() + newtab_button_h_offset(); 2509} 2510 2511// static 2512int TabStrip::button_v_offset() { 2513 return newtab_button_v_offset(); 2514} 2515 2516int TabStrip::tab_area_width() const { 2517 return width() - new_tab_button_width(); 2518} 2519 2520void TabStrip::StartResizeLayoutAnimation() { 2521 PrepareForAnimation(); 2522 GenerateIdealBounds(); 2523 AnimateToIdealBounds(); 2524} 2525 2526void TabStrip::StartMiniTabAnimation() { 2527 in_tab_close_ = false; 2528 available_width_for_tabs_ = -1; 2529 2530 PrepareForAnimation(); 2531 2532 GenerateIdealBounds(); 2533 AnimateToIdealBounds(); 2534} 2535 2536void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) { 2537 // The user initiated the close. We want to persist the bounds of all the 2538 // existing tabs, so we manually shift ideal_bounds then animate. 2539 Tab* tab_closing = tab_at(model_index); 2540 int delta = tab_closing->width() + tab_h_offset(); 2541 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to 2542 // add the extra padding. 2543 DCHECK_NE(model_index + 1, tab_count()); 2544 if (tab_closing->data().mini && model_index + 1 < tab_count() && 2545 !tab_at(model_index + 1)->data().mini) { 2546 delta += kMiniToNonMiniGap; 2547 } 2548 2549 for (int i = model_index + 1; i < tab_count(); ++i) { 2550 gfx::Rect bounds = ideal_bounds(i); 2551 bounds.set_x(bounds.x() - delta); 2552 set_ideal_bounds(i, bounds); 2553 } 2554 2555 newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta); 2556 2557 PrepareForAnimation(); 2558 2559 tab_closing->set_closing(true); 2560 2561 // We still need to paint the tab until we actually remove it. Put it in 2562 // tabs_closing_map_ so we can find it. 2563 RemoveTabFromViewModel(model_index); 2564 2565 AnimateToIdealBounds(); 2566 2567 gfx::Rect tab_bounds = tab_closing->bounds(); 2568 tab_bounds.set_width(0); 2569 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds); 2570 2571 // Register delegate to do cleanup when done, BoundsAnimator takes 2572 // ownership of RemoveTabDelegate. 2573 bounds_animator_.SetAnimationDelegate( 2574 tab_closing, 2575 new RemoveTabDelegate(this, tab_closing), 2576 true); 2577} 2578 2579bool TabStrip::IsPointInTab(Tab* tab, 2580 const gfx::Point& point_in_tabstrip_coords) { 2581 gfx::Point point_in_tab_coords(point_in_tabstrip_coords); 2582 View::ConvertPointToTarget(this, tab, &point_in_tab_coords); 2583 return tab->HitTestPoint(point_in_tab_coords); 2584} 2585 2586int TabStrip::GetStartXForNormalTabs() const { 2587 int mini_tab_count = GetMiniTabCount(); 2588 if (mini_tab_count == 0) 2589 return 0; 2590 return mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset()) + 2591 kMiniToNonMiniGap; 2592} 2593 2594Tab* TabStrip::FindTabForEvent(const gfx::Point& point) { 2595 if (touch_layout_.get()) { 2596 int active_tab_index = touch_layout_->active_index(); 2597 if (active_tab_index != -1) { 2598 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1); 2599 if (!tab) 2600 tab = FindTabForEventFrom(point, active_tab_index + 1, 1); 2601 return tab; 2602 } else if (tab_count()) { 2603 return FindTabForEventFrom(point, 0, 1); 2604 } 2605 } else { 2606 for (int i = 0; i < tab_count(); ++i) { 2607 if (IsPointInTab(tab_at(i), point)) 2608 return tab_at(i); 2609 } 2610 } 2611 return NULL; 2612} 2613 2614Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point, 2615 int start, 2616 int delta) { 2617 // |start| equals tab_count() when there are only pinned tabs. 2618 if (start == tab_count()) 2619 start += delta; 2620 for (int i = start; i >= 0 && i < tab_count(); i += delta) { 2621 if (IsPointInTab(tab_at(i), point)) 2622 return tab_at(i); 2623 } 2624 return NULL; 2625} 2626 2627views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) { 2628 // The display order doesn't necessarily match the child list order, so we 2629 // walk the display list hit-testing Tabs. Since the active tab always 2630 // renders on top of adjacent tabs, it needs to be hit-tested before any 2631 // left-adjacent Tab, so we look ahead for it as we walk. 2632 for (int i = 0; i < tab_count(); ++i) { 2633 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL; 2634 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point)) 2635 return next_tab; 2636 if (IsPointInTab(tab_at(i), point)) 2637 return tab_at(i); 2638 } 2639 2640 return NULL; 2641} 2642 2643std::vector<int> TabStrip::GetTabXCoordinates() { 2644 std::vector<int> results; 2645 for (int i = 0; i < tab_count(); ++i) 2646 results.push_back(ideal_bounds(i).x()); 2647 return results; 2648} 2649 2650void TabStrip::SwapLayoutIfNecessary() { 2651 bool needs_touch = NeedsTouchLayout(); 2652 bool using_touch = touch_layout_.get() != NULL; 2653 if (needs_touch == using_touch) 2654 return; 2655 2656 if (needs_touch) { 2657 gfx::Size tab_size(Tab::GetMinimumSelectedSize()); 2658 tab_size.set_width(Tab::GetTouchWidth()); 2659 touch_layout_.reset(new StackedTabStripLayout( 2660 tab_size, 2661 tab_h_offset(), 2662 kStackedPadding, 2663 kMaxStackedCount, 2664 &tabs_)); 2665 touch_layout_->SetWidth(width() - new_tab_button_width()); 2666 // This has to be after SetWidth() as SetWidth() is going to reset the 2667 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how 2668 // many mini-tabs there are). 2669 GenerateIdealBoundsForMiniTabs(NULL); 2670 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(), 2671 GetMiniTabCount()); 2672 touch_layout_->SetActiveIndex(controller_->GetActiveIndex()); 2673 } else { 2674 touch_layout_.reset(); 2675 } 2676 PrepareForAnimation(); 2677 GenerateIdealBounds(); 2678 AnimateToIdealBounds(); 2679} 2680 2681bool TabStrip::NeedsTouchLayout() const { 2682 if (layout_type_ == TAB_STRIP_LAYOUT_SHRINK) 2683 return false; 2684 2685 int mini_tab_count = GetMiniTabCount(); 2686 int normal_count = tab_count() - mini_tab_count; 2687 if (normal_count <= 1 || normal_count == mini_tab_count) 2688 return false; 2689 int x = GetStartXForNormalTabs(); 2690 int available_width = width() - x - new_tab_button_width(); 2691 return (Tab::GetTouchWidth() * normal_count + 2692 tab_h_offset() * (normal_count - 1)) > available_width; 2693} 2694 2695void TabStrip::SetResetToShrinkOnExit(bool value) { 2696 if (!GetAdjustLayout()) 2697 return; 2698 2699 if (value && layout_type_ == TAB_STRIP_LAYOUT_SHRINK) 2700 value = false; // We're already at TAB_STRIP_LAYOUT_SHRINK. 2701 2702 if (value == reset_to_shrink_on_exit_) 2703 return; 2704 2705 reset_to_shrink_on_exit_ = value; 2706 // Add an observer so we know when the mouse moves out of the tabstrip. 2707 if (reset_to_shrink_on_exit_) 2708 AddMessageLoopObserver(); 2709 else 2710 RemoveMessageLoopObserver(); 2711} 2712 2713bool TabStrip::GetAdjustLayout() const { 2714 if (!adjust_layout_) 2715 return false; 2716 2717#if !defined(OS_CHROMEOS) 2718 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH) 2719 return false; 2720#endif 2721 2722 return true; 2723} 2724