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