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.
5 #ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
6 #define CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_
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/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"
27 class ListSelectionModel
;
34 struct TabRendererData
;
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).
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.
49 class TabDragController
: public content::WebContentsDelegate
,
50 public content::NotificationObserver
,
51 public base::MessageLoopForUI::Observer
,
52 public views::WidgetObserver
,
53 public TabStripModelObserver
{
60 // What should happen as the mouse is dragged within the tabstrip.
62 // Only the set of visible tabs should change. This is only applicable when
63 // using touch layout.
66 // Typical behavior where tabs are dragged around.
70 // Indicates the event source that initiated the drag.
76 // Amount above or below the tabstrip the user has to drag before detaching.
77 static const int kTouchVerticalDetachMagnetism
;
78 static const int kVerticalDetachMagnetism
;
81 virtual ~TabDragController();
83 // Initializes TabDragController to drag the tabs in |tabs| originating from
84 // |source_tabstrip|. |source_tab| is the tab that initiated the drag and is
85 // contained in |tabs|. |mouse_offset| is the distance of the mouse pointer
86 // from the origin of the first tab in |tabs| and |source_tab_offset| the
87 // offset from |source_tab|. |source_tab_offset| is the horizontal offset of
88 // |mouse_offset| relative to |source_tab|. |initial_selection_model| is the
89 // selection model before the drag started and is only non-empty if
90 // |source_tab| was not initially selected.
91 void Init(TabStrip
* source_tabstrip
,
93 const std::vector
<Tab
*>& tabs
,
94 const gfx::Point
& mouse_offset
,
95 int source_tab_offset
,
96 const ui::ListSelectionModel
& initial_selection_model
,
97 DetachBehavior detach_behavior
,
98 MoveBehavior move_behavior
,
99 EventSource event_source
);
101 // Returns true if there is a drag underway and the drag is attached to
103 // NOTE: this returns false if the TabDragController is in the process of
104 // finishing the drag.
105 static bool IsAttachedTo(const TabStrip
* tab_strip
);
107 // Returns true if there is a drag underway.
108 static bool IsActive();
110 // Sets the move behavior. Has no effect if started_drag() is true.
111 void SetMoveBehavior(MoveBehavior behavior
);
112 MoveBehavior
move_behavior() const { return move_behavior_
; }
114 EventSource
event_source() const { return event_source_
; }
116 // See description above fields for details on these.
117 bool active() const { return active_
; }
118 const TabStrip
* attached_tabstrip() const { return attached_tabstrip_
; }
120 // Returns true if a drag started.
121 bool started_drag() const { return started_drag_
; }
123 // Returns true if mutating the TabStripModel.
124 bool is_mutating() const { return is_mutating_
; }
126 // Returns true if we've detached from a tabstrip and are running a nested
127 // move message loop.
128 bool is_dragging_window() const { return is_dragging_window_
; }
130 // Invoked to drag to the new location, in screen coordinates.
131 void Drag(const gfx::Point
& point_in_screen
);
133 // Complete the current drag session.
134 void EndDrag(EndDragReason reason
);
138 friend class DockDisplayer
;
140 typedef std::set
<gfx::NativeView
> DockWindows
;
142 // Used to indicate the direction the mouse has moved when attached.
143 static const int kMovedMouseLeft
= 1 << 0;
144 static const int kMovedMouseRight
= 1 << 1;
146 // Enumeration of the ways a drag session can end.
148 // Drag session exited normally: the user released the mouse.
151 // The drag session was canceled (alt-tab during drag, escape ...)
154 // The tab (NavigationController) was destroyed during the drag.
158 // Whether Detach() should release capture or not.
159 enum ReleaseCapture
{
161 DONT_RELEASE_CAPTURE
,
164 // Specifies what should happen when RunMoveLoop completes.
165 enum EndRunLoopBehavior
{
166 // Indicates the drag should end.
167 END_RUN_LOOP_STOP_DRAGGING
,
169 // Indicates the drag should continue.
170 END_RUN_LOOP_CONTINUE_DRAGGING
173 // Enumeration of the possible positions the detached tab may detach from.
174 enum DetachPosition
{
177 DETACH_ABOVE_OR_BELOW
180 // Indicates what should happen after invoking DragBrowserToNewTabStrip().
181 enum DragBrowserResultType
{
182 // The caller should return immediately. This return value is used if a
183 // nested message loop was created or we're in a nested message loop and
185 DRAG_BROWSER_RESULT_STOP
,
187 // The caller should continue.
188 DRAG_BROWSER_RESULT_CONTINUE
,
191 // Stores the date associated with a single tab that is being dragged.
196 // The WebContents being dragged.
197 content::WebContents
* contents
;
199 // content::WebContentsDelegate for |contents| before it was detached from
200 // the browser window. We store this so that we can forward certain delegate
201 // notifications back to it if we can't handle them locally.
202 content::WebContentsDelegate
* original_delegate
;
204 // This is the index of the tab in |source_tabstrip_| when the drag
205 // began. This is used to restore the previous state if the drag is aborted.
206 int source_model_index
;
208 // If attached this is the tab in |attached_tabstrip_|.
211 // Is the tab pinned?
215 typedef std::vector
<TabDragData
> DragData
;
217 // Sets |drag_data| from |tab|. This also registers for necessary
218 // notifications and resets the delegate of the WebContents.
219 void InitTabDragData(Tab
* tab
, TabDragData
* drag_data
);
221 // Overridden from content::WebContentsDelegate:
222 virtual content::WebContents
* OpenURLFromTab(
223 content::WebContents
* source
,
224 const content::OpenURLParams
& params
) OVERRIDE
;
225 virtual void NavigationStateChanged(const content::WebContents
* source
,
226 unsigned changed_flags
) OVERRIDE
;
227 virtual void AddNewContents(content::WebContents
* source
,
228 content::WebContents
* new_contents
,
229 WindowOpenDisposition disposition
,
230 const gfx::Rect
& initial_pos
,
232 bool* was_blocked
) OVERRIDE
;
233 virtual bool ShouldSuppressDialogs() OVERRIDE
;
234 virtual content::JavaScriptDialogManager
*
235 GetJavaScriptDialogManager() OVERRIDE
;
236 virtual void RequestMediaAccessPermission(
237 content::WebContents
* web_contents
,
238 const content::MediaStreamRequest
& request
,
239 const content::MediaResponseCallback
& callback
) OVERRIDE
;
241 // Overridden from content::NotificationObserver:
242 virtual void Observe(int type
,
243 const content::NotificationSource
& source
,
244 const content::NotificationDetails
& details
) OVERRIDE
;
246 // Overridden from MessageLoop::Observer:
247 virtual base::EventStatus
WillProcessEvent(
248 const base::NativeEvent
& event
) OVERRIDE
;
249 virtual void DidProcessEvent(const base::NativeEvent
& event
) OVERRIDE
;
251 // Overriden from views::WidgetObserver:
252 virtual void OnWidgetBoundsChanged(views::Widget
* widget
,
253 const gfx::Rect
& new_bounds
) OVERRIDE
;
255 // Overriden from TabStripModelObserver:
256 virtual void TabStripEmpty() OVERRIDE
;
258 // Initialize the offset used to calculate the position to create windows
259 // in |GetWindowCreatePoint|. This should only be invoked from |Init|.
260 void InitWindowCreatePoint();
262 // Returns the point where a detached window should be created given the
263 // current mouse position |origin|.
264 gfx::Point
GetWindowCreatePoint(const gfx::Point
& origin
) const;
266 void UpdateDockInfo(const gfx::Point
& point_in_screen
);
268 // Saves focus in the window that the drag initiated from. Focus will be
269 // restored appropriately if the drag ends within this same window.
272 // Restore focus to the View that had focus before the drag was started, if
273 // the drag ends within the same Window as it began.
276 // Tests whether |point_in_screen| is past a minimum elasticity threshold
277 // required to start a drag.
278 bool CanStartDrag(const gfx::Point
& point_in_screen
) const;
280 // Invoked once a drag has started to determine the appropriate tabstrip to
281 // drag to (which may be the currently attached one).
282 void ContinueDragging(const gfx::Point
& point_in_screen
);
284 // Transitions dragging from |attached_tabstrip_| to |target_tabstrip|.
285 // |target_tabstrip| is NULL if the mouse is not over a valid tab strip. See
286 // DragBrowserResultType for details of the return type.
287 DragBrowserResultType
DragBrowserToNewTabStrip(
288 TabStrip
* target_tabstrip
,
289 const gfx::Point
& point_in_screen
);
291 // Handles dragging for a touch tabstrip when the tabs are stacked. Doesn't
292 // actually reorder the tabs in anyway, just changes what's visible.
293 void DragActiveTabStacked(const gfx::Point
& point_in_screen
);
295 // Moves the active tab to the next/previous tab. Used when the next/previous
297 void MoveAttachedToNextStackedIndex(const gfx::Point
& point_in_screen
);
298 void MoveAttachedToPreviousStackedIndex(const gfx::Point
& point_in_screen
);
300 // Handles dragging tabs while the tabs are attached.
301 void MoveAttached(const gfx::Point
& point_in_screen
);
303 // If necessary starts the |move_stacked_timer_|. The timer is started if
304 // close enough to an edge with stacked tabs.
305 void StartMoveStackedTimerIfNecessary(
306 const gfx::Point
& point_in_screen
,
309 // Returns the TabStrip for the specified window, or NULL if one doesn't exist
310 // or isn't compatible.
311 TabStrip
* GetTabStripForWindow(gfx::NativeWindow window
);
313 // Returns the compatible TabStrip to drag to at the specified point (screen
314 // coordinates), or NULL if there is none.
315 TabStrip
* GetTargetTabStripForPoint(const gfx::Point
& point_in_screen
);
317 // Returns true if |tabstrip| contains the specified point in screen
319 bool DoesTabStripContain(TabStrip
* tabstrip
,
320 const gfx::Point
& point_in_screen
) const;
322 // Returns the DetachPosition given the specified location in screen
324 DetachPosition
GetDetachPosition(const gfx::Point
& point_in_screen
);
326 DockInfo
GetDockInfoAtPoint(const gfx::Point
& point_in_screen
);
328 // Attach the dragged Tab to the specified TabStrip.
329 void Attach(TabStrip
* attached_tabstrip
, const gfx::Point
& point_in_screen
);
331 // Detach the dragged Tab from the current TabStrip.
332 void Detach(ReleaseCapture release_capture
);
334 // Detaches the tabs being dragged, creates a new Browser to contain them and
335 // runs a nested move loop.
336 void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point
& point_in_screen
);
338 // Runs a nested message loop that handles moving the current
339 // Browser. |drag_offset| is the offset from the window origin and is used in
340 // calculating the location of the window offset from the cursor while
342 void RunMoveLoop(const gfx::Vector2d
& drag_offset
);
344 // Determines the index to insert tabs at. |dragged_bounds| is the bounds of
345 // the tabs being dragged, |start| the index of the tab to start looking from
346 // and |delta| the amount to increment (1 or -1).
347 int GetInsertionIndexFrom(const gfx::Rect
& dragged_bounds
,
351 // Returns the index where the dragged WebContents should be inserted into
352 // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in
353 // coordinates relative to |attached_tabstrip_| and has had the mirroring
354 // transformation applied.
355 // NOTE: this is invoked from Attach() before the tabs have been inserted.
356 int GetInsertionIndexForDraggedBounds(const gfx::Rect
& dragged_bounds
) const;
358 // Returns true if |dragged_bounds| is close enough to the next stacked tab
359 // so that the active tab should be dragged there.
360 bool ShouldDragToNextStackedTab(const gfx::Rect
& dragged_bounds
,
363 // Returns true if |dragged_bounds| is close enough to the previous stacked
364 // tab so that the active tab should be dragged there.
365 bool ShouldDragToPreviousStackedTab(const gfx::Rect
& dragged_bounds
,
368 // Used by GetInsertionIndexForDraggedBounds() when the tabstrip is stacked.
369 int GetInsertionIndexForDraggedBoundsStacked(
370 const gfx::Rect
& dragged_bounds
) const;
372 // Retrieve the bounds of the DraggedTabView relative to the attached
373 // TabStrip. |tab_strip_point| is in the attached TabStrip's coordinate
375 gfx::Rect
GetDraggedViewTabStripBounds(const gfx::Point
& tab_strip_point
);
377 // Get the position of the dragged tab view relative to the attached tab
378 // strip with the mirroring transform applied.
379 gfx::Point
GetAttachedDragPoint(const gfx::Point
& point_in_screen
);
381 // Finds the Tabs within the specified TabStrip that corresponds to the
382 // WebContents of the dragged tabs. Returns an empty vector if not attached.
383 std::vector
<Tab
*> GetTabsMatchingDraggedContents(TabStrip
* tabstrip
);
385 // Returns the bounds for the tabs based on the attached tab strip.
386 std::vector
<gfx::Rect
> CalculateBoundsForDraggedTabs();
388 // Does the work for EndDrag(). If we actually started a drag and |how_end| is
389 // not TAB_DESTROYED then one of EndDrag() or RevertDrag() is invoked.
390 void EndDragImpl(EndDragType how_end
);
392 // Reverts a cancelled drag operation.
395 // Reverts the tab at |drag_index| in |drag_data_|.
396 void RevertDragAt(size_t drag_index
);
398 // Selects the dragged tabs in |model|. Does nothing if there are no longer
399 // any dragged contents (as happens when a WebContents is deleted out from
401 void ResetSelection(TabStripModel
* model
);
403 // Restores |initial_selection_model_| to the |source_tabstrip_|.
404 void RestoreInitialSelection();
406 // Finishes a succesful drag operation.
409 // Maximizes the attached window.
410 void MaximizeAttachedWindow();
412 // Resets the delegates of the WebContents.
413 void ResetDelegates();
415 // Returns the bounds (in screen coordinates) of the specified View.
416 gfx::Rect
GetViewScreenBounds(views::View
* tabstrip
) const;
418 // Hides the frame for the window that contains the TabStrip the current
419 // drag session was initiated from.
422 // Closes a hidden frame at the end of a drag session.
423 void CleanUpHiddenFrame();
425 void DockDisplayerDestroyed(DockDisplayer
* controller
);
427 void BringWindowUnderPointToFront(const gfx::Point
& point_in_screen
);
429 // Convenience for getting the TabDragData corresponding to the tab the user
431 TabDragData
* source_tab_drag_data() {
432 return &(drag_data_
[source_tab_index_
]);
435 // Convenience for |source_tab_drag_data()->contents|.
436 content::WebContents
* source_dragged_contents() {
437 return source_tab_drag_data()->contents
;
440 // Returns the Widget of the currently attached TabStrip's BrowserView.
441 views::Widget
* GetAttachedBrowserWidget();
443 // Returns true if the tabs were originality one after the other in
444 // |source_tabstrip_|.
445 bool AreTabsConsecutive();
447 // Calculates and returns new bounds for the dragged browser window.
448 // Takes into consideration current and restore bounds of |source| tab strip
449 // preventing the dragged size from being too small. Positions the new bounds
450 // such that the tab that was dragged remains under the |point_in_screen|.
451 // Offsets |drag_bounds| if necessary when dragging to the right from the
453 gfx::Rect
CalculateDraggedBrowserBounds(TabStrip
* source
,
454 const gfx::Point
& point_in_screen
,
455 std::vector
<gfx::Rect
>* drag_bounds
);
457 // Calculates scaled |drag_bounds| for dragged tabs and sets the tabs bounds.
458 // Layout of the tabstrip is performed and a new tabstrip width calculated.
459 // When |last_tabstrip_width| is larger than the new tabstrip width the tabs
460 // in attached tabstrip are scaled and the attached browser is positioned such
461 // that the tab that was dragged remains under the |point_in_screen|.
462 void AdjustBrowserAndTabBoundsForDrag(int last_tabstrip_width
,
463 const gfx::Point
& point_in_screen
,
464 std::vector
<gfx::Rect
>* drag_bounds
);
466 // Creates and returns a new Browser to handle the drag.
467 Browser
* CreateBrowserForDrag(TabStrip
* source
,
468 const gfx::Point
& point_in_screen
,
469 gfx::Vector2d
* drag_offset
,
470 std::vector
<gfx::Rect
>* drag_bounds
);
472 // Returns the TabStripModel for the specified tabstrip.
473 TabStripModel
* GetModel(TabStrip
* tabstrip
) const;
475 // Returns the location of the cursor. This is either the location of the
476 // mouse or the location of the current touch point.
477 gfx::Point
GetCursorScreenPoint();
479 // Returns the offset from the top left corner of the window to
480 // |point_in_screen|.
481 gfx::Vector2d
GetWindowOffset(const gfx::Point
& point_in_screen
);
483 // Returns true if moving the mouse only changes the visible tabs.
484 bool move_only() const {
485 return (move_behavior_
== MOVE_VISIBILE_TABS
) != 0;
488 // If true detaching creates a new browser and enters a nested message loop.
489 bool detach_into_browser_
;
491 // Handles registering for notifications.
492 content::NotificationRegistrar registrar_
;
494 EventSource event_source_
;
496 // The TabStrip the drag originated from.
497 TabStrip
* source_tabstrip_
;
499 // The TabStrip the dragged Tab is currently attached to, or NULL if the
500 // dragged Tab is detached.
501 TabStrip
* attached_tabstrip_
;
503 // The screen that this drag is associated with. Cached, because other UI
504 // elements are NULLd at various points during the lifetime of this object.
505 gfx::Screen
* screen_
;
507 // The desktop type that this drag is associated with. Cached, because other
508 // UI elements are NULLd at various points during the lifetime of this
510 chrome::HostDesktopType host_desktop_type_
;
512 // The position of the mouse (in screen coordinates) at the start of the drag
513 // operation. This is used to calculate minimum elasticity before a
514 // DraggedTabView is constructed.
515 gfx::Point start_point_in_screen_
;
517 // This is the offset of the mouse from the top left of the first Tab where
518 // dragging began. This is used to ensure that the dragged view is always
519 // positioned at the correct location during the drag, and to ensure that the
520 // detached window is created at the right location.
521 gfx::Point mouse_offset_
;
523 // Ratio of the x-coordinate of the |source_tab_offset| to the width of the
525 float offset_to_width_ratio_
;
527 // A hint to use when positioning new windows created by detaching Tabs. This
528 // is the distance of the mouse from the top left of the dragged tab as if it
529 // were the distance of the mouse from the top left of the first tab in the
530 // attached TabStrip from the top left of the window.
531 gfx::Point window_create_point_
;
533 // Location of the first tab in the source tabstrip in screen coordinates.
534 // This is used to calculate |window_create_point_|.
535 gfx::Point first_source_tab_point_
;
537 // The bounds of the browser window before the last Tab was detached. When
538 // the last Tab is detached, rather than destroying the frame (which would
539 // abort the drag session), the frame is moved off-screen. If the drag is
540 // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is
541 // attached to the hidden frame and the frame moved back to these bounds.
542 gfx::Rect restore_bounds_
;
544 // Storage ID in ViewStorage where the last view that had focus in the window
545 // containing |source_tab_| is saved. This is saved so that focus can be
546 // restored properly when a drag begins and ends within this same window.
547 const int old_focused_view_id_
;
549 // The horizontal position of the mouse cursor in screen coordinates at the
550 // time of the last re-order event.
551 int last_move_screen_loc_
;
555 DockWindows dock_windows_
;
557 std::vector
<DockDisplayer
*> dock_controllers_
;
559 // Timer used to bring the window under the cursor to front. If the user
560 // stops moving the mouse for a brief time over a browser window, it is
562 base::OneShotTimer
<TabDragController
> bring_to_front_timer_
;
564 // Timer used to move the stacked tabs. See comment aboue
565 // StartMoveStackedTimerIfNecessary().
566 base::OneShotTimer
<TabDragController
> move_stacked_timer_
;
568 // Did the mouse move enough that we started a drag?
571 // Is the drag active?
576 // Index of the source tab in |drag_data_|.
577 size_t source_tab_index_
;
579 // True until MoveAttached() is first invoked.
582 // The selection model before the drag started. See comment above Init() for
584 ui::ListSelectionModel initial_selection_model_
;
586 // The selection model of |attached_tabstrip_| before the tabs were attached.
587 ui::ListSelectionModel selection_model_before_attach_
;
589 // Initial x-coordinates of the tabs when the drag started. Only used for
591 std::vector
<int> initial_tab_positions_
;
593 DetachBehavior detach_behavior_
;
594 MoveBehavior move_behavior_
;
596 // Updated as the mouse is moved when attached. Indicates whether the mouse
597 // has ever moved to the left or right. If the tabs are ever detached this
598 // is set to kMovedMouseRight | kMovedMouseLeft.
599 int mouse_move_direction_
;
601 // Last location used in screen coordinates.
602 gfx::Point last_point_in_screen_
;
604 // The following are needed when detaching into a browser
605 // (|detach_into_browser_| is true).
607 // See description above getter.
608 bool is_dragging_window_
;
610 // True if |attached_tabstrip_| is in a browser specifically created for
612 bool is_dragging_new_browser_
;
614 // True if |source_tabstrip_| was maximized before the drag.
615 bool was_source_maximized_
;
617 // True if |source_tabstrip_| was in immersive fullscreen before the drag.
618 bool was_source_fullscreen_
;
620 // True if the initial drag resulted in restoring the window (because it was
622 bool did_restore_window_
;
624 EndRunLoopBehavior end_run_loop_behavior_
;
626 // If true, we're waiting for a move loop to complete.
627 bool waiting_for_run_loop_to_exit_
;
629 // The TabStrip to attach to after the move loop completes.
630 TabStrip
* tab_strip_to_attach_to_after_exit_
;
632 // Non-null for the duration of RunMoveLoop.
633 views::Widget
* move_loop_widget_
;
635 // See description above getter.
638 // |attach_x_| and |attach_index_| are set to the x-coordinate of the mouse
639 // (in terms of the tabstrip) and the insertion index at the time tabs are
640 // dragged into a new browser (attached). They are used to ensure we don't
641 // shift the tabs around in the wrong direction. The two are only valid if
642 // |attach_index_| is not -1.
643 // See comment around use for more details.
647 base::WeakPtrFactory
<TabDragController
> weak_factory_
;
649 DISALLOW_COPY_AND_ASSIGN(TabDragController
);
652 #endif // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_