tab_drag_controller.h revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
6#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
7
8#include <vector>
9
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop.h"
12#include "base/timer/timer.h"
13#include "chrome/browser/ui/tabs/dock_info.h"
14#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
15#include "chrome/browser/ui/views/tabs/tab_strip_types.h"
16#include "content/public/browser/notification_observer.h"
17#include "content/public/browser/notification_registrar.h"
18#include "content/public/browser/web_contents_delegate.h"
19#include "ui/base/models/list_selection_model.h"
20#include "ui/gfx/rect.h"
21#include "ui/views/widget/widget_observer.h"
22
23namespace gfx {
24class Screen;
25}
26namespace ui {
27class ListSelectionModel;
28}
29namespace views {
30class View;
31}
32class Browser;
33class DraggedTabView;
34class Tab;
35struct TabRendererData;
36class TabStrip;
37class TabStripModel;
38
39// TabDragController is responsible for managing the tab dragging session. When
40// the user presses the mouse on a tab a new TabDragController is created and
41// Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough
42// TabDragController starts a drag session. The drag session is completed when
43// EndDrag() is invoked (or the TabDragController is destroyed).
44//
45// While dragging within a tab strip TabDragController sets the bounds of the
46// tabs (this is referred to as attached). When the user drags far enough such
47// that the tabs should be moved out of the tab strip two possible things
48// can happen (this state is referred to as detached):
49// . If |detach_into_browser_| is true then a new Browser is created and
50//   RunMoveLoop() is invoked on the Widget to drag the browser around. This is
51//   the default on chromeos and can be enabled on windows with a flag.
52// . If |detach_into_browser_| is false a small representation of the active tab
53//   is created and that is dragged around. This mode does not run a nested
54//   message loop.
55class TabDragController : public content::WebContentsDelegate,
56                          public content::NotificationObserver,
57                          public base::MessageLoopForUI::Observer,
58                          public views::WidgetObserver,
59                          public TabStripModelObserver {
60 public:
61  enum DetachBehavior {
62    DETACHABLE,
63    NOT_DETACHABLE
64  };
65
66  // What should happen as the mouse is dragged within the tabstrip.
67  enum MoveBehavior {
68    // Only the set of visible tabs should change. This is only applicable when
69    // using touch layout.
70    MOVE_VISIBILE_TABS,
71
72    // Typical behavior where tabs are dragged around.
73    REORDER
74  };
75
76  // Indicates the event source that initiated the drag.
77  enum EventSource {
78    EVENT_SOURCE_MOUSE,
79    EVENT_SOURCE_TOUCH,
80  };
81
82  // Amount above or below the tabstrip the user has to drag before detaching.
83  static const int kTouchVerticalDetachMagnetism;
84  static const int kVerticalDetachMagnetism;
85
86  TabDragController();
87  virtual ~TabDragController();
88
89  // Initializes TabDragController to drag the tabs in |tabs| originating
90  // from |source_tabstrip|. |source_tab| is the tab that initiated the drag and
91  // is contained in |tabs|.  |mouse_offset| is the distance of the mouse
92  // pointer from the origin of the first tab in |tabs| and |source_tab_offset|
93  // the offset from |source_tab|. |source_tab_offset| is the horizontal distant
94  // for a horizontal tab strip, and the vertical distance for a vertical tab
95  // strip. |initial_selection_model| is the selection model before the drag
96  // started and is only non-empty if |source_tab| was not initially selected.
97  void Init(TabStrip* source_tabstrip,
98            Tab* source_tab,
99            const std::vector<Tab*>& tabs,
100            const gfx::Point& mouse_offset,
101            int source_tab_offset,
102            const ui::ListSelectionModel& initial_selection_model,
103            DetachBehavior detach_behavior,
104            MoveBehavior move_behavior,
105            EventSource event_source);
106
107  // Returns true if there is a drag underway and the drag is attached to
108  // |tab_strip|.
109  // NOTE: this returns false if the TabDragController is in the process of
110  // finishing the drag.
111  static bool IsAttachedTo(TabStrip* tab_strip);
112
113  // Returns true if there is a drag underway.
114  static bool IsActive();
115
116  // Sets the move behavior. Has no effect if started_drag() is true.
117  void SetMoveBehavior(MoveBehavior behavior);
118  MoveBehavior move_behavior() const { return move_behavior_; }
119
120  EventSource event_source() const { return event_source_; }
121
122  // See description above fields for details on these.
123  bool active() const { return active_; }
124  const TabStrip* attached_tabstrip() const { return attached_tabstrip_; }
125
126  // Returns true if a drag started.
127  bool started_drag() const { return started_drag_; }
128
129  // Returns true if mutating the TabStripModel.
130  bool is_mutating() const { return is_mutating_; }
131
132  // Returns true if we've detached from a tabstrip and are running a nested
133  // move message loop.
134  bool is_dragging_window() const { return is_dragging_window_; }
135
136  // Invoked to drag to the new location, in screen coordinates.
137  void Drag(const gfx::Point& point_in_screen);
138
139  // Complete the current drag session.
140  void EndDrag(EndDragReason reason);
141
142 private:
143  class DockDisplayer;
144  friend class DockDisplayer;
145
146  typedef std::set<gfx::NativeView> DockWindows;
147
148  // Used to indicate the direction the mouse has moved when attached.
149  static const int kMovedMouseLeft  = 1 << 0;
150  static const int kMovedMouseRight = 1 << 1;
151
152  // Enumeration of the ways a drag session can end.
153  enum EndDragType {
154    // Drag session exited normally: the user released the mouse.
155    NORMAL,
156
157    // The drag session was canceled (alt-tab during drag, escape ...)
158    CANCELED,
159
160    // The tab (NavigationController) was destroyed during the drag.
161    TAB_DESTROYED
162  };
163
164  // Whether Detach() should release capture or not.
165  enum ReleaseCapture {
166    RELEASE_CAPTURE,
167    DONT_RELEASE_CAPTURE,
168  };
169
170  // Specifies what should happen when RunMoveLoop completes.
171  enum EndRunLoopBehavior {
172    // Indicates the drag should end.
173    END_RUN_LOOP_STOP_DRAGGING,
174
175    // Indicates the drag should continue.
176    END_RUN_LOOP_CONTINUE_DRAGGING
177  };
178
179  // Enumeration of the possible positions the detached tab may detach from.
180  enum DetachPosition {
181    DETACH_BEFORE,
182    DETACH_AFTER,
183    DETACH_ABOVE_OR_BELOW
184  };
185
186  // Indicates what should happen after invoking DragBrowserToNewTabStrip().
187  enum DragBrowserResultType {
188    // The caller should return immediately. This return value is used if a
189    // nested message loop was created or we're in a nested message loop and
190    // need to exit it.
191    DRAG_BROWSER_RESULT_STOP,
192
193    // The caller should continue.
194    DRAG_BROWSER_RESULT_CONTINUE,
195  };
196
197  // Stores the date associated with a single tab that is being dragged.
198  struct TabDragData {
199    TabDragData();
200    ~TabDragData();
201
202    // The WebContents being dragged.
203    content::WebContents* contents;
204
205    // The original content::WebContentsDelegate of |contents|, before it was
206    // detached from the browser window. We store this so that we can forward
207    // certain delegate notifications back to it if we can't handle them
208    // locally.
209    content::WebContentsDelegate* original_delegate;
210
211    // This is the index of the tab in |source_tabstrip_| when the drag
212    // began. This is used to restore the previous state if the drag is aborted.
213    int source_model_index;
214
215    // If attached this is the tab in |attached_tabstrip_|.
216    Tab* attached_tab;
217
218    // Is the tab pinned?
219    bool pinned;
220  };
221
222  typedef std::vector<TabDragData> DragData;
223
224  // Sets |drag_data| from |tab|. This also registers for necessary
225  // notifications and resets the delegate of the WebContents.
226  void InitTabDragData(Tab* tab, TabDragData* drag_data);
227
228  // Overridden from content::WebContentsDelegate:
229  virtual content::WebContents* OpenURLFromTab(
230      content::WebContents* source,
231      const content::OpenURLParams& params) OVERRIDE;
232  virtual void NavigationStateChanged(const content::WebContents* source,
233                                      unsigned changed_flags) OVERRIDE;
234  virtual void AddNewContents(content::WebContents* source,
235                              content::WebContents* new_contents,
236                              WindowOpenDisposition disposition,
237                              const gfx::Rect& initial_pos,
238                              bool user_gesture,
239                              bool* was_blocked) OVERRIDE;
240  virtual void LoadingStateChanged(content::WebContents* source) OVERRIDE;
241  virtual bool ShouldSuppressDialogs() OVERRIDE;
242  virtual content::JavaScriptDialogManager*
243      GetJavaScriptDialogManager() OVERRIDE;
244
245  // Overridden from content::NotificationObserver:
246  virtual void Observe(int type,
247                       const content::NotificationSource& source,
248                       const content::NotificationDetails& details) OVERRIDE;
249
250  // Overridden from MessageLoop::Observer:
251  virtual base::EventStatus WillProcessEvent(
252      const base::NativeEvent& event) OVERRIDE;
253  virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE;
254
255  // Overriden from views::WidgetObserver:
256  virtual void OnWidgetBoundsChanged(views::Widget* widget,
257                                     const gfx::Rect& new_bounds) OVERRIDE;
258
259  // Overriden from TabStripModelObserver:
260  virtual void TabStripEmpty() OVERRIDE;
261
262  // Initialize the offset used to calculate the position to create windows
263  // in |GetWindowCreatePoint|. This should only be invoked from |Init|.
264  void InitWindowCreatePoint();
265
266  // Returns the point where a detached window should be created given the
267  // current mouse position |origin|.
268  gfx::Point GetWindowCreatePoint(const gfx::Point& origin) const;
269
270  void UpdateDockInfo(const gfx::Point& point_in_screen);
271
272  // Saves focus in the window that the drag initiated from. Focus will be
273  // restored appropriately if the drag ends within this same window.
274  void SaveFocus();
275
276  // Restore focus to the View that had focus before the drag was started, if
277  // the drag ends within the same Window as it began.
278  void RestoreFocus();
279
280  // Tests whether |point_in_screen| is past a minimum elasticity threshold
281  // required to start a drag.
282  bool CanStartDrag(const gfx::Point& point_in_screen) const;
283
284  // Move the DraggedTabView according to the current mouse screen position,
285  // potentially updating the source and other TabStrips.
286  void ContinueDragging(const gfx::Point& point_in_screen);
287
288  // Transitions dragging from |attached_tabstrip_| to |target_tabstrip|.
289  // |target_tabstrip| is NULL if the mouse is not over a valid tab strip.  See
290  // DragBrowserResultType for details of the return type.
291  DragBrowserResultType DragBrowserToNewTabStrip(
292      TabStrip* target_tabstrip,
293      const gfx::Point& point_in_screen);
294
295  // Handles dragging for a touch tabstrip when the tabs are stacked. Doesn't
296  // actually reorder the tabs in anyway, just changes what's visible.
297  void DragActiveTabStacked(const gfx::Point& point_in_screen);
298
299  // Moves the active tab to the next/previous tab. Used when the next/previous
300  // tab is stacked.
301  void MoveAttachedToNextStackedIndex(const gfx::Point& point_in_screen);
302  void MoveAttachedToPreviousStackedIndex(const gfx::Point& point_in_screen);
303
304  // Handles dragging tabs while the tabs are attached.
305  void MoveAttached(const gfx::Point& point_in_screen);
306
307  // Handles dragging while the tabs are detached.
308  void MoveDetached(const gfx::Point& point_in_screen);
309
310  // If necessary starts the |move_stacked_timer_|. The timer is started if
311  // close enough to an edge with stacked tabs.
312  void StartMoveStackedTimerIfNecessary(
313      const gfx::Point& point_in_screen,
314      int delay_ms);
315
316  // Returns the TabStrip for the specified window, or NULL if one doesn't exist
317  // or isn't compatible.
318  TabStrip* GetTabStripForWindow(gfx::NativeWindow window);
319
320  // Returns the compatible TabStrip to drag to at the specified point (screen
321  // coordinates), or NULL if there is none.
322  TabStrip* GetTargetTabStripForPoint(const gfx::Point& point_in_screen);
323
324  // Returns true if |tabstrip| contains the specified point in screen
325  // coordinates.
326  bool DoesTabStripContain(TabStrip* tabstrip,
327                           const gfx::Point& point_in_screen) const;
328
329  // Returns the DetachPosition given the specified location in screen
330  // coordinates.
331  DetachPosition GetDetachPosition(const gfx::Point& point_in_screen);
332
333  DockInfo GetDockInfoAtPoint(const gfx::Point& point_in_screen);
334
335  // Attach the dragged Tab to the specified TabStrip.
336  void Attach(TabStrip* attached_tabstrip, const gfx::Point& point_in_screen);
337
338  // Detach the dragged Tab from the current TabStrip.
339  void Detach(ReleaseCapture release_capture);
340
341  // Detaches the tabs being dragged, creates a new Browser to contain them and
342  // runs a nested move loop.
343  void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point& point_in_screen);
344
345  // Runs a nested message loop that handles moving the current
346  // Browser. |drag_offset| is the offset from the window origin and is used in
347  // calculating the location of the window offset from the cursor while
348  // dragging.
349  void RunMoveLoop(const gfx::Vector2d& drag_offset);
350
351  // Determines the index to insert tabs at. |dragged_bounds| is the bounds of
352  // the tabs being dragged, |start| the index of the tab to start looking from
353  // and |delta| the amount to increment (1 or -1).
354  int GetInsertionIndexFrom(const gfx::Rect& dragged_bounds,
355                            int start,
356                            int delta) const;
357
358  // Returns the index where the dragged WebContents should be inserted into
359  // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in
360  // coordinates relative to |attached_tabstrip_| and has had the mirroring
361  // transformation applied.
362  // NOTE: this is invoked from |Attach| before the tabs have been inserted.
363  int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const;
364
365  // Returns true if |dragged_bounds| is close enough to the next stacked tab
366  // so that the active tab should be dragged there.
367  bool ShouldDragToNextStackedTab(const gfx::Rect& dragged_bounds,
368                                  int index) const;
369
370  // Returns true if |dragged_bounds| is close enough to the previous stacked
371  // tab so that the active tab should be dragged there.
372  bool ShouldDragToPreviousStackedTab(const gfx::Rect& dragged_bounds,
373                                      int index) const;
374
375  // Used by GetInsertionIndexForDraggedBounds() when the tabstrip is stacked.
376  int GetInsertionIndexForDraggedBoundsStacked(
377      const gfx::Rect& dragged_bounds) const;
378
379  // Retrieve the bounds of the DraggedTabView relative to the attached
380  // TabStrip. |tab_strip_point| is in the attached TabStrip's coordinate
381  // system.
382  gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& tab_strip_point);
383
384  // Get the position of the dragged tab view relative to the attached tab
385  // strip with the mirroring transform applied.
386  gfx::Point GetAttachedDragPoint(const gfx::Point& point_in_screen);
387
388  // Finds the Tabs within the specified TabStrip that corresponds to the
389  // WebContents of the dragged tabs. Returns an empty vector if not attached.
390  std::vector<Tab*> GetTabsMatchingDraggedContents(TabStrip* tabstrip);
391
392  // Returns the bounds for the tabs based on the attached tab strip. The
393  // x-coordinate of each tab is offset by |x_offset|.
394  std::vector<gfx::Rect> CalculateBoundsForDraggedTabs(int x_offset);
395
396  // Does the work for EndDrag. If we actually started a drag and |how_end| is
397  // not TAB_DESTROYED then one of EndDrag or RevertDrag is invoked.
398  void EndDragImpl(EndDragType how_end);
399
400  // Reverts a cancelled drag operation.
401  void RevertDrag();
402
403  // Reverts the tab at |drag_index| in |drag_data_|.
404  void RevertDragAt(size_t drag_index);
405
406  // Selects the dragged tabs in |model|. Does nothing if there are no longer
407  // any dragged contents (as happens when a WebContents is deleted out from
408  // under us).
409  void ResetSelection(TabStripModel* model);
410
411  // Finishes a succesful drag operation.
412  void CompleteDrag();
413
414  // Resets the delegates of the WebContents.
415  void ResetDelegates();
416
417  // Create the DraggedTabView.
418  void CreateDraggedView(const std::vector<TabRendererData>& data,
419                         const std::vector<gfx::Rect>& renderer_bounds);
420
421  // Returns the bounds (in screen coordinates) of the specified View.
422  gfx::Rect GetViewScreenBounds(views::View* tabstrip) const;
423
424  // Hides the frame for the window that contains the TabStrip the current
425  // drag session was initiated from.
426  void HideFrame();
427
428  // Closes a hidden frame at the end of a drag session.
429  void CleanUpHiddenFrame();
430
431  void DockDisplayerDestroyed(DockDisplayer* controller);
432
433  void BringWindowUnderPointToFront(const gfx::Point& point_in_screen);
434
435  // Convenience for getting the TabDragData corresponding to the tab the user
436  // started dragging.
437  TabDragData* source_tab_drag_data() {
438    return &(drag_data_[source_tab_index_]);
439  }
440
441  // Convenience for |source_tab_drag_data()->contents|.
442  content::WebContents* source_dragged_contents() {
443    return source_tab_drag_data()->contents;
444  }
445
446  // Returns the Widget of the currently attached TabStrip's BrowserView.
447  views::Widget* GetAttachedBrowserWidget();
448
449  // Returns true if the tabs were originality one after the other in
450  // |source_tabstrip_|.
451  bool AreTabsConsecutive();
452
453  // Creates and returns a new Browser to handle the drag.
454  Browser* CreateBrowserForDrag(TabStrip* source,
455                                const gfx::Point& point_in_screen,
456                                gfx::Vector2d* drag_offset,
457                                std::vector<gfx::Rect>* drag_bounds);
458
459  // Returns the TabStripModel for the specified tabstrip.
460  TabStripModel* GetModel(TabStrip* tabstrip) const;
461
462  // Returns the location of the cursor. This is either the location of the
463  // mouse or the location of the current touch point.
464  gfx::Point GetCursorScreenPoint();
465
466  // Returns the offset from the top left corner of the window to
467  // |point_in_screen|.
468  gfx::Vector2d GetWindowOffset(const gfx::Point& point_in_screen);
469
470  // Returns true if moving the mouse only changes the visible tabs.
471  bool move_only() const {
472    return (move_behavior_ == MOVE_VISIBILE_TABS) != 0;
473  }
474
475  // If true Detaching creates a new browser and enters a nested message loop.
476  bool detach_into_browser_;
477
478  // Handles registering for notifications.
479  content::NotificationRegistrar registrar_;
480
481  EventSource event_source_;
482
483  // The TabStrip the drag originated from.
484  TabStrip* source_tabstrip_;
485
486  // The TabStrip the dragged Tab is currently attached to, or NULL if the
487  // dragged Tab is detached.
488  TabStrip* attached_tabstrip_;
489
490  // The screen that this drag is associated with. Cached, because other UI
491  // elements are NULLd at various points during the lifetime of this object.
492  gfx::Screen* screen_;
493
494  // The desktop type that this drag is associated with. Cached, because other
495  // UI elements are NULLd at various points during the lifetime of this
496  // object.
497  chrome::HostDesktopType host_desktop_type_;
498
499  // The visual representation of the dragged Tab.
500  scoped_ptr<DraggedTabView> view_;
501
502  // The position of the mouse (in screen coordinates) at the start of the drag
503  // operation. This is used to calculate minimum elasticity before a
504  // DraggedTabView is constructed.
505  gfx::Point start_point_in_screen_;
506
507  // This is the offset of the mouse from the top left of the Tab where
508  // dragging begun. This is used to ensure that the dragged view is always
509  // positioned at the correct location during the drag, and to ensure that the
510  // detached window is created at the right location.
511  gfx::Point mouse_offset_;
512
513  // Ratio of the x-coordinate of the |source_tab_offset| to the width of the
514  // tab. Not used for vertical tabs.
515  float offset_to_width_ratio_;
516
517  // A hint to use when positioning new windows created by detaching Tabs. This
518  // is the distance of the mouse from the top left of the dragged tab as if it
519  // were the distance of the mouse from the top left of the first tab in the
520  // attached TabStrip from the top left of the window.
521  gfx::Point window_create_point_;
522
523  // Location of the first tab in the source tabstrip in screen coordinates.
524  // This is used to calculate window_create_point_.
525  gfx::Point first_source_tab_point_;
526
527  // The bounds of the browser window before the last Tab was detached. When
528  // the last Tab is detached, rather than destroying the frame (which would
529  // abort the drag session), the frame is moved off-screen. If the drag is
530  // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is
531  // attached to the hidden frame and the frame moved back to these bounds.
532  gfx::Rect restore_bounds_;
533
534  // The last view that had focus in the window containing |source_tab_|. This
535  // is saved so that focus can be restored properly when a drag begins and
536  // ends within this same window.
537  views::View* old_focused_view_;
538
539  // The position along the major axis of the mouse cursor in screen coordinates
540  // at the time of the last re-order event.
541  int last_move_screen_loc_;
542
543  DockInfo dock_info_;
544
545  DockWindows dock_windows_;
546
547  std::vector<DockDisplayer*> dock_controllers_;
548
549  // Timer used to bring the window under the cursor to front. If the user
550  // stops moving the mouse for a brief time over a browser window, it is
551  // brought to front.
552  base::OneShotTimer<TabDragController> bring_to_front_timer_;
553
554  // Timer used to move the stacked tabs. See comment aboue
555  // StartMoveStackedTimerIfNecessary().
556  base::OneShotTimer<TabDragController> move_stacked_timer_;
557
558  // Did the mouse move enough that we started a drag?
559  bool started_drag_;
560
561  // Is the drag active?
562  bool active_;
563
564  DragData drag_data_;
565
566  // Index of the source tab in drag_data_.
567  size_t source_tab_index_;
568
569  // True until |MoveAttached| is invoked once.
570  bool initial_move_;
571
572  // The selection model before the drag started. See comment above Init() for
573  // details.
574  ui::ListSelectionModel initial_selection_model_;
575
576  // The selection model of |attached_tabstrip_| before the tabs were attached.
577  ui::ListSelectionModel selection_model_before_attach_;
578
579  // Initial x-coordinates of the tabs when the drag started. Only used for
580  // touch mode.
581  std::vector<int> initial_tab_positions_;
582
583  DetachBehavior detach_behavior_;
584  MoveBehavior move_behavior_;
585
586  // Updated as the mouse is moved when attached. Indicates whether the mouse
587  // has ever moved to the left or right. If the tabs are ever detached this
588  // is set to kMovedMouseRight | kMovedMouseLeft.
589  int mouse_move_direction_;
590
591  // Last location used in screen coordinates.
592  gfx::Point last_point_in_screen_;
593
594  // The following are needed when detaching into a browser
595  // (|detach_into_browser_| is true).
596
597  // See description above getter.
598  bool is_dragging_window_;
599
600  EndRunLoopBehavior end_run_loop_behavior_;
601
602  // If true, we're waiting for a move loop to complete.
603  bool waiting_for_run_loop_to_exit_;
604
605  // The TabStrip to attach to after the move loop completes.
606  TabStrip* tab_strip_to_attach_to_after_exit_;
607
608  // Non-null for the duration of RunMoveLoop.
609  views::Widget* move_loop_widget_;
610
611  // If non-null set to true from destructor.
612  bool* destroyed_;
613
614  // See description above getter.
615  bool is_mutating_;
616
617  DISALLOW_COPY_AND_ASSIGN(TabDragController);
618};
619
620#endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
621