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