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/base_tab_strip.h"
      6 
      7 #include "base/logging.h"
      8 #include "chrome/browser/ui/view_ids.h"
      9 #include "chrome/browser/ui/views/tabs/dragged_tab_controller.h"
     10 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
     11 #include "views/widget/root_view.h"
     12 #include "views/window/window.h"
     13 
     14 #if defined(OS_WIN)
     15 #include "views/widget/widget_win.h"
     16 #endif
     17 
     18 namespace {
     19 
     20 // Animation delegate used when a dragged tab is released. When done sets the
     21 // dragging state to false.
     22 class ResetDraggingStateDelegate
     23     : public views::BoundsAnimator::OwnedAnimationDelegate {
     24  public:
     25   explicit ResetDraggingStateDelegate(BaseTab* tab) : tab_(tab) {
     26   }
     27 
     28   virtual void AnimationEnded(const ui::Animation* animation) {
     29     tab_->set_dragging(false);
     30   }
     31 
     32   virtual void AnimationCanceled(const ui::Animation* animation) {
     33     tab_->set_dragging(false);
     34   }
     35 
     36  private:
     37   BaseTab* tab_;
     38 
     39   DISALLOW_COPY_AND_ASSIGN(ResetDraggingStateDelegate);
     40 };
     41 
     42 }  // namespace
     43 
     44 // AnimationDelegate used when removing a tab. Does the necessary cleanup when
     45 // done.
     46 class BaseTabStrip::RemoveTabDelegate
     47     : public views::BoundsAnimator::OwnedAnimationDelegate {
     48  public:
     49   RemoveTabDelegate(BaseTabStrip* tab_strip, BaseTab* tab)
     50       : tabstrip_(tab_strip),
     51         tab_(tab) {
     52   }
     53 
     54   virtual void AnimationEnded(const ui::Animation* animation) {
     55     CompleteRemove();
     56   }
     57 
     58   virtual void AnimationCanceled(const ui::Animation* animation) {
     59     // We can be canceled for two interesting reasons:
     60     // . The tab we reference was dragged back into the tab strip. In this case
     61     //   we don't want to remove the tab (closing is false).
     62     // . The drag was completed before the animation completed
     63     //   (DestroyDraggedSourceTab). In this case we need to remove the tab
     64     //   (closing is true).
     65     if (tab_->closing())
     66       CompleteRemove();
     67   }
     68 
     69  private:
     70   void CompleteRemove() {
     71     if (!tab_->closing()) {
     72       // The tab was added back yet we weren't canceled. This shouldn't happen.
     73       NOTREACHED();
     74       return;
     75     }
     76     tabstrip_->RemoveAndDeleteTab(tab_);
     77     HighlightCloseButton();
     78   }
     79 
     80   // When the animation completes, we send the Container a message to simulate
     81   // a mouse moved event at the current mouse position. This tickles the Tab
     82   // the mouse is currently over to show the "hot" state of the close button.
     83   void HighlightCloseButton() {
     84     if (tabstrip_->IsDragSessionActive() ||
     85         !tabstrip_->ShouldHighlightCloseButtonAfterRemove()) {
     86       // This function is not required (and indeed may crash!) for removes
     87       // spawned by non-mouse closes and drag-detaches.
     88       return;
     89     }
     90 
     91 #if defined(OS_WIN)
     92     views::Widget* widget = tabstrip_->GetWidget();
     93     // This can be null during shutdown. See http://crbug.com/42737.
     94     if (!widget)
     95       return;
     96     // Force the close button (that slides under the mouse) to highlight by
     97     // saying the mouse just moved, but sending the same coordinates.
     98     DWORD pos = GetMessagePos();
     99     POINT cursor_point = {GET_X_LPARAM(pos), GET_Y_LPARAM(pos)};
    100     MapWindowPoints(NULL, widget->GetNativeView(), &cursor_point, 1);
    101 
    102     static_cast<views::WidgetWin*>(widget)->ResetLastMouseMoveFlag();
    103     // Return to message loop - otherwise we may disrupt some operation that's
    104     // in progress.
    105     SendMessage(widget->GetNativeView(), WM_MOUSEMOVE, 0,
    106                 MAKELPARAM(cursor_point.x, cursor_point.y));
    107 #else
    108     NOTIMPLEMENTED();
    109 #endif
    110   }
    111 
    112   BaseTabStrip* tabstrip_;
    113   BaseTab* tab_;
    114 
    115   DISALLOW_COPY_AND_ASSIGN(RemoveTabDelegate);
    116 };
    117 
    118 BaseTabStrip::BaseTabStrip(TabStripController* controller, Type type)
    119     : controller_(controller),
    120       type_(type),
    121       attaching_dragged_tab_(false),
    122       ALLOW_THIS_IN_INITIALIZER_LIST(bounds_animator_(this)) {
    123 }
    124 
    125 BaseTabStrip::~BaseTabStrip() {
    126 }
    127 
    128 void BaseTabStrip::AddTabAt(int model_index, const TabRendererData& data) {
    129   BaseTab* tab = CreateTab();
    130   tab->SetData(data);
    131 
    132   TabData d = { tab, gfx::Rect() };
    133   tab_data_.insert(tab_data_.begin() + ModelIndexToTabIndex(model_index), d);
    134 
    135   AddChildView(tab);
    136 
    137   // Don't animate the first tab, it looks weird, and don't animate anything
    138   // if the containing window isn't visible yet.
    139   if (tab_count() > 1 && GetWindow() && GetWindow()->IsVisible())
    140     StartInsertTabAnimation(model_index);
    141   else
    142     DoLayout();
    143 }
    144 
    145 void BaseTabStrip::MoveTab(int from_model_index, int to_model_index) {
    146   int from_tab_data_index = ModelIndexToTabIndex(from_model_index);
    147   BaseTab* tab = tab_data_[from_tab_data_index].tab;
    148   tab_data_.erase(tab_data_.begin() + from_tab_data_index);
    149 
    150   TabData data = {tab, gfx::Rect()};
    151   int to_tab_data_index = ModelIndexToTabIndex(to_model_index);
    152   tab_data_.insert(tab_data_.begin() + to_tab_data_index, data);
    153 
    154   StartMoveTabAnimation();
    155 }
    156 
    157 void BaseTabStrip::SetTabData(int model_index, const TabRendererData& data) {
    158   BaseTab* tab = GetBaseTabAtModelIndex(model_index);
    159   bool mini_state_changed = tab->data().mini != data.mini;
    160   tab->SetData(data);
    161 
    162   if (mini_state_changed) {
    163     if (GetWindow() && GetWindow()->IsVisible())
    164       StartMiniTabAnimation();
    165     else
    166       DoLayout();
    167   }
    168 }
    169 
    170 BaseTab* BaseTabStrip::GetBaseTabAtModelIndex(int model_index) const {
    171   return base_tab_at_tab_index(ModelIndexToTabIndex(model_index));
    172 }
    173 
    174 int BaseTabStrip::GetModelIndexOfBaseTab(const BaseTab* tab) const {
    175   for (int i = 0, model_index = 0; i < tab_count(); ++i) {
    176     BaseTab* current_tab = base_tab_at_tab_index(i);
    177     if (!current_tab->closing()) {
    178       if (current_tab == tab)
    179         return model_index;
    180       model_index++;
    181     } else if (current_tab == tab) {
    182       return -1;
    183     }
    184   }
    185   return -1;
    186 }
    187 
    188 int BaseTabStrip::GetModelCount() const {
    189   return controller_->GetCount();
    190 }
    191 
    192 bool BaseTabStrip::IsValidModelIndex(int model_index) const {
    193   return controller_->IsValidIndex(model_index);
    194 }
    195 
    196 int BaseTabStrip::ModelIndexToTabIndex(int model_index) const {
    197   int current_model_index = 0;
    198   for (int i = 0; i < tab_count(); ++i) {
    199     if (!base_tab_at_tab_index(i)->closing()) {
    200       if (current_model_index == model_index)
    201         return i;
    202       current_model_index++;
    203     }
    204   }
    205   return static_cast<int>(tab_data_.size());
    206 }
    207 
    208 bool BaseTabStrip::IsDragSessionActive() const {
    209   return drag_controller_.get() != NULL;
    210 }
    211 
    212 bool BaseTabStrip::IsActiveDropTarget() const {
    213   for (int i = 0; i < tab_count(); ++i) {
    214     BaseTab* tab = base_tab_at_tab_index(i);
    215     if (tab->dragging())
    216       return true;
    217   }
    218   return false;
    219 }
    220 
    221 bool BaseTabStrip::IsTabStripEditable() const {
    222   return !IsDragSessionActive() && !IsActiveDropTarget();
    223 }
    224 
    225 bool BaseTabStrip::IsTabStripCloseable() const {
    226   return !IsDragSessionActive();
    227 }
    228 
    229 void BaseTabStrip::UpdateLoadingAnimations() {
    230   controller_->UpdateLoadingAnimations();
    231 }
    232 
    233 void BaseTabStrip::SelectTab(BaseTab* tab) {
    234   int model_index = GetModelIndexOfBaseTab(tab);
    235   if (IsValidModelIndex(model_index))
    236     controller_->SelectTab(model_index);
    237 }
    238 
    239 void BaseTabStrip::ExtendSelectionTo(BaseTab* tab) {
    240   int model_index = GetModelIndexOfBaseTab(tab);
    241   if (IsValidModelIndex(model_index))
    242     controller_->ExtendSelectionTo(model_index);
    243 }
    244 
    245 void BaseTabStrip::ToggleSelected(BaseTab* tab) {
    246   int model_index = GetModelIndexOfBaseTab(tab);
    247   if (IsValidModelIndex(model_index))
    248     controller_->ToggleSelected(model_index);
    249 }
    250 
    251 void BaseTabStrip::AddSelectionFromAnchorTo(BaseTab* tab) {
    252   int model_index = GetModelIndexOfBaseTab(tab);
    253   if (IsValidModelIndex(model_index))
    254     controller_->AddSelectionFromAnchorTo(model_index);
    255 }
    256 
    257 void BaseTabStrip::CloseTab(BaseTab* tab) {
    258   // Find the closest model index. We do this so that the user can rapdily close
    259   // tabs and have the close click close the next tab.
    260   int model_index = 0;
    261   for (int i = 0; i < tab_count(); ++i) {
    262     BaseTab* current_tab = base_tab_at_tab_index(i);
    263     if (current_tab == tab)
    264       break;
    265     if (!current_tab->closing())
    266       model_index++;
    267   }
    268 
    269   if (IsValidModelIndex(model_index))
    270     controller_->CloseTab(model_index);
    271 }
    272 
    273 void BaseTabStrip::ShowContextMenuForTab(BaseTab* tab, const gfx::Point& p) {
    274   controller_->ShowContextMenuForTab(tab, p);
    275 }
    276 
    277 bool BaseTabStrip::IsActiveTab(const BaseTab* tab) const {
    278   int model_index = GetModelIndexOfBaseTab(tab);
    279   return IsValidModelIndex(model_index) &&
    280       controller_->IsActiveTab(model_index);
    281 }
    282 
    283 bool BaseTabStrip::IsTabSelected(const BaseTab* tab) const {
    284   int model_index = GetModelIndexOfBaseTab(tab);
    285   return IsValidModelIndex(model_index) &&
    286       controller_->IsTabSelected(model_index);
    287 }
    288 
    289 bool BaseTabStrip::IsTabPinned(const BaseTab* tab) const {
    290   if (tab->closing())
    291     return false;
    292 
    293   int model_index = GetModelIndexOfBaseTab(tab);
    294   return IsValidModelIndex(model_index) &&
    295       controller_->IsTabPinned(model_index);
    296 }
    297 
    298 bool BaseTabStrip::IsTabCloseable(const BaseTab* tab) const {
    299   int model_index = GetModelIndexOfBaseTab(tab);
    300   return !IsValidModelIndex(model_index) ||
    301       controller_->IsTabCloseable(model_index);
    302 }
    303 
    304 void BaseTabStrip::MaybeStartDrag(BaseTab* tab,
    305                                   const views::MouseEvent& event) {
    306   // Don't accidentally start any drag operations during animations if the
    307   // mouse is down... during an animation tabs are being resized automatically,
    308   // so the View system can misinterpret this easily if the mouse is down that
    309   // the user is dragging.
    310   if (IsAnimating() || tab->closing() ||
    311       controller_->HasAvailableDragActions() == 0) {
    312     return;
    313   }
    314   int model_index = GetModelIndexOfBaseTab(tab);
    315   if (!IsValidModelIndex(model_index)) {
    316     CHECK(false);
    317     return;
    318   }
    319   drag_controller_.reset(new DraggedTabController());
    320   std::vector<BaseTab*> tabs;
    321   int size_to_selected = 0;
    322   int x = tab->GetMirroredXInView(event.x());
    323   int y = event.y();
    324   // Build the set of selected tabs to drag and calculate the offset from the
    325   // first selected tab.
    326   for (int i = 0; i < tab_count(); ++i) {
    327     BaseTab* other_tab = base_tab_at_tab_index(i);
    328     if (IsTabSelected(other_tab) && !other_tab->closing()) {
    329       tabs.push_back(other_tab);
    330       if (other_tab == tab) {
    331         size_to_selected = GetSizeNeededForTabs(tabs);
    332         if (type() == HORIZONTAL_TAB_STRIP)
    333           x = size_to_selected - tab->width() + x;
    334         else
    335           y = size_to_selected - tab->height() + y;
    336       }
    337     }
    338   }
    339   DCHECK(!tabs.empty());
    340   DCHECK(std::find(tabs.begin(), tabs.end(), tab) != tabs.end());
    341   drag_controller_->Init(this, tab, tabs, gfx::Point(x, y),
    342                          tab->GetMirroredXInView(event.x()));
    343 }
    344 
    345 void BaseTabStrip::ContinueDrag(const views::MouseEvent& event) {
    346   // We can get called even if |MaybeStartDrag| wasn't called in the event of
    347   // a TabStrip animation when the mouse button is down. In this case we should
    348   // _not_ continue the drag because it can lead to weird bugs.
    349   if (drag_controller_.get()) {
    350     bool started_drag = drag_controller_->started_drag();
    351     drag_controller_->Drag();
    352     if (drag_controller_->started_drag() && !started_drag) {
    353       // The drag just started. Redirect mouse events to us to that the tab that
    354       // originated the drag can be safely deleted.
    355       GetRootView()->SetMouseHandler(this);
    356     }
    357   }
    358 }
    359 
    360 bool BaseTabStrip::EndDrag(bool canceled) {
    361   if (!drag_controller_.get())
    362     return false;
    363   bool started_drag = drag_controller_->started_drag();
    364   drag_controller_->EndDrag(canceled);
    365   return started_drag;
    366 }
    367 
    368 BaseTab* BaseTabStrip::GetTabAt(BaseTab* tab,
    369                                 const gfx::Point& tab_in_tab_coordinates) {
    370   gfx::Point local_point = tab_in_tab_coordinates;
    371   ConvertPointToView(tab, this, &local_point);
    372   return GetTabAtLocal(local_point);
    373 }
    374 
    375 void BaseTabStrip::Layout() {
    376   // Only do a layout if our size changed.
    377   if (last_layout_size_ == size())
    378     return;
    379   DoLayout();
    380 }
    381 
    382 bool BaseTabStrip::OnMouseDragged(const views::MouseEvent&  event) {
    383   if (drag_controller_.get())
    384     drag_controller_->Drag();
    385   return true;
    386 }
    387 
    388 void BaseTabStrip::OnMouseReleased(const views::MouseEvent& event) {
    389   EndDrag(false);
    390 }
    391 
    392 void BaseTabStrip::OnMouseCaptureLost() {
    393   EndDrag(true);
    394 }
    395 
    396 void BaseTabStrip::StartMoveTabAnimation() {
    397   PrepareForAnimation();
    398   GenerateIdealBounds();
    399   AnimateToIdealBounds();
    400 }
    401 
    402 void BaseTabStrip::StartRemoveTabAnimation(int model_index) {
    403   PrepareForAnimation();
    404 
    405   // Mark the tab as closing.
    406   BaseTab* tab = GetBaseTabAtModelIndex(model_index);
    407   tab->set_closing(true);
    408 
    409   // Start an animation for the tabs.
    410   GenerateIdealBounds();
    411   AnimateToIdealBounds();
    412 
    413   // Animate the tab being closed to 0x0.
    414   gfx::Rect tab_bounds = tab->bounds();
    415   if (type() == HORIZONTAL_TAB_STRIP)
    416     tab_bounds.set_width(0);
    417   else
    418     tab_bounds.set_height(0);
    419   bounds_animator_.AnimateViewTo(tab, tab_bounds);
    420 
    421   // Register delegate to do cleanup when done, BoundsAnimator takes
    422   // ownership of RemoveTabDelegate.
    423   bounds_animator_.SetAnimationDelegate(tab, new RemoveTabDelegate(this, tab),
    424                                         true);
    425 }
    426 
    427 void BaseTabStrip::StartMiniTabAnimation() {
    428   PrepareForAnimation();
    429 
    430   GenerateIdealBounds();
    431   AnimateToIdealBounds();
    432 }
    433 
    434 bool BaseTabStrip::ShouldHighlightCloseButtonAfterRemove() {
    435   return true;
    436 }
    437 
    438 void BaseTabStrip::RemoveAndDeleteTab(BaseTab* tab) {
    439   int tab_data_index = TabIndexOfTab(tab);
    440 
    441   DCHECK(tab_data_index != -1);
    442 
    443   // Remove the Tab from the TabStrip's list...
    444   tab_data_.erase(tab_data_.begin() + tab_data_index);
    445 
    446   delete tab;
    447 }
    448 
    449 int BaseTabStrip::TabIndexOfTab(BaseTab* tab) const {
    450   for (int i = 0; i < tab_count(); ++i) {
    451     if (base_tab_at_tab_index(i) == tab)
    452       return i;
    453   }
    454   return -1;
    455 }
    456 
    457 void BaseTabStrip::StopAnimating(bool layout) {
    458   if (!IsAnimating())
    459     return;
    460 
    461   bounds_animator().Cancel();
    462 
    463   if (layout)
    464     DoLayout();
    465 }
    466 
    467 void BaseTabStrip::DestroyDragController() {
    468   if (IsDragSessionActive())
    469     drag_controller_.reset(NULL);
    470 }
    471 
    472 void BaseTabStrip::StartedDraggingTabs(const std::vector<BaseTab*>& tabs) {
    473   PrepareForAnimation();
    474 
    475   // Reset dragging state of existing tabs.
    476   for (int i = 0; i < tab_count(); ++i)
    477     base_tab_at_tab_index(i)->set_dragging(false);
    478 
    479   for (size_t i = 0; i < tabs.size(); ++i) {
    480     tabs[i]->set_dragging(true);
    481     bounds_animator_.StopAnimatingView(tabs[i]);
    482   }
    483 
    484   // Move the dragged tabs to their ideal bounds.
    485   GenerateIdealBounds();
    486 
    487   // Sets the bounds of the dragged tabs.
    488   for (size_t i = 0; i < tabs.size(); ++i) {
    489     int tab_data_index = TabIndexOfTab(tabs[i]);
    490     DCHECK(tab_data_index != -1);
    491     tabs[i]->SetBoundsRect(ideal_bounds(tab_data_index));
    492   }
    493   SchedulePaint();
    494 }
    495 
    496 void BaseTabStrip::StoppedDraggingTabs(const std::vector<BaseTab*>& tabs) {
    497   bool is_first_tab = true;
    498   for (size_t i = 0; i < tabs.size(); ++i)
    499     StoppedDraggingTab(tabs[i], &is_first_tab);
    500 }
    501 
    502 void BaseTabStrip::PrepareForAnimation() {
    503   if (!IsDragSessionActive() && !DraggedTabController::IsAttachedTo(this)) {
    504     for (int i = 0; i < tab_count(); ++i)
    505       base_tab_at_tab_index(i)->set_dragging(false);
    506   }
    507 }
    508 
    509 ui::AnimationDelegate* BaseTabStrip::CreateRemoveTabDelegate(BaseTab* tab) {
    510   return new RemoveTabDelegate(this, tab);
    511 }
    512 
    513 void BaseTabStrip::DoLayout() {
    514   last_layout_size_ = size();
    515 
    516   StopAnimating(false);
    517 
    518   GenerateIdealBounds();
    519 
    520   for (int i = 0; i < tab_count(); ++i)
    521     tab_data_[i].tab->SetBoundsRect(tab_data_[i].ideal_bounds);
    522 
    523   SchedulePaint();
    524 }
    525 
    526 bool BaseTabStrip::IsAnimating() const {
    527   return bounds_animator_.IsAnimating();
    528 }
    529 
    530 BaseTab* BaseTabStrip::GetTabAtLocal(const gfx::Point& local_point) {
    531   views::View* view = GetEventHandlerForPoint(local_point);
    532   if (!view)
    533     return NULL;  // No tab contains the point.
    534 
    535   // Walk up the view hierarchy until we find a tab, or the TabStrip.
    536   while (view && view != this && view->GetID() != VIEW_ID_TAB)
    537     view = view->parent();
    538 
    539   return view && view->GetID() == VIEW_ID_TAB ?
    540       static_cast<BaseTab*>(view) : NULL;
    541 }
    542 
    543 void BaseTabStrip::StoppedDraggingTab(BaseTab* tab, bool* is_first_tab) {
    544   int tab_data_index = TabIndexOfTab(tab);
    545   if (tab_data_index == -1) {
    546     // The tab was removed before the drag completed. Don't do anything.
    547     return;
    548   }
    549 
    550   if (*is_first_tab) {
    551     *is_first_tab = false;
    552     PrepareForAnimation();
    553 
    554     // Animate the view back to its correct position.
    555     GenerateIdealBounds();
    556     AnimateToIdealBounds();
    557   }
    558   bounds_animator_.AnimateViewTo(tab, ideal_bounds(TabIndexOfTab(tab)));
    559   // Install a delegate to reset the dragging state when done. We have to leave
    560   // dragging true for the tab otherwise it'll draw beneath the new tab button.
    561   bounds_animator_.SetAnimationDelegate(
    562       tab, new ResetDraggingStateDelegate(tab), true);
    563 }
    564