tab_strip.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 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#include <algorithm> 8 9#include "base/compiler_specific.h" 10#include "base/stl_util-inl.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/defaults.h" 13#include "chrome/browser/themes/browser_theme_provider.h" 14#include "chrome/browser/ui/view_ids.h" 15#include "chrome/browser/ui/views/tabs/tab.h" 16#include "chrome/browser/ui/views/tabs/tab_strip_controller.h" 17#include "grit/generated_resources.h" 18#include "grit/theme_resources.h" 19#include "ui/base/animation/animation_container.h" 20#include "ui/base/dragdrop/drag_drop_types.h" 21#include "ui/base/l10n/l10n_util.h" 22#include "ui/base/resource/resource_bundle.h" 23#include "ui/gfx/canvas_skia.h" 24#include "ui/gfx/path.h" 25#include "ui/gfx/size.h" 26#include "views/controls/image_view.h" 27#include "views/widget/default_theme_provider.h" 28#include "views/window/non_client_view.h" 29#include "views/window/window.h" 30 31#if defined(OS_WIN) 32#include "views/widget/monitor_win.h" 33#include "views/widget/widget_win.h" 34#elif defined(OS_LINUX) 35#include "views/widget/widget_gtk.h" 36#endif 37 38#undef min 39#undef max 40 41#if defined(COMPILER_GCC) 42// Squash false positive signed overflow warning in GenerateStartAndEndWidths 43// when doing 'start_tab_count < end_tab_count'. 44#pragma GCC diagnostic ignored "-Wstrict-overflow" 45#endif 46 47using views::DropTargetEvent; 48 49static const int kNewTabButtonHOffset = -5; 50static const int kNewTabButtonVOffset = 5; 51static const int kSuspendAnimationsTimeMs = 200; 52static const int kTabHOffset = -16; 53static const int kTabStripAnimationVSlop = 40; 54 55// Size of the drop indicator. 56static int drop_indicator_width; 57static int drop_indicator_height; 58 59static inline int Round(double x) { 60 // Why oh why is this not in a standard header? 61 return static_cast<int>(floor(x + 0.5)); 62} 63 64namespace { 65 66/////////////////////////////////////////////////////////////////////////////// 67// NewTabButton 68// 69// A subclass of button that hit-tests to the shape of the new tab button. 70 71class NewTabButton : public views::ImageButton { 72 public: 73 explicit NewTabButton(views::ButtonListener* listener) 74 : views::ImageButton(listener) { 75 } 76 virtual ~NewTabButton() {} 77 78 protected: 79 // Overridden from views::View: 80 virtual bool HasHitTestMask() const { 81 // When the button is sized to the top of the tab strip we want the user to 82 // be able to click on complete bounds, and so don't return a custom hit 83 // mask. 84 return !browser_defaults::kSizeTabButtonToTopOfTabStrip; 85 } 86 virtual void GetHitTestMask(gfx::Path* path) const { 87 DCHECK(path); 88 89 SkScalar w = SkIntToScalar(width()); 90 91 // These values are defined by the shape of the new tab bitmap. Should that 92 // bitmap ever change, these values will need to be updated. They're so 93 // custom it's not really worth defining constants for. 94 path->moveTo(0, 1); 95 path->lineTo(w - 7, 1); 96 path->lineTo(w - 4, 4); 97 path->lineTo(w, 16); 98 path->lineTo(w - 1, 17); 99 path->lineTo(7, 17); 100 path->lineTo(4, 13); 101 path->lineTo(0, 1); 102 path->close(); 103 } 104 105 private: 106 DISALLOW_COPY_AND_ASSIGN(NewTabButton); 107}; 108 109} // namespace 110 111/////////////////////////////////////////////////////////////////////////////// 112// TabStrip, public: 113 114// static 115const int TabStrip::mini_to_non_mini_gap_ = 3; 116 117TabStrip::TabStrip(TabStripController* controller) 118 : BaseTabStrip(controller, BaseTabStrip::HORIZONTAL_TAB_STRIP), 119 current_unselected_width_(Tab::GetStandardSize().width()), 120 current_selected_width_(Tab::GetStandardSize().width()), 121 available_width_for_tabs_(-1), 122 in_tab_close_(false), 123 animation_container_(new ui::AnimationContainer()) { 124 Init(); 125} 126 127TabStrip::~TabStrip() { 128 // The animations may reference the tabs. Shut down the animation before we 129 // delete the tabs. 130 StopAnimating(false); 131 132 DestroyDragController(); 133 134 // Make sure we unhook ourselves as a message loop observer so that we don't 135 // crash in the case where the user closes the window after closing a tab 136 // but before moving the mouse. 137 RemoveMessageLoopObserver(); 138 139 // The children (tabs) may callback to us from their destructor. Delete them 140 // so that if they call back we aren't in a weird state. 141 RemoveAllChildViews(true); 142} 143 144void TabStrip::InitTabStripButtons() { 145 newtab_button_ = new NewTabButton(this); 146 if (browser_defaults::kSizeTabButtonToTopOfTabStrip) { 147 newtab_button_->SetImageAlignment(views::ImageButton::ALIGN_LEFT, 148 views::ImageButton::ALIGN_BOTTOM); 149 } 150 LoadNewTabButtonImage(); 151 newtab_button_->SetAccessibleName( 152 l10n_util::GetStringUTF16(IDS_ACCNAME_NEWTAB)); 153 AddChildView(newtab_button_); 154} 155 156gfx::Rect TabStrip::GetNewTabButtonBounds() { 157 return newtab_button_->bounds(); 158} 159 160void TabStrip::MouseMovedOutOfView() { 161 ResizeLayoutTabs(); 162} 163 164//////////////////////////////////////////////////////////////////////////////// 165// TabStrip, BaseTabStrip implementation: 166 167void TabStrip::SetBackgroundOffset(const gfx::Point& offset) { 168 for (int i = 0; i < tab_count(); ++i) 169 GetTabAtTabDataIndex(i)->set_background_offset(offset); 170} 171 172bool TabStrip::IsPositionInWindowCaption(const gfx::Point& point) { 173 views::View* v = GetEventHandlerForPoint(point); 174 175 // If there is no control at this location, claim the hit was in the title 176 // bar to get a move action. 177 if (v == this) 178 return true; 179 180 // Check to see if the point is within the non-button parts of the new tab 181 // button. The button has a non-rectangular shape, so if it's not in the 182 // visual portions of the button we treat it as a click to the caption. 183 gfx::Point point_in_newtab_coords(point); 184 View::ConvertPointToView(this, newtab_button_, &point_in_newtab_coords); 185 if (newtab_button_->bounds().Contains(point) && 186 !newtab_button_->HitTest(point_in_newtab_coords)) { 187 return true; 188 } 189 190 // All other regions, including the new Tab button, should be considered part 191 // of the containing Window's client area so that regular events can be 192 // processed for them. 193 return false; 194} 195 196void TabStrip::PrepareForCloseAt(int model_index) { 197 if (!in_tab_close_ && IsAnimating()) { 198 // Cancel any current animations. We do this as remove uses the current 199 // ideal bounds and we need to know ideal bounds is in a good state. 200 StopAnimating(true); 201 } 202 203 int model_count = GetModelCount(); 204 if (model_index + 1 != model_count && model_count > 1) { 205 // The user is about to close a tab other than the last tab. Set 206 // available_width_for_tabs_ so that if we do a layout we don't position a 207 // tab past the end of the second to last tab. We do this so that as the 208 // user closes tabs with the mouse a tab continues to fall under the mouse. 209 available_width_for_tabs_ = GetAvailableWidthForTabs( 210 GetTabAtModelIndex(model_count - 2)); 211 } 212 213 in_tab_close_ = true; 214 AddMessageLoopObserver(); 215} 216 217void TabStrip::RemoveTabAt(int model_index) { 218 if (in_tab_close_ && model_index != GetModelCount()) 219 StartMouseInitiatedRemoveTabAnimation(model_index); 220 else 221 StartRemoveTabAnimation(model_index); 222} 223 224void TabStrip::SelectTabAt(int old_model_index, int new_model_index) { 225 // We have "tiny tabs" if the tabs are so tiny that the unselected ones are 226 // a different size to the selected ones. 227 bool tiny_tabs = current_unselected_width_ != current_selected_width_; 228 if (!IsAnimating() && (!in_tab_close_ || tiny_tabs)) { 229 DoLayout(); 230 } else { 231 SchedulePaint(); 232 } 233 234 if (old_model_index >= 0) { 235 GetTabAtTabDataIndex(ModelIndexToTabIndex(old_model_index))-> 236 StopMiniTabTitleAnimation(); 237 } 238} 239 240void TabStrip::TabTitleChangedNotLoading(int model_index) { 241 Tab* tab = GetTabAtModelIndex(model_index); 242 if (tab->data().mini && !tab->IsSelected()) 243 tab->StartMiniTabTitleAnimation(); 244} 245 246void TabStrip::StartHighlight(int model_index) { 247 GetTabAtModelIndex(model_index)->StartPulse(); 248} 249 250void TabStrip::StopAllHighlighting() { 251 for (int i = 0; i < tab_count(); ++i) 252 GetTabAtTabDataIndex(i)->StopPulse(); 253} 254 255BaseTab* TabStrip::CreateTabForDragging() { 256 Tab* tab = new Tab(NULL); 257 // Make sure the dragged tab shares our theme provider. We need to explicitly 258 // do this as during dragging there isn't a theme provider. 259 tab->set_theme_provider(GetThemeProvider()); 260 return tab; 261} 262 263/////////////////////////////////////////////////////////////////////////////// 264// TabStrip, views::View overrides: 265 266void TabStrip::PaintChildren(gfx::Canvas* canvas) { 267 // Tabs are painted in reverse order, so they stack to the left. 268 Tab* selected_tab = NULL; 269 Tab* dragging_tab = NULL; 270 271 for (int i = tab_count() - 1; i >= 0; --i) { 272 Tab* tab = GetTabAtTabDataIndex(i); 273 // We must ask the _Tab's_ model, not ourselves, because in some situations 274 // the model will be different to this object, e.g. when a Tab is being 275 // removed after its TabContents has been destroyed. 276 if (tab->dragging()) { 277 dragging_tab = tab; 278 } else if (!tab->IsSelected()) { 279 tab->Paint(canvas); 280 } else { 281 selected_tab = tab; 282 } 283 } 284 285 if (GetWindow()->non_client_view()->UseNativeFrame()) { 286 // Make sure unselected tabs are somewhat transparent. 287 SkPaint paint; 288 paint.setColor(SkColorSetARGB(200, 255, 255, 255)); 289 paint.setXfermodeMode(SkXfermode::kDstIn_Mode); 290 paint.setStyle(SkPaint::kFill_Style); 291 canvas->DrawRectInt(0, 0, width(), 292 height() - 2, // Visible region that overlaps the toolbar. 293 paint); 294 } 295 296 // Paint the selected tab last, so it overlaps all the others. 297 if (selected_tab) 298 selected_tab->Paint(canvas); 299 300 // Paint the New Tab button. 301 newtab_button_->Paint(canvas); 302 303 // And the dragged tab. 304 if (dragging_tab) 305 dragging_tab->Paint(canvas); 306} 307 308// Overridden to support automation. See automation_proxy_uitest.cc. 309const views::View* TabStrip::GetViewByID(int view_id) const { 310 if (tab_count() > 0) { 311 if (view_id == VIEW_ID_TAB_LAST) { 312 return GetTabAtTabDataIndex(tab_count() - 1); 313 } else if ((view_id >= VIEW_ID_TAB_0) && (view_id < VIEW_ID_TAB_LAST)) { 314 int index = view_id - VIEW_ID_TAB_0; 315 if (index >= 0 && index < tab_count()) { 316 return GetTabAtTabDataIndex(index); 317 } else { 318 return NULL; 319 } 320 } 321 } 322 323 return View::GetViewByID(view_id); 324} 325 326gfx::Size TabStrip::GetPreferredSize() { 327 return gfx::Size(0, Tab::GetMinimumUnselectedSize().height()); 328} 329 330void TabStrip::OnDragEntered(const DropTargetEvent& event) { 331 // Force animations to stop, otherwise it makes the index calculation tricky. 332 StopAnimating(true); 333 334 UpdateDropIndex(event); 335} 336 337int TabStrip::OnDragUpdated(const DropTargetEvent& event) { 338 UpdateDropIndex(event); 339 return GetDropEffect(event); 340} 341 342void TabStrip::OnDragExited() { 343 SetDropIndex(-1, false); 344} 345 346int TabStrip::OnPerformDrop(const DropTargetEvent& event) { 347 if (!drop_info_.get()) 348 return ui::DragDropTypes::DRAG_NONE; 349 350 const int drop_index = drop_info_->drop_index; 351 const bool drop_before = drop_info_->drop_before; 352 353 // Hide the drop indicator. 354 SetDropIndex(-1, false); 355 356 GURL url; 357 string16 title; 358 if (!event.data().GetURLAndTitle(&url, &title) || !url.is_valid()) 359 return ui::DragDropTypes::DRAG_NONE; 360 361 controller()->PerformDrop(drop_before, drop_index, url); 362 363 return GetDropEffect(event); 364} 365 366AccessibilityTypes::Role TabStrip::GetAccessibleRole() { 367 return AccessibilityTypes::ROLE_PAGETABLIST; 368} 369 370views::View* TabStrip::GetEventHandlerForPoint(const gfx::Point& point) { 371 // Return any view that isn't a Tab or this TabStrip immediately. We don't 372 // want to interfere. 373 views::View* v = View::GetEventHandlerForPoint(point); 374 if (v && v != this && v->GetClassName() != Tab::kViewClassName) 375 return v; 376 377 // The display order doesn't necessarily match the child list order, so we 378 // walk the display list hit-testing Tabs. Since the selected tab always 379 // renders on top of adjacent tabs, it needs to be hit-tested before any 380 // left-adjacent Tab, so we look ahead for it as we walk. 381 for (int i = 0; i < tab_count(); ++i) { 382 Tab* next_tab = i < (tab_count() - 1) ? GetTabAtTabDataIndex(i + 1) : NULL; 383 if (next_tab && next_tab->IsSelected() && IsPointInTab(next_tab, point)) 384 return next_tab; 385 Tab* tab = GetTabAtTabDataIndex(i); 386 if (IsPointInTab(tab, point)) 387 return tab; 388 } 389 390 // No need to do any floating view stuff, we don't use them in the TabStrip. 391 return this; 392} 393 394void TabStrip::OnThemeChanged() { 395 LoadNewTabButtonImage(); 396} 397 398BaseTab* TabStrip::CreateTab() { 399 Tab* tab = new Tab(this); 400 tab->set_animation_container(animation_container_.get()); 401 return tab; 402} 403 404void TabStrip::StartInsertTabAnimation(int model_index, bool foreground) { 405 PrepareForAnimation(); 406 407 // The TabStrip can now use its entire width to lay out Tabs. 408 in_tab_close_ = false; 409 available_width_for_tabs_ = -1; 410 411 GenerateIdealBounds(); 412 413 int tab_data_index = ModelIndexToTabIndex(model_index); 414 BaseTab* tab = base_tab_at_tab_index(tab_data_index); 415 if (model_index == 0) { 416 tab->SetBounds(0, ideal_bounds(tab_data_index).y(), 0, 417 ideal_bounds(tab_data_index).height()); 418 } else { 419 BaseTab* last_tab = base_tab_at_tab_index(tab_data_index - 1); 420 tab->SetBounds(last_tab->bounds().right() + kTabHOffset, 421 ideal_bounds(tab_data_index).y(), 0, 422 ideal_bounds(tab_data_index).height()); 423 } 424 425 AnimateToIdealBounds(); 426} 427 428void TabStrip::AnimateToIdealBounds() { 429 for (int i = 0; i < tab_count(); ++i) { 430 Tab* tab = GetTabAtTabDataIndex(i); 431 if (!tab->closing() && !tab->dragging()) 432 bounds_animator().AnimateViewTo(tab, ideal_bounds(i)); 433 } 434 435 bounds_animator().AnimateViewTo(newtab_button_, newtab_button_bounds_); 436} 437 438bool TabStrip::ShouldHighlightCloseButtonAfterRemove() { 439 return in_tab_close_; 440} 441 442void TabStrip::DoLayout() { 443 BaseTabStrip::DoLayout(); 444 445 newtab_button_->SetBoundsRect(newtab_button_bounds_); 446} 447 448void TabStrip::ViewHierarchyChanged(bool is_add, 449 views::View* parent, 450 views::View* child) { 451 if (is_add && child == this) 452 InitTabStripButtons(); 453} 454 455/////////////////////////////////////////////////////////////////////////////// 456// TabStrip, Tab::Delegate implementation: 457 458bool TabStrip::IsTabSelected(const BaseTab* btr) const { 459 const Tab* tab = static_cast<const Tab*>(btr); 460 return !tab->closing() && BaseTabStrip::IsTabSelected(btr); 461} 462 463/////////////////////////////////////////////////////////////////////////////// 464// TabStrip, views::BaseButton::ButtonListener implementation: 465 466void TabStrip::ButtonPressed(views::Button* sender, const views::Event& event) { 467 if (sender == newtab_button_) 468 controller()->CreateNewTab(); 469} 470 471/////////////////////////////////////////////////////////////////////////////// 472// TabStrip, private: 473 474void TabStrip::Init() { 475 SetID(VIEW_ID_TAB_STRIP); 476 newtab_button_bounds_.SetRect(0, 0, kNewTabButtonWidth, kNewTabButtonHeight); 477 if (browser_defaults::kSizeTabButtonToTopOfTabStrip) { 478 newtab_button_bounds_.set_height( 479 kNewTabButtonHeight + kNewTabButtonVOffset); 480 } 481 if (drop_indicator_width == 0) { 482 // Direction doesn't matter, both images are the same size. 483 SkBitmap* drop_image = GetDropArrowImage(true); 484 drop_indicator_width = drop_image->width(); 485 drop_indicator_height = drop_image->height(); 486 } 487} 488 489void TabStrip::LoadNewTabButtonImage() { 490 ui::ThemeProvider* tp = GetThemeProvider(); 491 492 // If we don't have a theme provider yet, it means we do not have a 493 // root view, and are therefore in a test. 494 bool in_test = false; 495 if (tp == NULL) { 496 tp = new views::DefaultThemeProvider(); 497 in_test = true; 498 } 499 500 SkBitmap* bitmap = tp->GetBitmapNamed(IDR_NEWTAB_BUTTON); 501 SkColor color = tp->GetColor(BrowserThemeProvider::COLOR_BUTTON_BACKGROUND); 502 SkBitmap* background = tp->GetBitmapNamed( 503 IDR_THEME_WINDOW_CONTROL_BACKGROUND); 504 505 newtab_button_->SetImage(views::CustomButton::BS_NORMAL, bitmap); 506 newtab_button_->SetImage(views::CustomButton::BS_PUSHED, 507 tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_P)); 508 newtab_button_->SetImage(views::CustomButton::BS_HOT, 509 tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_H)); 510 newtab_button_->SetBackground(color, background, 511 tp->GetBitmapNamed(IDR_NEWTAB_BUTTON_MASK)); 512 if (in_test) 513 delete tp; 514} 515 516Tab* TabStrip::GetTabAtTabDataIndex(int tab_data_index) const { 517 return static_cast<Tab*>(base_tab_at_tab_index(tab_data_index)); 518} 519 520Tab* TabStrip::GetTabAtModelIndex(int model_index) const { 521 return GetTabAtTabDataIndex(ModelIndexToTabIndex(model_index)); 522} 523 524void TabStrip::GetCurrentTabWidths(double* unselected_width, 525 double* selected_width) const { 526 *unselected_width = current_unselected_width_; 527 *selected_width = current_selected_width_; 528} 529 530void TabStrip::GetDesiredTabWidths(int tab_count, 531 int mini_tab_count, 532 double* unselected_width, 533 double* selected_width) const { 534 DCHECK(tab_count >= 0 && mini_tab_count >= 0 && mini_tab_count <= tab_count); 535 const double min_unselected_width = Tab::GetMinimumUnselectedSize().width(); 536 const double min_selected_width = Tab::GetMinimumSelectedSize().width(); 537 538 *unselected_width = min_unselected_width; 539 *selected_width = min_selected_width; 540 541 if (tab_count == 0) { 542 // Return immediately to avoid divide-by-zero below. 543 return; 544 } 545 546 // Determine how much space we can actually allocate to tabs. 547 int available_width; 548 if (available_width_for_tabs_ < 0) { 549 available_width = width(); 550 available_width -= (kNewTabButtonHOffset + newtab_button_bounds_.width()); 551 } else { 552 // Interesting corner case: if |available_width_for_tabs_| > the result 553 // of the calculation in the conditional arm above, the strip is in 554 // overflow. We can either use the specified width or the true available 555 // width here; the first preserves the consistent "leave the last tab under 556 // the user's mouse so they can close many tabs" behavior at the cost of 557 // prolonging the glitchy appearance of the overflow state, while the second 558 // gets us out of overflow as soon as possible but forces the user to move 559 // their mouse for a few tabs' worth of closing. We choose visual 560 // imperfection over behavioral imperfection and select the first option. 561 available_width = available_width_for_tabs_; 562 } 563 564 if (mini_tab_count > 0) { 565 available_width -= mini_tab_count * (Tab::GetMiniWidth() + kTabHOffset); 566 tab_count -= mini_tab_count; 567 if (tab_count == 0) { 568 *selected_width = *unselected_width = Tab::GetStandardSize().width(); 569 return; 570 } 571 // Account for gap between the last mini-tab and first non-mini-tab. 572 available_width -= mini_to_non_mini_gap_; 573 } 574 575 // Calculate the desired tab widths by dividing the available space into equal 576 // portions. Don't let tabs get larger than the "standard width" or smaller 577 // than the minimum width for each type, respectively. 578 const int total_offset = kTabHOffset * (tab_count - 1); 579 const double desired_tab_width = std::min((static_cast<double>( 580 available_width - total_offset) / static_cast<double>(tab_count)), 581 static_cast<double>(Tab::GetStandardSize().width())); 582 *unselected_width = std::max(desired_tab_width, min_unselected_width); 583 *selected_width = std::max(desired_tab_width, min_selected_width); 584 585 // When there are multiple tabs, we'll have one selected and some unselected 586 // tabs. If the desired width was between the minimum sizes of these types, 587 // try to shrink the tabs with the smaller minimum. For example, if we have 588 // a strip of width 10 with 4 tabs, the desired width per tab will be 2.5. If 589 // selected tabs have a minimum width of 4 and unselected tabs have a minimum 590 // width of 1, the above code would set *unselected_width = 2.5, 591 // *selected_width = 4, which results in a total width of 11.5. Instead, we 592 // want to set *unselected_width = 2, *selected_width = 4, for a total width 593 // of 10. 594 if (tab_count > 1) { 595 if ((min_unselected_width < min_selected_width) && 596 (desired_tab_width < min_selected_width)) { 597 // Unselected width = (total width - selected width) / (num_tabs - 1) 598 *unselected_width = std::max(static_cast<double>( 599 available_width - total_offset - min_selected_width) / 600 static_cast<double>(tab_count - 1), min_unselected_width); 601 } else if ((min_unselected_width > min_selected_width) && 602 (desired_tab_width < min_unselected_width)) { 603 // Selected width = (total width - (unselected width * (num_tabs - 1))) 604 *selected_width = std::max(available_width - total_offset - 605 (min_unselected_width * (tab_count - 1)), min_selected_width); 606 } 607 } 608} 609 610void TabStrip::ResizeLayoutTabs() { 611 // We've been called back after the TabStrip has been emptied out (probably 612 // just prior to the window being destroyed). We need to do nothing here or 613 // else GetTabAt below will crash. 614 if (tab_count() == 0) 615 return; 616 617 // It is critically important that this is unhooked here, otherwise we will 618 // keep spying on messages forever. 619 RemoveMessageLoopObserver(); 620 621 in_tab_close_ = false; 622 available_width_for_tabs_ = -1; 623 int mini_tab_count = GetMiniTabCount(); 624 if (mini_tab_count == tab_count()) { 625 // Only mini-tabs, we know the tab widths won't have changed (all 626 // mini-tabs have the same width), so there is nothing to do. 627 return; 628 } 629 Tab* first_tab = GetTabAtTabDataIndex(mini_tab_count); 630 double unselected, selected; 631 GetDesiredTabWidths(tab_count(), mini_tab_count, &unselected, &selected); 632 int w = Round(first_tab->IsSelected() ? selected : selected); 633 634 // We only want to run the animation if we're not already at the desired 635 // size. 636 if (abs(first_tab->width() - w) > 1) 637 StartResizeLayoutAnimation(); 638} 639 640void TabStrip::AddMessageLoopObserver() { 641 if (!mouse_watcher_.get()) { 642 mouse_watcher_.reset( 643 new views::MouseWatcher(this, this, 644 gfx::Insets(0, 0, kTabStripAnimationVSlop, 0))); 645 } 646 mouse_watcher_->Start(); 647} 648 649void TabStrip::RemoveMessageLoopObserver() { 650 mouse_watcher_.reset(NULL); 651} 652 653gfx::Rect TabStrip::GetDropBounds(int drop_index, 654 bool drop_before, 655 bool* is_beneath) { 656 DCHECK(drop_index != -1); 657 int center_x; 658 if (drop_index < tab_count()) { 659 Tab* tab = GetTabAtTabDataIndex(drop_index); 660 if (drop_before) 661 center_x = tab->x() - (kTabHOffset / 2); 662 else 663 center_x = tab->x() + (tab->width() / 2); 664 } else { 665 Tab* last_tab = GetTabAtTabDataIndex(drop_index - 1); 666 center_x = last_tab->x() + last_tab->width() + (kTabHOffset / 2); 667 } 668 669 // Mirror the center point if necessary. 670 center_x = GetMirroredXInView(center_x); 671 672 // Determine the screen bounds. 673 gfx::Point drop_loc(center_x - drop_indicator_width / 2, 674 -drop_indicator_height); 675 ConvertPointToScreen(this, &drop_loc); 676 gfx::Rect drop_bounds(drop_loc.x(), drop_loc.y(), drop_indicator_width, 677 drop_indicator_height); 678 679 // If the rect doesn't fit on the monitor, push the arrow to the bottom. 680#if defined(OS_WIN) 681 gfx::Rect monitor_bounds = views::GetMonitorBoundsForRect(drop_bounds); 682 *is_beneath = (monitor_bounds.IsEmpty() || 683 !monitor_bounds.Contains(drop_bounds)); 684#else 685 *is_beneath = false; 686 NOTIMPLEMENTED(); 687#endif 688 if (*is_beneath) 689 drop_bounds.Offset(0, drop_bounds.height() + height()); 690 691 return drop_bounds; 692} 693 694void TabStrip::UpdateDropIndex(const DropTargetEvent& event) { 695 // If the UI layout is right-to-left, we need to mirror the mouse 696 // coordinates since we calculate the drop index based on the 697 // original (and therefore non-mirrored) positions of the tabs. 698 const int x = GetMirroredXInView(event.x()); 699 // We don't allow replacing the urls of mini-tabs. 700 for (int i = GetMiniTabCount(); i < tab_count(); ++i) { 701 Tab* tab = GetTabAtTabDataIndex(i); 702 const int tab_max_x = tab->x() + tab->width(); 703 const int hot_width = tab->width() / 3; 704 if (x < tab_max_x) { 705 if (x < tab->x() + hot_width) 706 SetDropIndex(i, true); 707 else if (x >= tab_max_x - hot_width) 708 SetDropIndex(i + 1, true); 709 else 710 SetDropIndex(i, false); 711 return; 712 } 713 } 714 715 // The drop isn't over a tab, add it to the end. 716 SetDropIndex(tab_count(), true); 717} 718 719void TabStrip::SetDropIndex(int tab_data_index, bool drop_before) { 720 if (tab_data_index == -1) { 721 if (drop_info_.get()) 722 drop_info_.reset(NULL); 723 return; 724 } 725 726 if (drop_info_.get() && drop_info_->drop_index == tab_data_index && 727 drop_info_->drop_before == drop_before) { 728 return; 729 } 730 731 bool is_beneath; 732 gfx::Rect drop_bounds = GetDropBounds(tab_data_index, drop_before, 733 &is_beneath); 734 735 if (!drop_info_.get()) { 736 drop_info_.reset(new DropInfo(tab_data_index, drop_before, !is_beneath)); 737 } else { 738 drop_info_->drop_index = tab_data_index; 739 drop_info_->drop_before = drop_before; 740 if (is_beneath == drop_info_->point_down) { 741 drop_info_->point_down = !is_beneath; 742 drop_info_->arrow_view->SetImage( 743 GetDropArrowImage(drop_info_->point_down)); 744 } 745 } 746 747 // Reposition the window. Need to show it too as the window is initially 748 // hidden. 749 750#if defined(OS_WIN) 751 drop_info_->arrow_window->SetWindowPos( 752 HWND_TOPMOST, drop_bounds.x(), drop_bounds.y(), drop_bounds.width(), 753 drop_bounds.height(), SWP_NOACTIVATE | SWP_SHOWWINDOW); 754#else 755 drop_info_->arrow_window->SetBounds(drop_bounds); 756 drop_info_->arrow_window->Show(); 757#endif 758} 759 760int TabStrip::GetDropEffect(const views::DropTargetEvent& event) { 761 const int source_ops = event.source_operations(); 762 if (source_ops & ui::DragDropTypes::DRAG_COPY) 763 return ui::DragDropTypes::DRAG_COPY; 764 if (source_ops & ui::DragDropTypes::DRAG_LINK) 765 return ui::DragDropTypes::DRAG_LINK; 766 return ui::DragDropTypes::DRAG_MOVE; 767} 768 769// static 770SkBitmap* TabStrip::GetDropArrowImage(bool is_down) { 771 return ResourceBundle::GetSharedInstance().GetBitmapNamed( 772 is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP); 773} 774 775// TabStrip::DropInfo ---------------------------------------------------------- 776 777TabStrip::DropInfo::DropInfo(int drop_index, bool drop_before, bool point_down) 778 : drop_index(drop_index), 779 drop_before(drop_before), 780 point_down(point_down) { 781 arrow_view = new views::ImageView; 782 arrow_view->SetImage(GetDropArrowImage(point_down)); 783 784#if defined(OS_WIN) 785 arrow_window = new views::WidgetWin; 786 arrow_window->set_window_style(WS_POPUP); 787 arrow_window->set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE | 788 WS_EX_LAYERED | WS_EX_TRANSPARENT); 789#else 790 arrow_window = new views::WidgetGtk(views::WidgetGtk::TYPE_POPUP); 791 arrow_window->MakeTransparent(); 792#endif 793 arrow_window->Init( 794 NULL, 795 gfx::Rect(0, 0, drop_indicator_width, drop_indicator_height)); 796 arrow_window->SetContentsView(arrow_view); 797} 798 799TabStrip::DropInfo::~DropInfo() { 800 // Close eventually deletes the window, which deletes arrow_view too. 801 arrow_window->Close(); 802} 803 804/////////////////////////////////////////////////////////////////////////////// 805 806// Called from: 807// - BasicLayout 808// - Tab insertion/removal 809// - Tab reorder 810void TabStrip::GenerateIdealBounds() { 811 int non_closing_tab_count = 0; 812 int mini_tab_count = 0; 813 for (int i = 0; i < tab_count(); ++i) { 814 BaseTab* tab = base_tab_at_tab_index(i); 815 if (!tab->closing()) { 816 ++non_closing_tab_count; 817 if (tab->data().mini) 818 mini_tab_count++; 819 } 820 } 821 822 double unselected, selected; 823 GetDesiredTabWidths(non_closing_tab_count, mini_tab_count, &unselected, 824 &selected); 825 826 current_unselected_width_ = unselected; 827 current_selected_width_ = selected; 828 829 // NOTE: This currently assumes a tab's height doesn't differ based on 830 // selected state or the number of tabs in the strip! 831 int tab_height = Tab::GetStandardSize().height(); 832 double tab_x = 0; 833 bool last_was_mini = false; 834 for (int i = 0; i < tab_count(); ++i) { 835 Tab* tab = GetTabAtTabDataIndex(i); 836 if (!tab->closing()) { 837 double tab_width = unselected; 838 if (tab->data().mini) { 839 tab_width = Tab::GetMiniWidth(); 840 } else { 841 if (last_was_mini) { 842 // Give a bigger gap between mini and non-mini tabs. 843 tab_x += mini_to_non_mini_gap_; 844 } 845 if (tab->IsSelected()) 846 tab_width = selected; 847 } 848 double end_of_tab = tab_x + tab_width; 849 int rounded_tab_x = Round(tab_x); 850 set_ideal_bounds(i, 851 gfx::Rect(rounded_tab_x, 0, Round(end_of_tab) - rounded_tab_x, 852 tab_height)); 853 tab_x = end_of_tab + kTabHOffset; 854 last_was_mini = tab->data().mini; 855 } 856 } 857 858 // Update bounds of new tab button. 859 int new_tab_x; 860 int new_tab_y = browser_defaults::kSizeTabButtonToTopOfTabStrip ? 861 0 : kNewTabButtonVOffset; 862 if (abs(Round(unselected) - Tab::GetStandardSize().width()) > 1 && 863 !in_tab_close_) { 864 // We're shrinking tabs, so we need to anchor the New Tab button to the 865 // right edge of the TabStrip's bounds, rather than the right edge of the 866 // right-most Tab, otherwise it'll bounce when animating. 867 new_tab_x = width() - newtab_button_bounds_.width(); 868 } else { 869 new_tab_x = Round(tab_x - kTabHOffset) + kNewTabButtonHOffset; 870 } 871 newtab_button_bounds_.set_origin(gfx::Point(new_tab_x, new_tab_y)); 872} 873 874void TabStrip::StartResizeLayoutAnimation() { 875 PrepareForAnimation(); 876 GenerateIdealBounds(); 877 AnimateToIdealBounds(); 878} 879 880void TabStrip::StartMiniTabAnimation() { 881 in_tab_close_ = false; 882 available_width_for_tabs_ = -1; 883 884 PrepareForAnimation(); 885 886 GenerateIdealBounds(); 887 AnimateToIdealBounds(); 888} 889 890void TabStrip::StartMouseInitiatedRemoveTabAnimation(int model_index) { 891 // The user initiated the close. We want to persist the bounds of all the 892 // existing tabs, so we manually shift ideal_bounds then animate. 893 int tab_data_index = ModelIndexToTabIndex(model_index); 894 DCHECK(tab_data_index != tab_count()); 895 BaseTab* tab_closing = base_tab_at_tab_index(tab_data_index); 896 int delta = tab_closing->width() + kTabHOffset; 897 if (tab_closing->data().mini && model_index + 1 < GetModelCount() && 898 !GetBaseTabAtModelIndex(model_index + 1)->data().mini) { 899 delta += mini_to_non_mini_gap_; 900 } 901 902 for (int i = tab_data_index + 1; i < tab_count(); ++i) { 903 BaseTab* tab = base_tab_at_tab_index(i); 904 if (!tab->closing()) { 905 gfx::Rect bounds = ideal_bounds(i); 906 bounds.set_x(bounds.x() - delta); 907 set_ideal_bounds(i, bounds); 908 } 909 } 910 911 newtab_button_bounds_.set_x(newtab_button_bounds_.x() - delta); 912 913 PrepareForAnimation(); 914 915 // Mark the tab as closing. 916 tab_closing->set_closing(true); 917 918 AnimateToIdealBounds(); 919 920 gfx::Rect tab_bounds = tab_closing->bounds(); 921 if (type() == HORIZONTAL_TAB_STRIP) 922 tab_bounds.set_width(0); 923 else 924 tab_bounds.set_height(0); 925 bounds_animator().AnimateViewTo(tab_closing, tab_bounds); 926 927 // Register delegate to do cleanup when done, BoundsAnimator takes 928 // ownership of RemoveTabDelegate. 929 bounds_animator().SetAnimationDelegate(tab_closing, 930 CreateRemoveTabDelegate(tab_closing), 931 true); 932} 933 934int TabStrip::GetMiniTabCount() const { 935 int mini_count = 0; 936 for (int i = 0; i < tab_count(); ++i) { 937 if (base_tab_at_tab_index(i)->data().mini) 938 mini_count++; 939 else 940 return mini_count; 941 } 942 return mini_count; 943} 944 945int TabStrip::GetAvailableWidthForTabs(Tab* last_tab) const { 946 return last_tab->x() + last_tab->width(); 947} 948 949bool TabStrip::IsPointInTab(Tab* tab, 950 const gfx::Point& point_in_tabstrip_coords) { 951 gfx::Point point_in_tab_coords(point_in_tabstrip_coords); 952 View::ConvertPointToView(this, tab, &point_in_tab_coords); 953 return tab->HitTest(point_in_tab_coords); 954} 955