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