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