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