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