• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
ResetDraggingStateDelegate(BaseTab * tab)25   explicit ResetDraggingStateDelegate(BaseTab* tab) : tab_(tab) {
26   }
27 
AnimationEnded(const ui::Animation * animation)28   virtual void AnimationEnded(const ui::Animation* animation) {
29     tab_->set_dragging(false);
30   }
31 
AnimationCanceled(const ui::Animation * animation)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:
RemoveTabDelegate(BaseTabStrip * tab_strip,BaseTab * tab)49   RemoveTabDelegate(BaseTabStrip* tab_strip, BaseTab* tab)
50       : tabstrip_(tab_strip),
51         tab_(tab) {
52   }
53 
AnimationEnded(const ui::Animation * animation)54   virtual void AnimationEnded(const ui::Animation* animation) {
55     CompleteRemove();
56   }
57 
AnimationCanceled(const ui::Animation * animation)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:
CompleteRemove()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.
HighlightCloseButton()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 
BaseTabStrip(TabStripController * controller,Type type)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 
~BaseTabStrip()125 BaseTabStrip::~BaseTabStrip() {
126 }
127 
AddTabAt(int model_index,const TabRendererData & data)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 
MoveTab(int from_model_index,int to_model_index)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 
SetTabData(int model_index,const TabRendererData & data)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 
GetBaseTabAtModelIndex(int model_index) const170 BaseTab* BaseTabStrip::GetBaseTabAtModelIndex(int model_index) const {
171   return base_tab_at_tab_index(ModelIndexToTabIndex(model_index));
172 }
173 
GetModelIndexOfBaseTab(const BaseTab * tab) const174 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 
GetModelCount() const188 int BaseTabStrip::GetModelCount() const {
189   return controller_->GetCount();
190 }
191 
IsValidModelIndex(int model_index) const192 bool BaseTabStrip::IsValidModelIndex(int model_index) const {
193   return controller_->IsValidIndex(model_index);
194 }
195 
ModelIndexToTabIndex(int model_index) const196 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 
IsDragSessionActive() const208 bool BaseTabStrip::IsDragSessionActive() const {
209   return drag_controller_.get() != NULL;
210 }
211 
IsActiveDropTarget() const212 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 
IsTabStripEditable() const221 bool BaseTabStrip::IsTabStripEditable() const {
222   return !IsDragSessionActive() && !IsActiveDropTarget();
223 }
224 
IsTabStripCloseable() const225 bool BaseTabStrip::IsTabStripCloseable() const {
226   return !IsDragSessionActive();
227 }
228 
UpdateLoadingAnimations()229 void BaseTabStrip::UpdateLoadingAnimations() {
230   controller_->UpdateLoadingAnimations();
231 }
232 
SelectTab(BaseTab * tab)233 void BaseTabStrip::SelectTab(BaseTab* tab) {
234   int model_index = GetModelIndexOfBaseTab(tab);
235   if (IsValidModelIndex(model_index))
236     controller_->SelectTab(model_index);
237 }
238 
ExtendSelectionTo(BaseTab * tab)239 void BaseTabStrip::ExtendSelectionTo(BaseTab* tab) {
240   int model_index = GetModelIndexOfBaseTab(tab);
241   if (IsValidModelIndex(model_index))
242     controller_->ExtendSelectionTo(model_index);
243 }
244 
ToggleSelected(BaseTab * tab)245 void BaseTabStrip::ToggleSelected(BaseTab* tab) {
246   int model_index = GetModelIndexOfBaseTab(tab);
247   if (IsValidModelIndex(model_index))
248     controller_->ToggleSelected(model_index);
249 }
250 
AddSelectionFromAnchorTo(BaseTab * tab)251 void BaseTabStrip::AddSelectionFromAnchorTo(BaseTab* tab) {
252   int model_index = GetModelIndexOfBaseTab(tab);
253   if (IsValidModelIndex(model_index))
254     controller_->AddSelectionFromAnchorTo(model_index);
255 }
256 
CloseTab(BaseTab * tab)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 
ShowContextMenuForTab(BaseTab * tab,const gfx::Point & p)273 void BaseTabStrip::ShowContextMenuForTab(BaseTab* tab, const gfx::Point& p) {
274   controller_->ShowContextMenuForTab(tab, p);
275 }
276 
IsActiveTab(const BaseTab * tab) const277 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 
IsTabSelected(const BaseTab * tab) const283 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 
IsTabPinned(const BaseTab * tab) const289 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 
IsTabCloseable(const BaseTab * tab) const298 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 
MaybeStartDrag(BaseTab * tab,const views::MouseEvent & event)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 
ContinueDrag(const views::MouseEvent & event)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 
EndDrag(bool canceled)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 
GetTabAt(BaseTab * tab,const gfx::Point & tab_in_tab_coordinates)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 
Layout()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 
OnMouseDragged(const views::MouseEvent & event)382 bool BaseTabStrip::OnMouseDragged(const views::MouseEvent&  event) {
383   if (drag_controller_.get())
384     drag_controller_->Drag();
385   return true;
386 }
387 
OnMouseReleased(const views::MouseEvent & event)388 void BaseTabStrip::OnMouseReleased(const views::MouseEvent& event) {
389   EndDrag(false);
390 }
391 
OnMouseCaptureLost()392 void BaseTabStrip::OnMouseCaptureLost() {
393   EndDrag(true);
394 }
395 
StartMoveTabAnimation()396 void BaseTabStrip::StartMoveTabAnimation() {
397   PrepareForAnimation();
398   GenerateIdealBounds();
399   AnimateToIdealBounds();
400 }
401 
StartRemoveTabAnimation(int model_index)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 
StartMiniTabAnimation()427 void BaseTabStrip::StartMiniTabAnimation() {
428   PrepareForAnimation();
429 
430   GenerateIdealBounds();
431   AnimateToIdealBounds();
432 }
433 
ShouldHighlightCloseButtonAfterRemove()434 bool BaseTabStrip::ShouldHighlightCloseButtonAfterRemove() {
435   return true;
436 }
437 
RemoveAndDeleteTab(BaseTab * tab)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 
TabIndexOfTab(BaseTab * tab) const449 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 
StopAnimating(bool layout)457 void BaseTabStrip::StopAnimating(bool layout) {
458   if (!IsAnimating())
459     return;
460 
461   bounds_animator().Cancel();
462 
463   if (layout)
464     DoLayout();
465 }
466 
DestroyDragController()467 void BaseTabStrip::DestroyDragController() {
468   if (IsDragSessionActive())
469     drag_controller_.reset(NULL);
470 }
471 
StartedDraggingTabs(const std::vector<BaseTab * > & tabs)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 
StoppedDraggingTabs(const std::vector<BaseTab * > & tabs)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 
PrepareForAnimation()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 
CreateRemoveTabDelegate(BaseTab * tab)509 ui::AnimationDelegate* BaseTabStrip::CreateRemoveTabDelegate(BaseTab* tab) {
510   return new RemoveTabDelegate(this, tab);
511 }
512 
DoLayout()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 
IsAnimating() const526 bool BaseTabStrip::IsAnimating() const {
527   return bounds_animator_.IsAnimating();
528 }
529 
GetTabAtLocal(const gfx::Point & local_point)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 
StoppedDraggingTab(BaseTab * tab,bool * is_first_tab)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