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