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