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