blob: 6af7ec79d4f63da46e3cbed4d77dc762fd2b0287 [file] [log] [blame]
// 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 <stddef.h>
#include <memory>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/views/tabs/tab_strip_types.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/widget/widget_observer.h"
namespace ui {
class EventHandler;
class ListSelectionModel;
}
namespace views {
class View;
class ViewTracker;
}
class Browser;
class Tab;
class TabDragControllerTest;
class TabStrip;
class TabStripModel;
class WindowFinder;
// 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 views::WidgetObserver,
public TabStripModelObserver {
public:
// 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_VISIBLE_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();
~TabDragController() override;
// 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,
ui::ListSelectionModel initial_selection_model,
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_; }
// Returns true if currently dragging a tab with |contents|.
bool IsDraggingTab(content::WebContents* contents);
// 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:
friend class TabDragControllerTest;
// Used to indicate the direction the mouse has moved when attached.
static const int kMovedMouseLeft = 1 << 0;
static const int kMovedMouseRight = 1 << 1;
enum class Liveness {
ALIVE,
DELETED,
};
// 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
};
// Specifies what should happen when a drag motion exits the tab strip region
// in an attempt to detach a tab.
enum DetachBehavior {
DETACHABLE,
NOT_DETACHABLE
};
// Indicates what should happen after invoking DragBrowserToNewTabStrip().
enum DragBrowserResultType {
// The caller should return immediately. This return value is used if a
// nested run loop was created or we're in a nested run 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();
TabDragData(TabDragData&&);
// The WebContents being dragged.
content::WebContents* contents;
// There is a brief period of time when a tab is being moved from one tab
// strip to another [after Detach but before Attach] that the TabDragData
// owns the WebContents.
std::unique_ptr<content::WebContents> owned_contents;
// 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;
private:
DISALLOW_COPY_AND_ASSIGN(TabDragData);
};
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);
// Overriden from views::WidgetObserver:
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) override;
// Overriden from TabStripModelObserver:
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).
Liveness ContinueDragging(const gfx::Point& point_in_screen)
WARN_UNUSED_RESULT;
// 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 nullptr if there is none.
Liveness GetTargetTabStripForPoint(const gfx::Point& point_in_screen,
TabStrip** tab_strip);
// 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. If |set_capture| is true,
// the newly attached tabstrip will have capture.
void Attach(TabStrip* attached_tabstrip,
const gfx::Point& point_in_screen,
bool set_capture = true);
// 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 run 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 tab being dragged, |start| the index of the tab to start looking from.
// The search proceeds to the end of the strip.
int GetInsertionIndexFrom(const gfx::Rect& dragged_bounds, int start) const;
// Like GetInsertionIndexFrom(), but searches backwards from |start| to the
// beginning of the strip.
int GetInsertionIndexFromReversed(const gfx::Rect& dragged_bounds,
int start) 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;
// Retrieves the bounds of the dragged tabs 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);
// Gets the position of the dragged tabs 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);
// Called after the drag ends and |deferred_target_tabstrip_| is not nullptr.
void PerformDeferredAttach();
// 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 successful drag operation.
void CompleteDrag();
// Maximizes the attached window.
void MaximizeAttachedWindow();
// 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();
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 and returns the dragged bounds for the non-maximize dragged
// browser window. Taks into consideration the initial drag offset so that
// the dragged tab remains under the |point_in_screen|.
gfx::Rect CalculateNonMaximizedDraggedBrowserBounds(
views::Widget* widget,
const gfx::Point& point_in_screen);
// 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 the 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_VISIBLE_TABS) != 0;
}
// Returns the NativeWindow in |window| at the specified point. If
// |exclude_dragged_view| is true, then the dragged view is not considered.
Liveness GetLocalProcessWindow(const gfx::Point& screen_point,
bool exclude_dragged_view,
gfx::NativeWindow* window) WARN_UNUSED_RESULT;
// Sets the dragging info for the current dragged tabstrip. On Chrome OS, the
// dragging info include two window properties: one is to indicate if the
// tab-dragging process starts/stops, and the other is to indicate which
// window initiates the dragging. This function is supposed to be called
// whenever the dragged tabs are attached to a new tabstrip.
void SetTabDraggingInfo();
// Clears the tab dragging info for the current dragged tabstrip. This
// function is supposed to be called whenever the dragged tabs are detached
// from the old tabstrip or the tab dragging is ended.
void ClearTabDraggingInfo();
// Sets |deferred_target_tabstrip_| and updates its corresponding window
// property.
void SetDeferredTargetTabstrip(TabStrip* deferred_target_tabstrip);
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 target TabStrip to attach to after the drag ends. It's only possible
// to happen in Chrome OS tablet mode, if the dragged tabs are dragged over
// an overview window, we should wait until the drag ends to attach it.
TabStrip* deferred_target_tabstrip_ = nullptr;
// Whether capture can be released during the drag. When false, capture should
// not be released when transferring capture between widgets and when starting
// the move loop.
bool can_release_capture_;
// 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_;
// Used to track the view that had focus in the window containing
// |source_tab_|. This is saved so that focus can be restored properly when
// a drag begins and ends within this same window.
std::unique_ptr<views::ViewTracker> old_focused_view_tracker_;
// 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 bring_to_front_timer_;
// Timer used to move the stacked tabs. See comment aboue
// StartMoveStackedTimerIfNecessary().
base::OneShotTimer 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_;
// What should occur during ConinueDragging when a tab is attempted to be
// detached.
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_;
std::unique_ptr<ui::EventHandler> escape_tracker_;
std::unique_ptr<WindowFinder> window_finder_;
base::WeakPtrFactory<TabDragController> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(TabDragController);
};
#endif // CHROME_BROWSER_UI_VIEWS_TABS_TAB_DRAG_CONTROLLER_H_