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