tab_strip.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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_CHROMEOS) 1131 // TODO(wittman): This is a temporary workaround to avoid crbug.com/275274 and 1132 // crbug.com/274856 in M30 and should be reverted after merge, as we have a 1133 // solution to these bugs in trunk. 1134 // 1135 // Don't allow detaching if the tab has a WebContentsModalDialogView opened. 1136 if (controller()->IsTabShowingWebViewModalDialog(GetModelIndexOfTab(tab))) 1137 detach_behavior = TabDragController::NOT_DETACHABLE; 1138#endif 1139 1140#if defined(OS_WIN) 1141 // It doesn't make sense to drag tabs out on Win8's single window Metro mode. 1142 if (win8::IsSingleWindowMetroMode()) 1143 detach_behavior = TabDragController::NOT_DETACHABLE; 1144#endif 1145 // Gestures don't automatically do a capture. We don't allow multiple drags at 1146 // the same time, so we explicitly capture. 1147 if (event.type() == ui::ET_GESTURE_BEGIN) 1148 widget->SetCapture(this); 1149 drag_controller_.reset(new TabDragController); 1150 drag_controller_->Init( 1151 this, tab, tabs, gfx::Point(x, y), event.x(), selection_model, 1152 detach_behavior, move_behavior, EventSourceFromEvent(event)); 1153} 1154 1155void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) { 1156 if (drag_controller_.get() && 1157 drag_controller_->event_source() == EventSourceFromEvent(event)) { 1158 gfx::Point screen_location(event.location()); 1159 views::View::ConvertPointToScreen(view, &screen_location); 1160 drag_controller_->Drag(screen_location); 1161 } 1162} 1163 1164bool TabStrip::EndDrag(EndDragReason reason) { 1165 if (!drag_controller_.get()) 1166 return false; 1167 bool started_drag = drag_controller_->started_drag(); 1168 drag_controller_->EndDrag(reason); 1169 return started_drag; 1170} 1171 1172Tab* TabStrip::GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) { 1173 gfx::Point local_point = tab_in_tab_coordinates; 1174 ConvertPointToTarget(tab, this, &local_point); 1175 1176 views::View* view = GetEventHandlerForPoint(local_point); 1177 if (!view) 1178 return NULL; // No tab contains the point. 1179 1180 // Walk up the view hierarchy until we find a tab, or the TabStrip. 1181 while (view && view != this && view->id() != VIEW_ID_TAB) 1182 view = view->parent(); 1183 1184 return view && view->id() == VIEW_ID_TAB ? static_cast<Tab*>(view) : NULL; 1185} 1186 1187void TabStrip::OnMouseEventInTab(views::View* source, 1188 const ui::MouseEvent& event) { 1189 UpdateLayoutTypeFromMouseEvent(source, event); 1190} 1191 1192bool TabStrip::ShouldPaintTab(const Tab* tab, gfx::Rect* clip) { 1193 // Only touch layout needs to restrict the clip. 1194 if (!(touch_layout_.get() || IsStackingDraggedTabs())) 1195 return true; 1196 1197 int index = GetModelIndexOfTab(tab); 1198 if (index == -1) 1199 return true; // Tab is closing, paint it all. 1200 1201 int active_index = IsStackingDraggedTabs() ? 1202 controller_->GetActiveIndex() : touch_layout_->active_index(); 1203 if (active_index == tab_count()) 1204 active_index--; 1205 1206 if (index < active_index) { 1207 if (tab_at(index)->x() == tab_at(index + 1)->x()) 1208 return false; 1209 1210 if (tab_at(index)->x() > tab_at(index + 1)->x()) 1211 return true; // Can happen during dragging. 1212 1213 clip->SetRect(0, 0, tab_at(index + 1)->x() - tab_at(index)->x() + 1214 stacked_tab_left_clip(), 1215 tab_at(index)->height()); 1216 } else if (index > active_index && index > 0) { 1217 const gfx::Rect& tab_bounds(tab_at(index)->bounds()); 1218 const gfx::Rect& previous_tab_bounds(tab_at(index - 1)->bounds()); 1219 if (tab_bounds.x() == previous_tab_bounds.x()) 1220 return false; 1221 1222 if (tab_bounds.x() < previous_tab_bounds.x()) 1223 return true; // Can happen during dragging. 1224 1225 if (previous_tab_bounds.right() + tab_h_offset() != tab_bounds.x()) { 1226 int x = previous_tab_bounds.right() - tab_bounds.x() - 1227 stacked_tab_right_clip(); 1228 clip->SetRect(x, 0, tab_bounds.width() - x, tab_bounds.height()); 1229 } 1230 } 1231 return true; 1232} 1233 1234bool TabStrip::IsImmersiveStyle() const { 1235 return immersive_style_; 1236} 1237 1238void TabStrip::MouseMovedOutOfHost() { 1239 ResizeLayoutTabs(); 1240 if (reset_to_shrink_on_exit_) { 1241 reset_to_shrink_on_exit_ = false; 1242 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true); 1243 controller_->LayoutTypeMaybeChanged(); 1244 } 1245} 1246 1247/////////////////////////////////////////////////////////////////////////////// 1248// TabStrip, views::View overrides: 1249 1250void TabStrip::Layout() { 1251 // Only do a layout if our size changed. 1252 if (last_layout_size_ == size()) 1253 return; 1254 if (IsDragSessionActive()) 1255 return; 1256 DoLayout(); 1257} 1258 1259void TabStrip::PaintChildren(gfx::Canvas* canvas) { 1260 // The view order doesn't match the paint order (tabs_ contains the tab 1261 // ordering). Additionally we need to paint the tabs that are closing in 1262 // |tabs_closing_map_|. 1263 Tab* active_tab = NULL; 1264 std::vector<Tab*> tabs_dragging; 1265 std::vector<Tab*> selected_tabs; 1266 int selected_tab_count = 0; 1267 bool is_dragging = false; 1268 int active_tab_index = -1; 1269 // Since |touch_layout_| is created based on number of tabs and width we use 1270 // the ideal state to determine if we should paint stacked. This minimizes 1271 // painting changes as we switch between the two. 1272 const bool stacking = (layout_type_ == TAB_STRIP_LAYOUT_STACKED) || 1273 IsStackingDraggedTabs(); 1274 1275 const chrome::HostDesktopType host_desktop_type = 1276 chrome::GetHostDesktopTypeForNativeView(GetWidget()->GetNativeView()); 1277 const int inactive_tab_alpha = 1278 host_desktop_type == chrome::HOST_DESKTOP_TYPE_ASH ? 1279 kInactiveTabAndNewTabButtonAlphaAsh : 1280 kInactiveTabAndNewTabButtonAlpha; 1281 1282 if (inactive_tab_alpha < 255) 1283 canvas->SaveLayerAlpha(inactive_tab_alpha); 1284 1285 PaintClosingTabs(canvas, tab_count()); 1286 1287 for (int i = tab_count() - 1; i >= 0; --i) { 1288 Tab* tab = tab_at(i); 1289 if (tab->IsSelected()) 1290 selected_tab_count++; 1291 if (tab->dragging() && !stacking) { 1292 is_dragging = true; 1293 if (tab->IsActive()) { 1294 active_tab = tab; 1295 active_tab_index = i; 1296 } else { 1297 tabs_dragging.push_back(tab); 1298 } 1299 } else if (!tab->IsActive()) { 1300 if (!tab->IsSelected()) { 1301 if (!stacking) 1302 tab->Paint(canvas); 1303 } else { 1304 selected_tabs.push_back(tab); 1305 } 1306 } else { 1307 active_tab = tab; 1308 active_tab_index = i; 1309 } 1310 PaintClosingTabs(canvas, i); 1311 } 1312 1313 // Draw from the left and then the right if we're in touch mode. 1314 if (stacking && active_tab_index >= 0) { 1315 for (int i = 0; i < active_tab_index; ++i) { 1316 Tab* tab = tab_at(i); 1317 tab->Paint(canvas); 1318 } 1319 1320 for (int i = tab_count() - 1; i > active_tab_index; --i) { 1321 Tab* tab = tab_at(i); 1322 tab->Paint(canvas); 1323 } 1324 } 1325 if (inactive_tab_alpha < 255) 1326 canvas->Restore(); 1327 1328 if (GetWidget()->ShouldUseNativeFrame()) { 1329 // Make sure non-active tabs are somewhat transparent. 1330 SkPaint paint; 1331 // If there are multiple tabs selected, fade non-selected tabs more to make 1332 // the selected tabs more noticable. 1333 int alpha = selected_tab_count > 1 ? 1334 kNativeFrameInactiveTabAlphaMultiSelection : 1335 kNativeFrameInactiveTabAlpha; 1336 paint.setColor(SkColorSetARGB(alpha, 255, 255, 255)); 1337 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 1338 paint.setStyle(SkPaint::kFill_Style); 1339 // The tabstrip area overlaps the toolbar area by 2 px. 1340 canvas->DrawRect(gfx::Rect(0, 0, width(), height() - 2), paint); 1341 } 1342 1343 // Now selected but not active. We don't want these dimmed if using native 1344 // frame, so they're painted after initial pass. 1345 for (size_t i = 0; i < selected_tabs.size(); ++i) 1346 selected_tabs[i]->Paint(canvas); 1347 1348 // Next comes the active tab. 1349 if (active_tab && !is_dragging) 1350 active_tab->Paint(canvas); 1351 1352 // Paint the New Tab button. 1353 if (inactive_tab_alpha < 255) 1354 canvas->SaveLayerAlpha(inactive_tab_alpha); 1355 newtab_button_->Paint(canvas); 1356 if (inactive_tab_alpha < 255) 1357 canvas->Restore(); 1358 1359 // And the dragged tabs. 1360 for (size_t i = 0; i < tabs_dragging.size(); ++i) 1361 tabs_dragging[i]->Paint(canvas); 1362 1363 // If the active tab is being dragged, it goes last. 1364 if (active_tab && is_dragging) 1365 active_tab->Paint(canvas); 1366} 1367 1368const char* TabStrip::GetClassName() const { 1369 return kViewClassName; 1370} 1371 1372gfx::Size TabStrip::GetPreferredSize() { 1373 // For stacked tabs the minimum size is calculated as the size needed to 1374 // handle showing any number of tabs. Otherwise report the minimum width as 1375 // the size required for a single selected tab plus the new tab button. Don't 1376 // base it on the actual number of tabs because it's undesirable to have the 1377 // minimum window size change when a new tab is opened. 1378 int needed_width; 1379 if (touch_layout_.get() || adjust_layout_) { 1380 needed_width = Tab::GetTouchWidth() + 1381 (2 * kStackedPadding * kMaxStackedCount); 1382 } else { 1383 needed_width = Tab::GetMinimumSelectedSize().width(); 1384 } 1385 needed_width += new_tab_button_width(); 1386 if (immersive_style_) 1387 return gfx::Size(needed_width, Tab::GetImmersiveHeight()); 1388 return gfx::Size(needed_width, Tab::GetMinimumUnselectedSize().height()); 1389} 1390 1391void TabStrip::OnDragEntered(const DropTargetEvent& event) { 1392 // Force animations to stop, otherwise it makes the index calculation tricky. 1393 StopAnimating(true); 1394 1395 UpdateDropIndex(event); 1396} 1397 1398int TabStrip::OnDragUpdated(const DropTargetEvent& event) { 1399 UpdateDropIndex(event); 1400 return GetDropEffect(event); 1401} 1402 1403void TabStrip::OnDragExited() { 1404 SetDropIndex(-1, false); 1405} 1406 1407int TabStrip::OnPerformDrop(const DropTargetEvent& event) { 1408 if (!drop_info_.get()) 1409 return ui::DragDropTypes::DRAG_NONE; 1410 1411 const int drop_index = drop_info_->drop_index; 1412 const bool drop_before = drop_info_->drop_before; 1413 1414 // Hide the drop indicator. 1415 SetDropIndex(-1, false); 1416 1417 GURL url; 1418 string16 title; 1419 if (!event.data().GetURLAndTitle(&url, &title) || !url.is_valid()) 1420 return ui::DragDropTypes::DRAG_NONE; 1421 1422 controller()->PerformDrop(drop_before, drop_index, url); 1423 1424 return GetDropEffect(event); 1425} 1426 1427void TabStrip::GetAccessibleState(ui::AccessibleViewState* state) { 1428 state->role = ui::AccessibilityTypes::ROLE_PAGETABLIST; 1429} 1430 1431views::View* TabStrip::GetEventHandlerForPoint(const gfx::Point& point) { 1432 if (!touch_layout_.get()) { 1433 // Return any view that isn't a Tab or this TabStrip immediately. We don't 1434 // want to interfere. 1435 views::View* v = View::GetEventHandlerForPoint(point); 1436 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName)) 1437 return v; 1438 1439 views::View* tab = FindTabHitByPoint(point); 1440 if (tab) 1441 return tab; 1442 } else { 1443 if (newtab_button_->visible()) { 1444 views::View* view = 1445 ConvertPointToViewAndGetEventHandler(this, newtab_button_, point); 1446 if (view) 1447 return view; 1448 } 1449 Tab* tab = FindTabForEvent(point); 1450 if (tab) 1451 return ConvertPointToViewAndGetEventHandler(this, tab, point); 1452 } 1453 return this; 1454} 1455 1456views::View* TabStrip::GetTooltipHandlerForPoint(const gfx::Point& point) { 1457 if (!HitTestPoint(point)) 1458 return NULL; 1459 1460 if (!touch_layout_.get()) { 1461 // Return any view that isn't a Tab or this TabStrip immediately. We don't 1462 // want to interfere. 1463 views::View* v = View::GetTooltipHandlerForPoint(point); 1464 if (v && v != this && strcmp(v->GetClassName(), Tab::kViewClassName)) 1465 return v; 1466 1467 views::View* tab = FindTabHitByPoint(point); 1468 if (tab) 1469 return tab; 1470 } else { 1471 if (newtab_button_->visible()) { 1472 views::View* view = 1473 ConvertPointToViewAndGetTooltipHandler(this, newtab_button_, point); 1474 if (view) 1475 return view; 1476 } 1477 Tab* tab = FindTabForEvent(point); 1478 if (tab) 1479 return ConvertPointToViewAndGetTooltipHandler(this, tab, point); 1480 } 1481 return this; 1482} 1483 1484// static 1485int TabStrip::GetImmersiveHeight() { 1486 return Tab::GetImmersiveHeight(); 1487} 1488 1489int TabStrip::GetMiniTabCount() const { 1490 int mini_count = 0; 1491 while (mini_count < tab_count() && tab_at(mini_count)->data().mini) 1492 mini_count++; 1493 return mini_count; 1494} 1495 1496/////////////////////////////////////////////////////////////////////////////// 1497// TabStrip, views::ButtonListener implementation: 1498 1499void TabStrip::ButtonPressed(views::Button* sender, const ui::Event& event) { 1500 if (sender == newtab_button_) { 1501 content::RecordAction(UserMetricsAction("NewTab_Button")); 1502 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", TabStripModel::NEW_TAB_BUTTON, 1503 TabStripModel::NEW_TAB_ENUM_COUNT); 1504 controller()->CreateNewTab(); 1505 if (event.type() == ui::ET_GESTURE_TAP) 1506 TouchUMA::RecordGestureAction(TouchUMA::GESTURE_NEWTAB_TAP); 1507 } 1508} 1509 1510/////////////////////////////////////////////////////////////////////////////// 1511// TabStrip, protected: 1512 1513// Overridden to support automation. See automation_proxy_uitest.cc. 1514const views::View* TabStrip::GetViewByID(int view_id) const { 1515 if (tab_count() > 0) { 1516 if (view_id == VIEW_ID_TAB_LAST) { 1517 return tab_at(tab_count() - 1); 1518 } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) { 1519 int index = view_id - VIEW_ID_TAB_0; 1520 if (index >= 0 && index < tab_count()) { 1521 return tab_at(index); 1522 } else { 1523 return NULL; 1524 } 1525 } 1526 } 1527 1528 return View::GetViewByID(view_id); 1529} 1530 1531bool TabStrip::OnMousePressed(const ui::MouseEvent& event) { 1532 UpdateLayoutTypeFromMouseEvent(this, event); 1533 // We can't return true here, else clicking in an empty area won't drag the 1534 // window. 1535 return false; 1536} 1537 1538bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) { 1539 ContinueDrag(this, event); 1540 return true; 1541} 1542 1543void TabStrip::OnMouseReleased(const ui::MouseEvent& event) { 1544 EndDrag(END_DRAG_COMPLETE); 1545 UpdateLayoutTypeFromMouseEvent(this, event); 1546} 1547 1548void TabStrip::OnMouseCaptureLost() { 1549 EndDrag(END_DRAG_CAPTURE_LOST); 1550} 1551 1552void TabStrip::OnMouseMoved(const ui::MouseEvent& event) { 1553 UpdateLayoutTypeFromMouseEvent(this, event); 1554} 1555 1556void TabStrip::OnMouseEntered(const ui::MouseEvent& event) { 1557 SetResetToShrinkOnExit(true); 1558} 1559 1560void TabStrip::OnGestureEvent(ui::GestureEvent* event) { 1561 SetResetToShrinkOnExit(false); 1562 switch (event->type()) { 1563 case ui::ET_GESTURE_END: 1564 EndDrag(END_DRAG_COMPLETE); 1565 if (adjust_layout_) { 1566 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true); 1567 controller_->LayoutTypeMaybeChanged(); 1568 } 1569 break; 1570 1571 case ui::ET_GESTURE_LONG_PRESS: 1572 if (drag_controller_.get()) 1573 drag_controller_->SetMoveBehavior(TabDragController::REORDER); 1574 break; 1575 1576 case ui::ET_GESTURE_LONG_TAP: { 1577 EndDrag(END_DRAG_CANCEL); 1578 gfx::Point local_point = event->location(); 1579 Tab* tab = FindTabForEvent(local_point); 1580 if (tab) { 1581 ConvertPointToScreen(this, &local_point); 1582 ShowContextMenuForTab(tab, local_point, ui::MENU_SOURCE_TOUCH); 1583 } 1584 break; 1585 } 1586 1587 case ui::ET_GESTURE_SCROLL_UPDATE: 1588 ContinueDrag(this, *event); 1589 break; 1590 1591 case ui::ET_GESTURE_BEGIN: 1592 EndDrag(END_DRAG_CANCEL); 1593 break; 1594 1595 case ui::ET_GESTURE_TAP: { 1596 const int active_index = controller_->GetActiveIndex(); 1597 DCHECK_NE(-1, active_index); 1598 Tab* active_tab = tab_at(active_index); 1599 TouchUMA::GestureActionType action = TouchUMA::GESTURE_TABNOSWITCH_TAP; 1600 if (active_tab->tab_activated_with_last_gesture_begin()) 1601 action = TouchUMA::GESTURE_TABSWITCH_TAP; 1602 TouchUMA::RecordGestureAction(action); 1603 break; 1604 } 1605 1606 default: 1607 break; 1608 } 1609 event->SetHandled(); 1610} 1611 1612/////////////////////////////////////////////////////////////////////////////// 1613// TabStrip, private: 1614 1615void TabStrip::Init() { 1616 set_id(VIEW_ID_TAB_STRIP); 1617 // So we get enter/exit on children to switch layout type. 1618 set_notify_enter_exit_on_child(true); 1619 newtab_button_bounds_.SetRect(0, 1620 0, 1621 newtab_button_asset_width(), 1622 newtab_button_asset_height() + 1623 newtab_button_v_offset()); 1624 newtab_button_ = new NewTabButton(this, this); 1625 newtab_button_->SetTooltipText( 1626 l10n_util::GetStringUTF16(IDS_TOOLTIP_NEW_TAB)); 1627 newtab_button_->SetAccessibleName( 1628 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB)); 1629 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT, 1630 views::ImageButton::ALIGN_BOTTOM); 1631 AddChildView(newtab_button_); 1632 if (drop_indicator_width == 0) { 1633 // Direction doesn't matter, both images are the same size. 1634 gfx::ImageSkia* drop_image = GetDropArrowImage(true); 1635 drop_indicator_width = drop_image->width(); 1636 drop_indicator_height = drop_image->height(); 1637 } 1638} 1639 1640Tab* TabStrip::CreateTab() { 1641 Tab* tab = new Tab(this); 1642 tab->set_animation_container(animation_container_.get()); 1643 return tab; 1644} 1645 1646void TabStrip::StartInsertTabAnimation(int model_index) { 1647 PrepareForAnimation(); 1648 1649 // The TabStrip can now use its entire width to lay out Tabs. 1650 in_tab_close_ = false; 1651 available_width_for_tabs_ = -1; 1652 1653 GenerateIdealBounds(); 1654 1655 Tab* tab = tab_at(model_index); 1656 if (model_index == 0) { 1657 tab->SetBounds(0, ideal_bounds(model_index).y(), 0, 1658 ideal_bounds(model_index).height()); 1659 } else { 1660 Tab* last_tab = tab_at(model_index - 1); 1661 tab->SetBounds(last_tab->bounds().right() + tab_h_offset(), 1662 ideal_bounds(model_index).y(), 0, 1663 ideal_bounds(model_index).height()); 1664 } 1665 1666 AnimateToIdealBounds(); 1667} 1668 1669void TabStrip::StartMoveTabAnimation() { 1670 PrepareForAnimation(); 1671 GenerateIdealBounds(); 1672 AnimateToIdealBounds(); 1673} 1674 1675void TabStrip::StartRemoveTabAnimation(int model_index) { 1676 PrepareForAnimation(); 1677 1678 // Mark the tab as closing. 1679 Tab* tab = tab_at(model_index); 1680 tab->set_closing(true); 1681 1682 RemoveTabFromViewModel(model_index); 1683 1684 ScheduleRemoveTabAnimation(tab); 1685} 1686 1687void TabStrip::ScheduleRemoveTabAnimation(Tab* tab) { 1688 // Start an animation for the tabs. 1689 GenerateIdealBounds(); 1690 AnimateToIdealBounds(); 1691 1692 // Animate the tab being closed to 0x0. 1693 gfx::Rect tab_bounds = tab->bounds(); 1694 tab_bounds.set_width(0); 1695 bounds_animator_.AnimateViewTo(tab, tab_bounds); 1696 1697 // Register delegate to do cleanup when done, BoundsAnimator takes 1698 // ownership of RemoveTabDelegate. 1699 bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab), 1700 true); 1701 1702 // Don't animate the new tab button when dragging tabs. Otherwise it looks 1703 // like the new tab button magically appears from beyond the end of the tab 1704 // strip. 1705 if (TabDragController::IsAttachedTo(this)) { 1706 bounds_animator_.StopAnimatingView(newtab_button_); 1707 newtab_button_->SetBoundsRect(newtab_button_bounds_); 1708 } 1709} 1710 1711void TabStrip::AnimateToIdealBounds() { 1712 for (int i = 0; i < tab_count(); ++i) { 1713 Tab* tab = tab_at(i); 1714 if (!tab->dragging()) 1715 bounds_animator_.AnimateViewTo(tab, ideal_bounds(i)); 1716 } 1717 1718 bounds_animator_.AnimateViewTo(newtab_button_, newtab_button_bounds_); 1719} 1720 1721bool TabStrip::ShouldHighlightCloseButtonAfterRemove() { 1722 return in_tab_close_; 1723} 1724 1725void TabStrip::DoLayout() { 1726 last_layout_size_ = size(); 1727 1728 StopAnimating(false); 1729 1730 SwapLayoutIfNecessary(); 1731 1732 if (touch_layout_.get()) 1733 touch_layout_->SetWidth(size().width() - new_tab_button_width()); 1734 1735 GenerateIdealBounds(); 1736 1737 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_); 1738 1739 SchedulePaint(); 1740 1741 bounds_animator_.StopAnimatingView(newtab_button_); 1742 newtab_button_->SetBoundsRect(newtab_button_bounds_); 1743} 1744 1745void TabStrip::DragActiveTab(const std::vector<int>& initial_positions, 1746 int delta) { 1747 DCHECK_EQ(tab_count(), static_cast<int>(initial_positions.size())); 1748 if (!touch_layout_.get()) { 1749 StackDraggedTabs(delta); 1750 return; 1751 } 1752 SetIdealBoundsFromPositions(initial_positions); 1753 touch_layout_->DragActiveTab(delta); 1754 DoLayout(); 1755} 1756 1757void TabStrip::SetIdealBoundsFromPositions(const std::vector<int>& positions) { 1758 if (static_cast<size_t>(tab_count()) != positions.size()) 1759 return; 1760 1761 for (int i = 0; i < tab_count(); ++i) { 1762 gfx::Rect bounds(ideal_bounds(i)); 1763 bounds.set_x(positions[i]); 1764 set_ideal_bounds(i, bounds); 1765 } 1766} 1767 1768void TabStrip::StackDraggedTabs(int delta) { 1769 DCHECK(!touch_layout_.get()); 1770 GenerateIdealBounds(); 1771 const int active_index = controller_->GetActiveIndex(); 1772 DCHECK_NE(-1, active_index); 1773 if (delta < 0) { 1774 // Drag the tabs to the left, stacking tabs before the active tab. 1775 const int adjusted_delta = 1776 std::min(ideal_bounds(active_index).x() - 1777 kStackedPadding * std::min(active_index, kMaxStackedCount), 1778 -delta); 1779 for (int i = 0; i <= active_index; ++i) { 1780 const int min_x = std::min(i, kMaxStackedCount) * kStackedPadding; 1781 gfx::Rect new_bounds(ideal_bounds(i)); 1782 new_bounds.set_x(std::max(min_x, new_bounds.x() - adjusted_delta)); 1783 set_ideal_bounds(i, new_bounds); 1784 } 1785 const bool is_active_mini = tab_at(active_index)->data().mini; 1786 const int active_width = ideal_bounds(active_index).width(); 1787 for (int i = active_index + 1; i < tab_count(); ++i) { 1788 const int max_x = ideal_bounds(active_index).x() + 1789 (kStackedPadding * std::min(i - active_index, kMaxStackedCount)); 1790 gfx::Rect new_bounds(ideal_bounds(i)); 1791 int new_x = std::max(new_bounds.x() + delta, max_x); 1792 if (new_x == max_x && !tab_at(i)->data().mini && !is_active_mini && 1793 new_bounds.width() != active_width) 1794 new_x += (active_width - new_bounds.width()); 1795 new_bounds.set_x(new_x); 1796 set_ideal_bounds(i, new_bounds); 1797 } 1798 } else { 1799 // Drag the tabs to the right, stacking tabs after the active tab. 1800 const int last_tab_width = ideal_bounds(tab_count() - 1).width(); 1801 const int last_tab_x = width() - new_tab_button_width() - last_tab_width; 1802 if (active_index == tab_count() - 1 && 1803 ideal_bounds(tab_count() - 1).x() == last_tab_x) 1804 return; 1805 const int adjusted_delta = 1806 std::min(last_tab_x - 1807 kStackedPadding * std::min(tab_count() - active_index - 1, 1808 kMaxStackedCount) - 1809 ideal_bounds(active_index).x(), 1810 delta); 1811 for (int last_index = tab_count() - 1, i = last_index; i >= active_index; 1812 --i) { 1813 const int max_x = last_tab_x - 1814 std::min(tab_count() - i - 1, kMaxStackedCount) * kStackedPadding; 1815 gfx::Rect new_bounds(ideal_bounds(i)); 1816 int new_x = std::min(max_x, new_bounds.x() + adjusted_delta); 1817 // Because of rounding not all tabs are the same width. Adjust the 1818 // position to accommodate this, otherwise the stacking is off. 1819 if (new_x == max_x && !tab_at(i)->data().mini && 1820 new_bounds.width() != last_tab_width) 1821 new_x += (last_tab_width - new_bounds.width()); 1822 new_bounds.set_x(new_x); 1823 set_ideal_bounds(i, new_bounds); 1824 } 1825 for (int i = active_index - 1; i >= 0; --i) { 1826 const int min_x = ideal_bounds(active_index).x() - 1827 std::min(active_index - i, kMaxStackedCount) * kStackedPadding; 1828 gfx::Rect new_bounds(ideal_bounds(i)); 1829 new_bounds.set_x(std::min(min_x, new_bounds.x() + delta)); 1830 set_ideal_bounds(i, new_bounds); 1831 } 1832 if (ideal_bounds(tab_count() - 1).right() >= newtab_button_->x()) 1833 newtab_button_->SetVisible(false); 1834 } 1835 views::ViewModelUtils::SetViewBoundsToIdealBounds(tabs_); 1836 SchedulePaint(); 1837} 1838 1839bool TabStrip::IsStackingDraggedTabs() const { 1840 return drag_controller_.get() && drag_controller_->started_drag() && 1841 (drag_controller_->move_behavior() == 1842 TabDragController::MOVE_VISIBILE_TABS); 1843} 1844 1845void TabStrip::LayoutDraggedTabsAt(const std::vector<Tab*>& tabs, 1846 Tab* active_tab, 1847 const gfx::Point& location, 1848 bool initial_drag) { 1849 // Immediately hide the new tab button if the last tab is being dragged. 1850 if (tab_at(tab_count() - 1)->dragging()) 1851 newtab_button_->SetVisible(false); 1852 std::vector<gfx::Rect> bounds; 1853 CalculateBoundsForDraggedTabs(tabs, &bounds); 1854 DCHECK_EQ(tabs.size(), bounds.size()); 1855 int active_tab_model_index = GetModelIndexOfTab(active_tab); 1856 int active_tab_index = static_cast<int>( 1857 std::find(tabs.begin(), tabs.end(), active_tab) - tabs.begin()); 1858 for (size_t i = 0; i < tabs.size(); ++i) { 1859 Tab* tab = tabs[i]; 1860 gfx::Rect new_bounds = bounds[i]; 1861 new_bounds.Offset(location.x(), location.y()); 1862 int consecutive_index = 1863 active_tab_model_index - (active_tab_index - static_cast<int>(i)); 1864 // If this is the initial layout during a drag and the tabs aren't 1865 // consecutive animate the view into position. Do the same if the tab is 1866 // already animating (which means we previously caused it to animate). 1867 if ((initial_drag && 1868 GetModelIndexOfTab(tabs[i]) != consecutive_index) || 1869 bounds_animator_.IsAnimating(tabs[i])) { 1870 bounds_animator_.SetTargetBounds(tabs[i], new_bounds); 1871 } else { 1872 tab->SetBoundsRect(new_bounds); 1873 } 1874 } 1875} 1876 1877void TabStrip::CalculateBoundsForDraggedTabs(const std::vector<Tab*>& tabs, 1878 std::vector<gfx::Rect>* bounds) { 1879 int x = 0; 1880 for (size_t i = 0; i < tabs.size(); ++i) { 1881 Tab* tab = tabs[i]; 1882 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini) 1883 x += kMiniToNonMiniGap; 1884 gfx::Rect new_bounds = tab->bounds(); 1885 new_bounds.set_origin(gfx::Point(x, 0)); 1886 bounds->push_back(new_bounds); 1887 x += tab->width() + tab_h_offset(); 1888 } 1889} 1890 1891int TabStrip::GetSizeNeededForTabs(const std::vector<Tab*>& tabs) { 1892 int width = 0; 1893 for (size_t i = 0; i < tabs.size(); ++i) { 1894 Tab* tab = tabs[i]; 1895 width += tab->width(); 1896 if (i > 0 && tab->data().mini != tabs[i - 1]->data().mini) 1897 width += kMiniToNonMiniGap; 1898 } 1899 if (tabs.size() > 0) 1900 width += tab_h_offset() * static_cast<int>(tabs.size() - 1); 1901 return width; 1902} 1903 1904void TabStrip::RemoveTabFromViewModel(int index) { 1905 // We still need to paint the tab until we actually remove it. Put it 1906 // in tabs_closing_map_ so we can find it. 1907 tabs_closing_map_[index].push_back(tab_at(index)); 1908 UpdateTabsClosingMap(index + 1, -1); 1909 tabs_.Remove(index); 1910} 1911 1912void TabStrip::RemoveAndDeleteTab(Tab* tab) { 1913 scoped_ptr<Tab> deleter(tab); 1914 for (TabsClosingMap::iterator i(tabs_closing_map_.begin()); 1915 i != tabs_closing_map_.end(); ++i) { 1916 std::vector<Tab*>::iterator j = 1917 std::find(i->second.begin(), i->second.end(), tab); 1918 if (j != i->second.end()) { 1919 i->second.erase(j); 1920 if (i->second.empty()) 1921 tabs_closing_map_.erase(i); 1922 return; 1923 } 1924 } 1925 NOTREACHED(); 1926} 1927 1928void TabStrip::UpdateTabsClosingMap(int index, int delta) { 1929 if (tabs_closing_map_.empty()) 1930 return; 1931 1932 if (delta == -1 && 1933 tabs_closing_map_.find(index - 1) != tabs_closing_map_.end() && 1934 tabs_closing_map_.find(index) != tabs_closing_map_.end()) { 1935 const std::vector<Tab*>& tabs(tabs_closing_map_[index]); 1936 tabs_closing_map_[index - 1].insert( 1937 tabs_closing_map_[index - 1].end(), tabs.begin(), tabs.end()); 1938 } 1939 TabsClosingMap updated_map; 1940 for (TabsClosingMap::iterator i(tabs_closing_map_.begin()); 1941 i != tabs_closing_map_.end(); ++i) { 1942 if (i->first > index) 1943 updated_map[i->first + delta] = i->second; 1944 else if (i->first < index) 1945 updated_map[i->first] = i->second; 1946 } 1947 if (delta > 0 && tabs_closing_map_.find(index) != tabs_closing_map_.end()) 1948 updated_map[index + delta] = tabs_closing_map_[index]; 1949 tabs_closing_map_.swap(updated_map); 1950} 1951 1952void TabStrip::StartedDraggingTabs(const std::vector<Tab*>& tabs) { 1953 // Let the controller know that the user started dragging tabs. 1954 controller()->OnStartedDraggingTabs(); 1955 1956 // Hide the new tab button immediately if we didn't originate the drag. 1957 if (!drag_controller_.get()) 1958 newtab_button_->SetVisible(false); 1959 1960 PrepareForAnimation(); 1961 1962 // Reset dragging state of existing tabs. 1963 for (int i = 0; i < tab_count(); ++i) 1964 tab_at(i)->set_dragging(false); 1965 1966 for (size_t i = 0; i < tabs.size(); ++i) { 1967 tabs[i]->set_dragging(true); 1968 bounds_animator_.StopAnimatingView(tabs[i]); 1969 } 1970 1971 // Move the dragged tabs to their ideal bounds. 1972 GenerateIdealBounds(); 1973 1974 // Sets the bounds of the dragged tabs. 1975 for (size_t i = 0; i < tabs.size(); ++i) { 1976 int tab_data_index = GetModelIndexOfTab(tabs[i]); 1977 DCHECK_NE(-1, tab_data_index); 1978 tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index)); 1979 } 1980 SchedulePaint(); 1981} 1982 1983void TabStrip::DraggedTabsDetached() { 1984 // Let the controller know that the user is not dragging this tabstrip's tabs 1985 // anymore. 1986 controller()->OnStoppedDraggingTabs(); 1987 newtab_button_->SetVisible(true); 1988} 1989 1990void TabStrip::StoppedDraggingTabs(const std::vector<Tab*>& tabs, 1991 const std::vector<int>& initial_positions, 1992 bool move_only, 1993 bool completed) { 1994 // Let the controller know that the user stopped dragging tabs. 1995 controller()->OnStoppedDraggingTabs(); 1996 1997 newtab_button_->SetVisible(true); 1998 if (move_only && touch_layout_.get()) { 1999 if (completed) { 2000 touch_layout_->SizeToFit(); 2001 } else { 2002 SetIdealBoundsFromPositions(initial_positions); 2003 } 2004 } 2005 bool is_first_tab = true; 2006 for (size_t i = 0; i < tabs.size(); ++i) 2007 StoppedDraggingTab(tabs[i], &is_first_tab); 2008} 2009 2010void TabStrip::StoppedDraggingTab(Tab* tab, bool* is_first_tab) { 2011 int tab_data_index = GetModelIndexOfTab(tab); 2012 if (tab_data_index == -1) { 2013 // The tab was removed before the drag completed. Don't do anything. 2014 return; 2015 } 2016 2017 if (*is_first_tab) { 2018 *is_first_tab = false; 2019 PrepareForAnimation(); 2020 2021 // Animate the view back to its correct position. 2022 GenerateIdealBounds(); 2023 AnimateToIdealBounds(); 2024 } 2025 bounds_animator_.AnimateViewTo(tab, ideal_bounds(tab_data_index)); 2026 // Install a delegate to reset the dragging state when done. We have to leave 2027 // dragging true for the tab otherwise it'll draw beneath the new tab button. 2028 bounds_animator_.SetAnimationDelegate( 2029 tab, new ResetDraggingStateDelegate(tab), true); 2030} 2031 2032void TabStrip::OwnDragController(TabDragController* controller) { 2033 // Typically, ReleaseDragController() and OwnDragController() calls are paired 2034 // via corresponding calls to TabDragController::Detach() and 2035 // TabDragController::Attach(). There is one exception to that rule: when a 2036 // drag might start, we create a TabDragController that is owned by the 2037 // potential source tabstrip in MaybeStartDrag(). If a drag actually starts, 2038 // we then call Attach() on the source tabstrip, but since the source tabstrip 2039 // already owns the TabDragController, so we don't need to do anything. 2040 if (controller != drag_controller_.get()) 2041 drag_controller_.reset(controller); 2042} 2043 2044void TabStrip::DestroyDragController() { 2045 newtab_button_->SetVisible(true); 2046 drag_controller_.reset(); 2047} 2048 2049TabDragController* TabStrip::ReleaseDragController() { 2050 return drag_controller_.release(); 2051} 2052 2053void TabStrip::PaintClosingTabs(gfx::Canvas* canvas, int index) { 2054 if (tabs_closing_map_.find(index) == tabs_closing_map_.end()) 2055 return; 2056 2057 const std::vector<Tab*>& tabs = tabs_closing_map_[index]; 2058 for (std::vector<Tab*>::const_reverse_iterator i(tabs.rbegin()); 2059 i != tabs.rend(); ++i) { 2060 (*i)->Paint(canvas); 2061 } 2062} 2063 2064void TabStrip::UpdateLayoutTypeFromMouseEvent(views::View* source, 2065 const ui::MouseEvent& event) { 2066 if (!GetAdjustLayout()) 2067 return; 2068 2069 // The following code attempts to switch to TAB_STRIP_LAYOUT_SHRINK when the 2070 // mouse exits the tabstrip (or the mouse is pressed on a stacked tab) and 2071 // TAB_STRIP_LAYOUT_STACKED when a touch device is used. This is made 2072 // problematic by windows generating mouse move events that do not clearly 2073 // indicate the move is the result of a touch device. This assumes a real 2074 // mouse is used if |kMouseMoveCountBeforeConsiderReal| mouse move events are 2075 // received within the time window |kMouseMoveTimeMS|. At the time we get a 2076 // mouse press we know whether its from a touch device or not, but we don't 2077 // layout then else everything shifts. Instead we wait for the release. 2078 // 2079 // TODO(sky): revisit this when touch events are really plumbed through. 2080 2081 switch (event.type()) { 2082 case ui::ET_MOUSE_PRESSED: 2083 mouse_move_count_ = 0; 2084 last_mouse_move_time_ = base::TimeTicks(); 2085 SetResetToShrinkOnExit((event.flags() & ui::EF_FROM_TOUCH) == 0); 2086 if (reset_to_shrink_on_exit_ && touch_layout_.get()) { 2087 gfx::Point tab_strip_point(event.location()); 2088 views::View::ConvertPointToTarget(source, this, &tab_strip_point); 2089 Tab* tab = FindTabForEvent(tab_strip_point); 2090 if (tab && touch_layout_->IsStacked(GetModelIndexOfTab(tab))) { 2091 SetLayoutType(TAB_STRIP_LAYOUT_SHRINK, true); 2092 controller_->LayoutTypeMaybeChanged(); 2093 } 2094 } 2095 break; 2096 2097 case ui::ET_MOUSE_MOVED: { 2098#if defined(USE_ASH) 2099 // Ash does not synthesize mouse events from touch events. 2100 SetResetToShrinkOnExit(true); 2101#else 2102 gfx::Point location(event.location()); 2103 ConvertPointToTarget(source, this, &location); 2104 if (location == last_mouse_move_location_) 2105 return; // Ignore spurious moves. 2106 last_mouse_move_location_ = location; 2107 if ((event.flags() & ui::EF_FROM_TOUCH) == 0 && 2108 (event.flags() & ui::EF_IS_SYNTHESIZED) == 0) { 2109 if ((base::TimeTicks::Now() - last_mouse_move_time_).InMilliseconds() < 2110 kMouseMoveTimeMS) { 2111 if (mouse_move_count_++ == kMouseMoveCountBeforeConsiderReal) 2112 SetResetToShrinkOnExit(true); 2113 } else { 2114 mouse_move_count_ = 1; 2115 last_mouse_move_time_ = base::TimeTicks::Now(); 2116 } 2117 } else { 2118 last_mouse_move_time_ = base::TimeTicks(); 2119 } 2120#endif 2121 break; 2122 } 2123 2124 case ui::ET_MOUSE_RELEASED: { 2125 gfx::Point location(event.location()); 2126 ConvertPointToTarget(source, this, &location); 2127 last_mouse_move_location_ = location; 2128 mouse_move_count_ = 0; 2129 last_mouse_move_time_ = base::TimeTicks(); 2130 if ((event.flags() & ui::EF_FROM_TOUCH) == ui::EF_FROM_TOUCH) { 2131 SetLayoutType(TAB_STRIP_LAYOUT_STACKED, true); 2132 controller_->LayoutTypeMaybeChanged(); 2133 } 2134 break; 2135 } 2136 2137 default: 2138 break; 2139 } 2140} 2141 2142void TabStrip::GetCurrentTabWidths(double* unselected_width, 2143 double* selected_width) const { 2144 *unselected_width = current_unselected_width_; 2145 *selected_width = current_selected_width_; 2146} 2147 2148void TabStrip::GetDesiredTabWidths(int tab_count, 2149 int mini_tab_count, 2150 double* unselected_width, 2151 double* selected_width) const { 2152 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count); 2153 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width(); 2154 const double min_selected_width = Tab::GetMinimumSelectedSize().width(); 2155 2156 *unselected_width = min_unselected_width; 2157 *selected_width = min_selected_width; 2158 2159 if (tab_count == 0) { 2160 // Return immediately to avoid divide-by-zero below. 2161 return; 2162 } 2163 2164 // Determine how much space we can actually allocate to tabs. 2165 int available_width; 2166 if (available_width_for_tabs_ < 0) { 2167 available_width = width() - new_tab_button_width(); 2168 } else { 2169 // Interesting corner case: if |available_width_for_tabs_| > the result 2170 // of the calculation in the conditional arm above, the strip is in 2171 // overflow. We can either use the specified width or the true available 2172 // width here; the first preserves the consistent "leave the last tab under 2173 // the user's mouse so they can close many tabs" behavior at the cost of 2174 // prolonging the glitchy appearance of the overflow state, while the second 2175 // gets us out of overflow as soon as possible but forces the user to move 2176 // their mouse for a few tabs' worth of closing. We choose visual 2177 // imperfection over behavioral imperfection and select the first option. 2178 available_width = available_width_for_tabs_; 2179 } 2180 2181 if (mini_tab_count > 0) { 2182 available_width -= mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset()); 2183 tab_count -= mini_tab_count; 2184 if (tab_count == 0) { 2185 *selected_width = *unselected_width = Tab::GetStandardSize().width(); 2186 return; 2187 } 2188 // Account for gap between the last mini-tab and first non-mini-tab. 2189 available_width -= kMiniToNonMiniGap; 2190 } 2191 2192 // Calculate the desired tab widths by dividing the available space into equal 2193 // portions. Don't let tabs get larger than the "standard width" or smaller 2194 // than the minimum width for each type, respectively. 2195 const int total_offset = tab_h_offset() * (tab_count - 1); 2196 const double desired_tab_width = std::min((static_cast<double>( 2197 available_width - total_offset) / static_cast<double>(tab_count)), 2198 static_cast<double>(Tab::GetStandardSize().width())); 2199 *unselected_width = std::max(desired_tab_width, min_unselected_width); 2200 *selected_width = std::max(desired_tab_width, min_selected_width); 2201 2202 // When there are multiple tabs, we'll have one selected and some unselected 2203 // tabs. If the desired width was between the minimum sizes of these types, 2204 // try to shrink the tabs with the smaller minimum. For example, if we have 2205 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If 2206 // selected tabs have a minimum width of 4 and unselected tabs have a minimum 2207 // width of 1, the above code would set *unselected_width = 2.5, 2208 // *selected_width = 4, which results in a total width of 11.5. Instead, we 2209 // want to set *unselected_width = 2, *selected_width = 4, for a total width 2210 // of 10. 2211 if (tab_count > 1) { 2212 if ((min_unselected_width < min_selected_width) && 2213 (desired_tab_width < min_selected_width)) { 2214 // Unselected width = (total width - selected width) / (num_tabs - 1) 2215 *unselected_width = std::max(static_cast<double>( 2216 available_width - total_offset - min_selected_width) / 2217 static_cast<double>(tab_count - 1), min_unselected_width); 2218 } else if ((min_unselected_width > min_selected_width) && 2219 (desired_tab_width < min_unselected_width)) { 2220 // Selected width = (total width - (unselected width * (num_tabs - 1))) 2221 *selected_width = std::max(available_width - total_offset - 2222 (min_unselected_width * (tab_count - 1)), min_selected_width); 2223 } 2224 } 2225} 2226 2227void TabStrip::ResizeLayoutTabs() { 2228 // We've been called back after the TabStrip has been emptied out (probably 2229 // just prior to the window being destroyed). We need to do nothing here or 2230 // else GetTabAt below will crash. 2231 if (tab_count() == 0) 2232 return; 2233 2234 // It is critically important that this is unhooked here, otherwise we will 2235 // keep spying on messages forever. 2236 RemoveMessageLoopObserver(); 2237 2238 in_tab_close_ = false; 2239 available_width_for_tabs_ = -1; 2240 int mini_tab_count = GetMiniTabCount(); 2241 if (mini_tab_count == tab_count()) { 2242 // Only mini-tabs, we know the tab widths won't have changed (all 2243 // mini-tabs have the same width), so there is nothing to do. 2244 return; 2245 } 2246 // Don't try and avoid layout based on tab sizes. If tabs are small enough 2247 // then the width of the active tab may not change, but other widths may 2248 // have. This is particularly important if we've overflowed (all tabs are at 2249 // the min). 2250 StartResizeLayoutAnimation(); 2251} 2252 2253void TabStrip::ResizeLayoutTabsFromTouch() { 2254 // Don't resize if the user is interacting with the tabstrip. 2255 if (!drag_controller_.get()) 2256 ResizeLayoutTabs(); 2257 else 2258 StartResizeLayoutTabsFromTouchTimer(); 2259} 2260 2261void TabStrip::StartResizeLayoutTabsFromTouchTimer() { 2262 resize_layout_timer_.Stop(); 2263 resize_layout_timer_.Start( 2264 FROM_HERE, base::TimeDelta::FromMilliseconds(kTouchResizeLayoutTimeMS), 2265 this, &TabStrip::ResizeLayoutTabsFromTouch); 2266} 2267 2268void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) { 2269 StopAnimating(false); 2270 DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size())); 2271 for (int i = 0; i < tab_count(); ++i) 2272 tab_at(i)->SetBoundsRect(tab_bounds[i]); 2273} 2274 2275void TabStrip::AddMessageLoopObserver() { 2276 if (!mouse_watcher_.get()) { 2277 mouse_watcher_.reset( 2278 new views::MouseWatcher( 2279 new views::MouseWatcherViewHost( 2280 this, gfx::Insets(0, 0, kTabStripAnimationVSlop, 0)), 2281 this)); 2282 } 2283 mouse_watcher_->Start(); 2284} 2285 2286void TabStrip::RemoveMessageLoopObserver() { 2287 mouse_watcher_.reset(NULL); 2288} 2289 2290gfx::Rect TabStrip::GetDropBounds(int drop_index, 2291 bool drop_before, 2292 bool* is_beneath) { 2293 DCHECK_NE(drop_index, -1); 2294 int center_x; 2295 if (drop_index < tab_count()) { 2296 Tab* tab = tab_at(drop_index); 2297 if (drop_before) 2298 center_x = tab->x() - (tab_h_offset() / 2); 2299 else 2300 center_x = tab->x() + (tab->width() / 2); 2301 } else { 2302 Tab* last_tab = tab_at(drop_index - 1); 2303 center_x = last_tab->x() + last_tab->width() + (tab_h_offset() / 2); 2304 } 2305 2306 // Mirror the center point if necessary. 2307 center_x = GetMirroredXInView(center_x); 2308 2309 // Determine the screen bounds. 2310 gfx::Point drop_loc(center_x - drop_indicator_width / 2, 2311 -drop_indicator_height); 2312 ConvertPointToScreen(this, &drop_loc); 2313 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, 2314 drop_indicator_height); 2315 2316 // If the rect doesn't fit on the monitor, push the arrow to the bottom. 2317#if defined(OS_WIN) && !defined(USE_AURA) 2318 gfx::Rect monitor_bounds = views::GetMonitorBoundsForRect(drop_bounds); 2319 *is_beneath = (monitor_bounds.IsEmpty() || 2320 !monitor_bounds.Contains(drop_bounds)); 2321#else 2322 *is_beneath = false; 2323 NOTIMPLEMENTED(); 2324#endif 2325 if (*is_beneath) 2326 drop_bounds.Offset(0, drop_bounds.height() + height()); 2327 2328 return drop_bounds; 2329} 2330 2331void TabStrip::UpdateDropIndex(const DropTargetEvent& event) { 2332 // If the UI layout is right-to-left, we need to mirror the mouse 2333 // coordinates since we calculate the drop index based on the 2334 // original (and therefore non-mirrored) positions of the tabs. 2335 const int x = GetMirroredXInView(event.x()); 2336 // We don't allow replacing the urls of mini-tabs. 2337 for (int i = GetMiniTabCount(); i < tab_count(); ++i) { 2338 Tab* tab = tab_at(i); 2339 const int tab_max_x = tab->x() + tab->width(); 2340 const int hot_width = tab->width() / kTabEdgeRatioInverse; 2341 if (x < tab_max_x) { 2342 if (x < tab->x() + hot_width) 2343 SetDropIndex(i, true); 2344 else if (x >= tab_max_x - hot_width) 2345 SetDropIndex(i + 1, true); 2346 else 2347 SetDropIndex(i, false); 2348 return; 2349 } 2350 } 2351 2352 // The drop isn't over a tab, add it to the end. 2353 SetDropIndex(tab_count(), true); 2354} 2355 2356void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) { 2357 // Let the controller know of the index update. 2358 controller()->OnDropIndexUpdate(tab_data_index, drop_before); 2359 2360 if (tab_data_index == -1) { 2361 if (drop_info_.get()) 2362 drop_info_.reset(NULL); 2363 return; 2364 } 2365 2366 if (drop_info_.get() && drop_info_->drop_index == tab_data_index && 2367 drop_info_->drop_before == drop_before) { 2368 return; 2369 } 2370 2371 bool is_beneath; 2372 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before, 2373 &is_beneath); 2374 2375 if (!drop_info_.get()) { 2376 drop_info_.reset( 2377 new DropInfo(tab_data_index, drop_before, !is_beneath, GetWidget())); 2378 } else { 2379 drop_info_->drop_index = tab_data_index; 2380 drop_info_->drop_before = drop_before; 2381 if (is_beneath == drop_info_->point_down) { 2382 drop_info_->point_down = !is_beneath; 2383 drop_info_->arrow_view->SetImage( 2384 GetDropArrowImage(drop_info_->point_down)); 2385 } 2386 } 2387 2388 // Reposition the window. Need to show it too as the window is initially 2389 // hidden. 2390 drop_info_->arrow_window->SetBounds(drop_bounds); 2391 drop_info_->arrow_window->Show(); 2392} 2393 2394int TabStrip::GetDropEffect(const ui::DropTargetEvent& event) { 2395 const int source_ops = event.source_operations(); 2396 if (source_ops & ui::DragDropTypes::DRAG_COPY) 2397 return ui::DragDropTypes::DRAG_COPY; 2398 if (source_ops & ui::DragDropTypes::DRAG_LINK) 2399 return ui::DragDropTypes::DRAG_LINK; 2400 return ui::DragDropTypes::DRAG_MOVE; 2401} 2402 2403// static 2404gfx::ImageSkia* TabStrip::GetDropArrowImage(bool is_down) { 2405 return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 2406 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); 2407} 2408 2409// TabStrip::DropInfo ---------------------------------------------------------- 2410 2411TabStrip::DropInfo::DropInfo(int drop_index, 2412 bool drop_before, 2413 bool point_down, 2414 views::Widget* context) 2415 : drop_index(drop_index), 2416 drop_before(drop_before), 2417 point_down(point_down) { 2418 arrow_view = new views::ImageView; 2419 arrow_view->SetImage(GetDropArrowImage(point_down)); 2420 2421 arrow_window = new views::Widget; 2422 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 2423 params.keep_on_top = true; 2424 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 2425 params.accept_events = false; 2426 params.can_activate = false; 2427 params.bounds = gfx::Rect(drop_indicator_width, drop_indicator_height); 2428 params.context = context->GetNativeView(); 2429 arrow_window->Init(params); 2430 arrow_window->SetContentsView(arrow_view); 2431} 2432 2433TabStrip::DropInfo::~DropInfo() { 2434 // Close eventually deletes the window, which deletes arrow_view too. 2435 arrow_window->Close(); 2436} 2437 2438/////////////////////////////////////////////////////////////////////////////// 2439 2440void TabStrip::PrepareForAnimation() { 2441 if (!IsDragSessionActive() && !TabDragController::IsAttachedTo(this)) { 2442 for (int i = 0; i < tab_count(); ++i) 2443 tab_at(i)->set_dragging(false); 2444 } 2445} 2446 2447void TabStrip::GenerateIdealBounds() { 2448 int new_tab_y = 0; 2449 2450 if (touch_layout_.get()) { 2451 if (tabs_.view_size() == 0) 2452 return; 2453 2454 int new_tab_x = tabs_.ideal_bounds(tabs_.view_size() - 1).right() + 2455 newtab_button_h_offset(); 2456 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); 2457 return; 2458 } 2459 2460 double unselected, selected; 2461 GetDesiredTabWidths(tab_count(), GetMiniTabCount(), &unselected, &selected); 2462 current_unselected_width_ = unselected; 2463 current_selected_width_ = selected; 2464 2465 // NOTE: This currently assumes a tab's height doesn't differ based on 2466 // selected state or the number of tabs in the strip! 2467 int tab_height = Tab::GetStandardSize().height(); 2468 int first_non_mini_index = 0; 2469 double tab_x = GenerateIdealBoundsForMiniTabs(&first_non_mini_index); 2470 for (int i = first_non_mini_index; i < tab_count(); ++i) { 2471 Tab* tab = tab_at(i); 2472 DCHECK(!tab->data().mini); 2473 double tab_width = tab->IsActive() ? selected : unselected; 2474 double end_of_tab = tab_x + tab_width; 2475 int rounded_tab_x = Round(tab_x); 2476 set_ideal_bounds( 2477 i, 2478 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, 2479 tab_height)); 2480 tab_x = end_of_tab + tab_h_offset(); 2481 } 2482 2483 // Update bounds of new tab button. 2484 int new_tab_x; 2485 if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 && 2486 !in_tab_close_) { 2487 // We're shrinking tabs, so we need to anchor the New Tab button to the 2488 // right edge of the TabStrip's bounds, rather than the right edge of the 2489 // right-most Tab, otherwise it'll bounce when animating. 2490 new_tab_x = width() - newtab_button_bounds_.width(); 2491 } else { 2492 new_tab_x = Round(tab_x - tab_h_offset()) + newtab_button_h_offset(); 2493 } 2494 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); 2495} 2496 2497int TabStrip::GenerateIdealBoundsForMiniTabs(int* first_non_mini_index) { 2498 int next_x = 0; 2499 int mini_width = Tab::GetMiniWidth(); 2500 int tab_height = Tab::GetStandardSize().height(); 2501 int index = 0; 2502 for (; index < tab_count() && tab_at(index)->data().mini; ++index) { 2503 set_ideal_bounds(index, 2504 gfx::Rect(next_x, 0, mini_width, tab_height)); 2505 next_x += mini_width + tab_h_offset(); 2506 } 2507 if (index > 0 && index < tab_count()) 2508 next_x += kMiniToNonMiniGap; 2509 if (first_non_mini_index) 2510 *first_non_mini_index = index; 2511 return next_x; 2512} 2513 2514// static 2515int TabStrip::new_tab_button_width() { 2516 return newtab_button_asset_width() + newtab_button_h_offset(); 2517} 2518 2519// static 2520int TabStrip::button_v_offset() { 2521 return newtab_button_v_offset(); 2522} 2523 2524int TabStrip::tab_area_width() const { 2525 return width() - new_tab_button_width(); 2526} 2527 2528void TabStrip::StartResizeLayoutAnimation() { 2529 PrepareForAnimation(); 2530 GenerateIdealBounds(); 2531 AnimateToIdealBounds(); 2532} 2533 2534void TabStrip::StartMiniTabAnimation() { 2535 in_tab_close_ = false; 2536 available_width_for_tabs_ = -1; 2537 2538 PrepareForAnimation(); 2539 2540 GenerateIdealBounds(); 2541 AnimateToIdealBounds(); 2542} 2543 2544void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) { 2545 // The user initiated the close. We want to persist the bounds of all the 2546 // existing tabs, so we manually shift ideal_bounds then animate. 2547 Tab* tab_closing = tab_at(model_index); 2548 int delta = tab_closing->width() + tab_h_offset(); 2549 // If the tab being closed is a mini-tab next to a non-mini-tab, be sure to 2550 // add the extra padding. 2551 DCHECK_NE(model_index + 1, tab_count()); 2552 if (tab_closing->data().mini && model_index + 1 < tab_count() && 2553 !tab_at(model_index + 1)->data().mini) { 2554 delta += kMiniToNonMiniGap; 2555 } 2556 2557 for (int i = model_index + 1; i < tab_count(); ++i) { 2558 gfx::Rect bounds = ideal_bounds(i); 2559 bounds.set_x(bounds.x() - delta); 2560 set_ideal_bounds(i, bounds); 2561 } 2562 2563 newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta); 2564 2565 PrepareForAnimation(); 2566 2567 tab_closing->set_closing(true); 2568 2569 // We still need to paint the tab until we actually remove it. Put it in 2570 // tabs_closing_map_ so we can find it. 2571 RemoveTabFromViewModel(model_index); 2572 2573 AnimateToIdealBounds(); 2574 2575 gfx::Rect tab_bounds = tab_closing->bounds(); 2576 tab_bounds.set_width(0); 2577 bounds_animator_.AnimateViewTo(tab_closing, tab_bounds); 2578 2579 // Register delegate to do cleanup when done, BoundsAnimator takes 2580 // ownership of RemoveTabDelegate. 2581 bounds_animator_.SetAnimationDelegate( 2582 tab_closing, 2583 new RemoveTabDelegate(this, tab_closing), 2584 true); 2585} 2586 2587bool TabStrip::IsPointInTab(Tab* tab, 2588 const gfx::Point& point_in_tabstrip_coords) { 2589 gfx::Point point_in_tab_coords(point_in_tabstrip_coords); 2590 View::ConvertPointToTarget(this, tab, &point_in_tab_coords); 2591 return tab->HitTestPoint(point_in_tab_coords); 2592} 2593 2594int TabStrip::GetStartXForNormalTabs() const { 2595 int mini_tab_count = GetMiniTabCount(); 2596 if (mini_tab_count == 0) 2597 return 0; 2598 return mini_tab_count * (Tab::GetMiniWidth() + tab_h_offset()) + 2599 kMiniToNonMiniGap; 2600} 2601 2602Tab* TabStrip::FindTabForEvent(const gfx::Point& point) { 2603 if (touch_layout_.get()) { 2604 int active_tab_index = touch_layout_->active_index(); 2605 if (active_tab_index != -1) { 2606 Tab* tab = FindTabForEventFrom(point, active_tab_index, -1); 2607 if (!tab) 2608 tab = FindTabForEventFrom(point, active_tab_index + 1, 1); 2609 return tab; 2610 } else if (tab_count()) { 2611 return FindTabForEventFrom(point, 0, 1); 2612 } 2613 } else { 2614 for (int i = 0; i < tab_count(); ++i) { 2615 if (IsPointInTab(tab_at(i), point)) 2616 return tab_at(i); 2617 } 2618 } 2619 return NULL; 2620} 2621 2622Tab* TabStrip::FindTabForEventFrom(const gfx::Point& point, 2623 int start, 2624 int delta) { 2625 // |start| equals tab_count() when there are only pinned tabs. 2626 if (start == tab_count()) 2627 start += delta; 2628 for (int i = start; i >= 0 && i < tab_count(); i += delta) { 2629 if (IsPointInTab(tab_at(i), point)) 2630 return tab_at(i); 2631 } 2632 return NULL; 2633} 2634 2635views::View* TabStrip::FindTabHitByPoint(const gfx::Point& point) { 2636 // The display order doesn't necessarily match the child list order, so we 2637 // walk the display list hit-testing Tabs. Since the active tab always 2638 // renders on top of adjacent tabs, it needs to be hit-tested before any 2639 // left-adjacent Tab, so we look ahead for it as we walk. 2640 for (int i = 0; i < tab_count(); ++i) { 2641 Tab* next_tab = i < (tab_count() - 1) ? tab_at(i + 1) : NULL; 2642 if (next_tab && next_tab->IsActive() && IsPointInTab(next_tab, point)) 2643 return next_tab; 2644 if (IsPointInTab(tab_at(i), point)) 2645 return tab_at(i); 2646 } 2647 2648 return NULL; 2649} 2650 2651std::vector<int> TabStrip::GetTabXCoordinates() { 2652 std::vector<int> results; 2653 for (int i = 0; i < tab_count(); ++i) 2654 results.push_back(ideal_bounds(i).x()); 2655 return results; 2656} 2657 2658void TabStrip::SwapLayoutIfNecessary() { 2659 bool needs_touch = NeedsTouchLayout(); 2660 bool using_touch = touch_layout_.get() != NULL; 2661 if (needs_touch == using_touch) 2662 return; 2663 2664 if (needs_touch) { 2665 gfx::Size tab_size(Tab::GetMinimumSelectedSize()); 2666 tab_size.set_width(Tab::GetTouchWidth()); 2667 touch_layout_.reset(new StackedTabStripLayout( 2668 tab_size, 2669 tab_h_offset(), 2670 kStackedPadding, 2671 kMaxStackedCount, 2672 &tabs_)); 2673 touch_layout_->SetWidth(width() - new_tab_button_width()); 2674 // This has to be after SetWidth() as SetWidth() is going to reset the 2675 // bounds of the mini-tabs (since StackedTabStripLayout doesn't yet know how 2676 // many mini-tabs there are). 2677 GenerateIdealBoundsForMiniTabs(NULL); 2678 touch_layout_->SetXAndMiniCount(GetStartXForNormalTabs(), 2679 GetMiniTabCount()); 2680 touch_layout_->SetActiveIndex(controller_->GetActiveIndex()); 2681 } else { 2682 touch_layout_.reset(); 2683 } 2684 PrepareForAnimation(); 2685 GenerateIdealBounds(); 2686 AnimateToIdealBounds(); 2687} 2688 2689bool TabStrip::NeedsTouchLayout() const { 2690 if (layout_type_ == TAB_STRIP_LAYOUT_SHRINK) 2691 return false; 2692 2693 int mini_tab_count = GetMiniTabCount(); 2694 int normal_count = tab_count() - mini_tab_count; 2695 if (normal_count <= 1 || normal_count == mini_tab_count) 2696 return false; 2697 int x = GetStartXForNormalTabs(); 2698 int available_width = width() - x - new_tab_button_width(); 2699 return (Tab::GetTouchWidth() * normal_count + 2700 tab_h_offset() * (normal_count - 1)) > available_width; 2701} 2702 2703void TabStrip::SetResetToShrinkOnExit(bool value) { 2704 if (!GetAdjustLayout()) 2705 return; 2706 2707 if (value && layout_type_ == TAB_STRIP_LAYOUT_SHRINK) 2708 value = false; // We're already at TAB_STRIP_LAYOUT_SHRINK. 2709 2710 if (value == reset_to_shrink_on_exit_) 2711 return; 2712 2713 reset_to_shrink_on_exit_ = value; 2714 // Add an observer so we know when the mouse moves out of the tabstrip. 2715 if (reset_to_shrink_on_exit_) 2716 AddMessageLoopObserver(); 2717 else 2718 RemoveMessageLoopObserver(); 2719} 2720 2721bool TabStrip::GetAdjustLayout() const { 2722 if (!adjust_layout_) 2723 return false; 2724 2725#if !defined(OS_CHROMEOS) 2726 if (ui::GetDisplayLayout() != ui::LAYOUT_TOUCH) 2727 return false; 2728#endif 2729 2730 return true; 2731} 2732