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