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