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