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 22 namespace gfx { 23 class Screen; 24 } 25 namespace ui { 26 class EventHandler; 27 class ListSelectionModel; 28 } 29 namespace views { 30 class View; 31 } 32 class Browser; 33 class Tab; 34 struct TabRendererData; 35 class TabStrip; 36 class 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. 49 class 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