// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_ #define CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_ #include <vector> #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "base/timer/timer.h" #include "chrome/browser/ui/host_desktop.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/browser/ui/views/tabs/tab_strip_types.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_contents_delegate.h" #include "ui/base/models/list_selection_model.h" #include "ui/gfx/rect.h" #include "ui/views/widget/widget_observer.h" namespace gfx { class Screen; } namespace ui { class ListSelectionModel; } namespace views { class View; } class Browser; class Tab; struct TabRendererData; class TabStrip; class TabStripModel; // TabDragController is responsible for managing the tab dragging session. When // the user presses the mouse on a tab a new TabDragController is created and // Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough // TabDragController starts a drag session. The drag session is completed when // EndDrag() is invoked (or the TabDragController is destroyed). // // While dragging within a tab strip TabDragController sets the bounds of the // tabs (this is referred to as attached). When the user drags far enough such // that the tabs should be moved out of the tab strip a new Browser is created // and RunMoveLoop() is invoked on the Widget to drag the browser around. This // is the default on aura. class TabDragController : public content::WebContentsDelegate, public content::NotificationObserver, public base::MessageLoopForUI::Observer, public views::WidgetObserver, public TabStripModelObserver { public: enum DetachBehavior { DETACHABLE, NOT_DETACHABLE }; // What should happen as the mouse is dragged within the tabstrip. enum MoveBehavior { // Only the set of visible tabs should change. This is only applicable when // using touch layout. MOVE_VISIBILE_TABS, // Typical behavior where tabs are dragged around. REORDER }; // Indicates the event source that initiated the drag. enum EventSource { EVENT_SOURCE_MOUSE, EVENT_SOURCE_TOUCH, }; // Amount above or below the tabstrip the user has to drag before detaching. static const int kTouchVerticalDetachMagnetism; static const int kVerticalDetachMagnetism; TabDragController(); virtual ~TabDragController(); // Initializes TabDragController to drag the tabs in |tabs| originating from // |source_tabstrip|. |source_tab| is the tab that initiated the drag and is // contained in |tabs|. |mouse_offset| is the distance of the mouse pointer // from the origin of the first tab in |tabs| and |source_tab_offset| the // offset from |source_tab|. |source_tab_offset| is the horizontal offset of // |mouse_offset| relative to |source_tab|. |initial_selection_model| is the // selection model before the drag started and is only non-empty if // |source_tab| was not initially selected. void Init(TabStrip* source_tabstrip, Tab* source_tab, const std::vector<Tab*>& tabs, const gfx::Point& mouse_offset, int source_tab_offset, const ui::ListSelectionModel& initial_selection_model, DetachBehavior detach_behavior, MoveBehavior move_behavior, EventSource event_source); // Returns true if there is a drag underway and the drag is attached to // |tab_strip|. // NOTE: this returns false if the TabDragController is in the process of // finishing the drag. static bool IsAttachedTo(const TabStrip* tab_strip); // Returns true if there is a drag underway. static bool IsActive(); // Sets the move behavior. Has no effect if started_drag() is true. void SetMoveBehavior(MoveBehavior behavior); MoveBehavior move_behavior() const { return move_behavior_; } EventSource event_source() const { return event_source_; } // See description above fields for details on these. bool active() const { return active_; } const TabStrip* attached_tabstrip() const { return attached_tabstrip_; } // Returns true if a drag started. bool started_drag() const { return started_drag_; } // Returns true if mutating the TabStripModel. bool is_mutating() const { return is_mutating_; } // Returns true if we've detached from a tabstrip and are running a nested // move message loop. bool is_dragging_window() const { return is_dragging_window_; } // Invoked to drag to the new location, in screen coordinates. void Drag(const gfx::Point& point_in_screen); // Complete the current drag session. void EndDrag(EndDragReason reason); private: // Used to indicate the direction the mouse has moved when attached. static const int kMovedMouseLeft = 1 << 0; static const int kMovedMouseRight = 1 << 1; // Enumeration of the ways a drag session can end. enum EndDragType { // Drag session exited normally: the user released the mouse. NORMAL, // The drag session was canceled (alt-tab during drag, escape ...) CANCELED, // The tab (NavigationController) was destroyed during the drag. TAB_DESTROYED }; // Whether Detach() should release capture or not. enum ReleaseCapture { RELEASE_CAPTURE, DONT_RELEASE_CAPTURE, }; // Specifies what should happen when RunMoveLoop completes. enum EndRunLoopBehavior { // Indicates the drag should end. END_RUN_LOOP_STOP_DRAGGING, // Indicates the drag should continue. END_RUN_LOOP_CONTINUE_DRAGGING }; // Enumeration of the possible positions the detached tab may detach from. enum DetachPosition { DETACH_BEFORE, DETACH_AFTER, DETACH_ABOVE_OR_BELOW }; // Indicates what should happen after invoking DragBrowserToNewTabStrip(). enum DragBrowserResultType { // The caller should return immediately. This return value is used if a // nested message loop was created or we're in a nested message loop and // need to exit it. DRAG_BROWSER_RESULT_STOP, // The caller should continue. DRAG_BROWSER_RESULT_CONTINUE, }; // Stores the date associated with a single tab that is being dragged. struct TabDragData { TabDragData(); ~TabDragData(); // The WebContents being dragged. content::WebContents* contents; // content::WebContentsDelegate for |contents| before it was detached from // the browser window. We store this so that we can forward certain delegate // notifications back to it if we can't handle them locally. content::WebContentsDelegate* original_delegate; // This is the index of the tab in |source_tabstrip_| when the drag // began. This is used to restore the previous state if the drag is aborted. int source_model_index; // If attached this is the tab in |attached_tabstrip_|. Tab* attached_tab; // Is the tab pinned? bool pinned; }; typedef std::vector<TabDragData> DragData; // Sets |drag_data| from |tab|. This also registers for necessary // notifications and resets the delegate of the WebContents. void InitTabDragData(Tab* tab, TabDragData* drag_data); // Overridden from content::WebContentsDelegate: virtual content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) OVERRIDE; virtual void NavigationStateChanged(const content::WebContents* source, unsigned changed_flags) OVERRIDE; virtual void AddNewContents(content::WebContents* source, content::WebContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture, bool* was_blocked) OVERRIDE; virtual bool ShouldSuppressDialogs() OVERRIDE; virtual content::JavaScriptDialogManager* GetJavaScriptDialogManager() OVERRIDE; virtual void RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) OVERRIDE; // Overridden from content::NotificationObserver: virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE; // Overridden from MessageLoop::Observer: virtual base::EventStatus WillProcessEvent( const base::NativeEvent& event) OVERRIDE; virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE; // Overriden from views::WidgetObserver: virtual void OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& new_bounds) OVERRIDE; // Overriden from TabStripModelObserver: virtual void TabStripEmpty() OVERRIDE; // Initialize the offset used to calculate the position to create windows // in |GetWindowCreatePoint|. This should only be invoked from |Init|. void InitWindowCreatePoint(); // Returns the point where a detached window should be created given the // current mouse position |origin|. gfx::Point GetWindowCreatePoint(const gfx::Point& origin) const; void UpdateDockInfo(const gfx::Point& point_in_screen); // Saves focus in the window that the drag initiated from. Focus will be // restored appropriately if the drag ends within this same window. void SaveFocus(); // Restore focus to the View that had focus before the drag was started, if // the drag ends within the same Window as it began. void RestoreFocus(); // Tests whether |point_in_screen| is past a minimum elasticity threshold // required to start a drag. bool CanStartDrag(const gfx::Point& point_in_screen) const; // Invoked once a drag has started to determine the appropriate tabstrip to // drag to (which may be the currently attached one). void ContinueDragging(const gfx::Point& point_in_screen); // Transitions dragging from |attached_tabstrip_| to |target_tabstrip|. // |target_tabstrip| is NULL if the mouse is not over a valid tab strip. See // DragBrowserResultType for details of the return type. DragBrowserResultType DragBrowserToNewTabStrip( TabStrip* target_tabstrip, const gfx::Point& point_in_screen); // Handles dragging for a touch tabstrip when the tabs are stacked. Doesn't // actually reorder the tabs in anyway, just changes what's visible. void DragActiveTabStacked(const gfx::Point& point_in_screen); // Moves the active tab to the next/previous tab. Used when the next/previous // tab is stacked. void MoveAttachedToNextStackedIndex(const gfx::Point& point_in_screen); void MoveAttachedToPreviousStackedIndex(const gfx::Point& point_in_screen); // Handles dragging tabs while the tabs are attached. void MoveAttached(const gfx::Point& point_in_screen); // If necessary starts the |move_stacked_timer_|. The timer is started if // close enough to an edge with stacked tabs. void StartMoveStackedTimerIfNecessary( const gfx::Point& point_in_screen, int delay_ms); // Returns the TabStrip for the specified window, or NULL if one doesn't exist // or isn't compatible. TabStrip* GetTabStripForWindow(gfx::NativeWindow window); // Returns the compatible TabStrip to drag to at the specified point (screen // coordinates), or NULL if there is none. TabStrip* GetTargetTabStripForPoint(const gfx::Point& point_in_screen); // Returns true if |tabstrip| contains the specified point in screen // coordinates. bool DoesTabStripContain(TabStrip* tabstrip, const gfx::Point& point_in_screen) const; // Returns the DetachPosition given the specified location in screen // coordinates. DetachPosition GetDetachPosition(const gfx::Point& point_in_screen); // Attach the dragged Tab to the specified TabStrip. void Attach(TabStrip* attached_tabstrip, const gfx::Point& point_in_screen); // Detach the dragged Tab from the current TabStrip. void Detach(ReleaseCapture release_capture); // Detaches the tabs being dragged, creates a new Browser to contain them and // runs a nested move loop. void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point& point_in_screen); // Runs a nested message loop that handles moving the current // Browser. |drag_offset| is the offset from the window origin and is used in // calculating the location of the window offset from the cursor while // dragging. void RunMoveLoop(const gfx::Vector2d& drag_offset); // Determines the index to insert tabs at. |dragged_bounds| is the bounds of // the tabs being dragged, |start| the index of the tab to start looking from // and |delta| the amount to increment (1 or -1). int GetInsertionIndexFrom(const gfx::Rect& dragged_bounds, int start, int delta) const; // Returns the index where the dragged WebContents should be inserted into // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in // coordinates relative to |attached_tabstrip_| and has had the mirroring // transformation applied. // NOTE: this is invoked from Attach() before the tabs have been inserted. int GetInsertionIndexForDraggedBounds(const gfx::Rect& dragged_bounds) const; // Returns true if |dragged_bounds| is close enough to the next stacked tab // so that the active tab should be dragged there. bool ShouldDragToNextStackedTab(const gfx::Rect& dragged_bounds, int index) const; // Returns true if |dragged_bounds| is close enough to the previous stacked // tab so that the active tab should be dragged there. bool ShouldDragToPreviousStackedTab(const gfx::Rect& dragged_bounds, int index) const; // Used by GetInsertionIndexForDraggedBounds() when the tabstrip is stacked. int GetInsertionIndexForDraggedBoundsStacked( const gfx::Rect& dragged_bounds) const; // Retrieve the bounds of the DraggedTabView relative to the attached // TabStrip. |tab_strip_point| is in the attached TabStrip's coordinate // system. gfx::Rect GetDraggedViewTabStripBounds(const gfx::Point& tab_strip_point); // Get the position of the dragged tab view relative to the attached tab // strip with the mirroring transform applied. gfx::Point GetAttachedDragPoint(const gfx::Point& point_in_screen); // Finds the Tabs within the specified TabStrip that corresponds to the // WebContents of the dragged tabs. Returns an empty vector if not attached. std::vector<Tab*> GetTabsMatchingDraggedContents(TabStrip* tabstrip); // Returns the bounds for the tabs based on the attached tab strip. std::vector<gfx::Rect> CalculateBoundsForDraggedTabs(); // Does the work for EndDrag(). If we actually started a drag and |how_end| is // not TAB_DESTROYED then one of EndDrag() or RevertDrag() is invoked. void EndDragImpl(EndDragType how_end); // Reverts a cancelled drag operation. void RevertDrag(); // Reverts the tab at |drag_index| in |drag_data_|. void RevertDragAt(size_t drag_index); // Selects the dragged tabs in |model|. Does nothing if there are no longer // any dragged contents (as happens when a WebContents is deleted out from // under us). void ResetSelection(TabStripModel* model); // Restores |initial_selection_model_| to the |source_tabstrip_|. void RestoreInitialSelection(); // Finishes a succesful drag operation. void CompleteDrag(); // Maximizes the attached window. void MaximizeAttachedWindow(); // Resets the delegates of the WebContents. void ResetDelegates(); // Returns the bounds (in screen coordinates) of the specified View. gfx::Rect GetViewScreenBounds(views::View* tabstrip) const; // Hides the frame for the window that contains the TabStrip the current // drag session was initiated from. void HideFrame(); // Closes a hidden frame at the end of a drag session. void CleanUpHiddenFrame(); void BringWindowUnderPointToFront(const gfx::Point& point_in_screen); // Convenience for getting the TabDragData corresponding to the tab the user // started dragging. TabDragData* source_tab_drag_data() { return &(drag_data_[source_tab_index_]); } // Convenience for |source_tab_drag_data()->contents|. content::WebContents* source_dragged_contents() { return source_tab_drag_data()->contents; } // Returns the Widget of the currently attached TabStrip's BrowserView. views::Widget* GetAttachedBrowserWidget(); // Returns true if the tabs were originality one after the other in // |source_tabstrip_|. bool AreTabsConsecutive(); // Calculates and returns new bounds for the dragged browser window. // Takes into consideration current and restore bounds of |source| tab strip // preventing the dragged size from being too small. Positions the new bounds // such that the tab that was dragged remains under the |point_in_screen|. // Offsets |drag_bounds| if necessary when dragging to the right from the // source browser. gfx::Rect CalculateDraggedBrowserBounds(TabStrip* source, const gfx::Point& point_in_screen, std::vector<gfx::Rect>* drag_bounds); // Calculates scaled |drag_bounds| for dragged tabs and sets the tabs bounds. // Layout of the tabstrip is performed and a new tabstrip width calculated. // When |last_tabstrip_width| is larger than the new tabstrip width the tabs // in attached tabstrip are scaled and the attached browser is positioned such // that the tab that was dragged remains under the |point_in_screen|. void AdjustBrowserAndTabBoundsForDrag(int last_tabstrip_width, const gfx::Point& point_in_screen, std::vector<gfx::Rect>* drag_bounds); // Creates and returns a new Browser to handle the drag. Browser* CreateBrowserForDrag(TabStrip* source, const gfx::Point& point_in_screen, gfx::Vector2d* drag_offset, std::vector<gfx::Rect>* drag_bounds); // Returns the TabStripModel for the specified tabstrip. TabStripModel* GetModel(TabStrip* tabstrip) const; // Returns the location of the cursor. This is either the location of the // mouse or the location of the current touch point. gfx::Point GetCursorScreenPoint(); // Returns the offset from the top left corner of the window to // |point_in_screen|. gfx::Vector2d GetWindowOffset(const gfx::Point& point_in_screen); // Returns true if moving the mouse only changes the visible tabs. bool move_only() const { return (move_behavior_ == MOVE_VISIBILE_TABS) != 0; } // Returns the NativeWindow at the specified point. If |exclude_dragged_view| // is true, then the dragged view is not considered. gfx::NativeWindow GetLocalProcessWindow(const gfx::Point& screen_point, bool exclude_dragged_view); // If true detaching creates a new browser and enters a nested message loop. bool detach_into_browser_; // Handles registering for notifications. content::NotificationRegistrar registrar_; EventSource event_source_; // The TabStrip the drag originated from. TabStrip* source_tabstrip_; // The TabStrip the dragged Tab is currently attached to, or NULL if the // dragged Tab is detached. TabStrip* attached_tabstrip_; // The screen that this drag is associated with. Cached, because other UI // elements are NULLd at various points during the lifetime of this object. gfx::Screen* screen_; // The desktop type that this drag is associated with. Cached, because other // UI elements are NULLd at various points during the lifetime of this // object. chrome::HostDesktopType host_desktop_type_; // Aura mouse capture and release is used on Ash platforms as well as on // Linux to ensure that pointer grab is not released prematurely. bool use_aura_capture_policy_; // The position of the mouse (in screen coordinates) at the start of the drag // operation. This is used to calculate minimum elasticity before a // DraggedTabView is constructed. gfx::Point start_point_in_screen_; // This is the offset of the mouse from the top left of the first Tab where // dragging began. This is used to ensure that the dragged view is always // positioned at the correct location during the drag, and to ensure that the // detached window is created at the right location. gfx::Point mouse_offset_; // Ratio of the x-coordinate of the |source_tab_offset| to the width of the // tab. float offset_to_width_ratio_; // A hint to use when positioning new windows created by detaching Tabs. This // is the distance of the mouse from the top left of the dragged tab as if it // were the distance of the mouse from the top left of the first tab in the // attached TabStrip from the top left of the window. gfx::Point window_create_point_; // Location of the first tab in the source tabstrip in screen coordinates. // This is used to calculate |window_create_point_|. gfx::Point first_source_tab_point_; // The bounds of the browser window before the last Tab was detached. When // the last Tab is detached, rather than destroying the frame (which would // abort the drag session), the frame is moved off-screen. If the drag is // aborted (e.g. by the user pressing Esc, or capture being lost), the Tab is // attached to the hidden frame and the frame moved back to these bounds. gfx::Rect restore_bounds_; // Storage ID in ViewStorage where the last view that had focus in the window // containing |source_tab_| is saved. This is saved so that focus can be // restored properly when a drag begins and ends within this same window. const int old_focused_view_id_; // The horizontal position of the mouse cursor in screen coordinates at the // time of the last re-order event. int last_move_screen_loc_; // Timer used to bring the window under the cursor to front. If the user // stops moving the mouse for a brief time over a browser window, it is // brought to front. base::OneShotTimer<TabDragController> bring_to_front_timer_; // Timer used to move the stacked tabs. See comment aboue // StartMoveStackedTimerIfNecessary(). base::OneShotTimer<TabDragController> move_stacked_timer_; // Did the mouse move enough that we started a drag? bool started_drag_; // Is the drag active? bool active_; DragData drag_data_; // Index of the source tab in |drag_data_|. size_t source_tab_index_; // True until MoveAttached() is first invoked. bool initial_move_; // The selection model before the drag started. See comment above Init() for // details. ui::ListSelectionModel initial_selection_model_; // The selection model of |attached_tabstrip_| before the tabs were attached. ui::ListSelectionModel selection_model_before_attach_; // Initial x-coordinates of the tabs when the drag started. Only used for // touch mode. std::vector<int> initial_tab_positions_; DetachBehavior detach_behavior_; MoveBehavior move_behavior_; // Updated as the mouse is moved when attached. Indicates whether the mouse // has ever moved to the left or right. If the tabs are ever detached this // is set to kMovedMouseRight | kMovedMouseLeft. int mouse_move_direction_; // Last location used in screen coordinates. gfx::Point last_point_in_screen_; // The following are needed when detaching into a browser // (|detach_into_browser_| is true). // See description above getter. bool is_dragging_window_; // True if |attached_tabstrip_| is in a browser specifically created for // the drag. bool is_dragging_new_browser_; // True if |source_tabstrip_| was maximized before the drag. bool was_source_maximized_; // True if |source_tabstrip_| was in immersive fullscreen before the drag. bool was_source_fullscreen_; // True if the initial drag resulted in restoring the window (because it was // maximized). bool did_restore_window_; EndRunLoopBehavior end_run_loop_behavior_; // If true, we're waiting for a move loop to complete. bool waiting_for_run_loop_to_exit_; // The TabStrip to attach to after the move loop completes. TabStrip* tab_strip_to_attach_to_after_exit_; // Non-null for the duration of RunMoveLoop. views::Widget* move_loop_widget_; // See description above getter. bool is_mutating_; // |attach_x_| and |attach_index_| are set to the x-coordinate of the mouse // (in terms of the tabstrip) and the insertion index at the time tabs are // dragged into a new browser (attached). They are used to ensure we don't // shift the tabs around in the wrong direction. The two are only valid if // |attach_index_| is not -1. // See comment around use for more details. int attach_x_; int attach_index_; base::WeakPtrFactory<TabDragController> weak_factory_; DISALLOW_COPY_AND_ASSIGN(TabDragController); }; #endif // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_