tab_drag_controller.cc revision 010d83a9304c5a91596085d917d248abff47903a
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#include "chrome/browser/ui/views/tabs/tab_drag_controller.h" 6 7#include <math.h> 8#include <set> 9 10#include "base/auto_reset.h" 11#include "base/callback.h" 12#include "base/command_line.h" 13#include "base/i18n/rtl.h" 14#include "chrome/browser/chrome_notification_types.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h" 17#include "chrome/browser/ui/browser_list.h" 18#include "chrome/browser/ui/browser_window.h" 19#include "chrome/browser/ui/media_utils.h" 20#include "chrome/browser/ui/tabs/tab_strip_model.h" 21#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" 22#include "chrome/browser/ui/views/frame/browser_view.h" 23#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" 24#include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h" 25#include "chrome/browser/ui/views/tabs/tab.h" 26#include "chrome/browser/ui/views/tabs/tab_strip.h" 27#include "chrome/browser/ui/views/tabs/window_finder.h" 28#include "chrome/common/chrome_switches.h" 29#include "content/public/browser/invalidate_type.h" 30#include "content/public/browser/notification_details.h" 31#include "content/public/browser/notification_service.h" 32#include "content/public/browser/notification_source.h" 33#include "content/public/browser/notification_types.h" 34#include "content/public/browser/user_metrics.h" 35#include "content/public/browser/web_contents.h" 36#include "extensions/browser/extension_function_dispatcher.h" 37#include "ui/aura/env.h" 38#include "ui/base/resource/resource_bundle.h" 39#include "ui/events/event_constants.h" 40#include "ui/events/event_utils.h" 41#include "ui/gfx/geometry/point_conversions.h" 42#include "ui/gfx/screen.h" 43#include "ui/views/focus/view_storage.h" 44#include "ui/views/widget/root_view.h" 45#include "ui/views/widget/widget.h" 46#include "ui/wm/core/window_modality_controller.h" 47 48#if defined(USE_ASH) 49#include "ash/accelerators/accelerator_commands.h" 50#include "ash/wm/coordinate_conversion.h" 51#include "ash/wm/window_state.h" 52#include "ui/aura/env.h" 53#include "ui/aura/window.h" 54#include "ui/aura/window_event_dispatcher.h" 55#include "ui/events/gestures/gesture_recognizer.h" 56#endif 57 58#if defined(OS_WIN) 59#include "ui/aura/window.h" 60#include "ui/events/gestures/gesture_recognizer.h" 61#endif 62 63using base::UserMetricsAction; 64using content::OpenURLParams; 65using content::WebContents; 66 67// If non-null there is a drag underway. 68static TabDragController* instance_ = NULL; 69 70namespace { 71 72// Delay, in ms, during dragging before we bring a window to front. 73const int kBringToFrontDelay = 750; 74 75// Initial delay before moving tabs when the dragged tab is close to the edge of 76// the stacked tabs. 77const int kMoveAttachedInitialDelay = 600; 78 79// Delay for moving tabs after the initial delay has passed. 80const int kMoveAttachedSubsequentDelay = 300; 81 82const int kHorizontalMoveThreshold = 16; // Pixels. 83 84// Distance from the next/previous stacked before before we consider the tab 85// close enough to trigger moving. 86const int kStackedDistance = 36; 87 88void SetWindowPositionManaged(gfx::NativeWindow window, bool value) { 89#if defined(USE_ASH) 90 ash::wm::GetWindowState(window)->set_window_position_managed(value); 91#endif 92} 93 94// Returns true if |tab_strip| browser window is docked. 95bool IsDockedOrSnapped(const TabStrip* tab_strip) { 96#if defined(USE_ASH) 97 DCHECK(tab_strip); 98 ash::wm::WindowState* window_state = 99 ash::wm::GetWindowState(tab_strip->GetWidget()->GetNativeWindow()); 100 return window_state->IsDocked() || window_state->IsSnapped(); 101#else 102 return false; 103#endif 104} 105 106// Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate 107// of |bounds| is adjusted by |vertical_adjustment|. 108bool DoesRectContainVerticalPointExpanded( 109 const gfx::Rect& bounds, 110 int vertical_adjustment, 111 int y) { 112 int upper_threshold = bounds.bottom() + vertical_adjustment; 113 int lower_threshold = bounds.y() - vertical_adjustment; 114 return y >= lower_threshold && y <= upper_threshold; 115} 116 117// Adds |x_offset| to all the rectangles in |rects|. 118void OffsetX(int x_offset, std::vector<gfx::Rect>* rects) { 119 if (x_offset == 0) 120 return; 121 122 for (size_t i = 0; i < rects->size(); ++i) 123 (*rects)[i].set_x((*rects)[i].x() + x_offset); 124} 125 126// WidgetObserver implementation that resets the window position managed 127// property on Show. 128// We're forced to do this here since BrowserFrameAsh resets the 'window 129// position managed' property during a show and we need the property set to 130// false before WorkspaceLayoutManager sees the visibility change. 131class WindowPositionManagedUpdater : public views::WidgetObserver { 132 public: 133 virtual void OnWidgetVisibilityChanged(views::Widget* widget, 134 bool visible) OVERRIDE { 135 SetWindowPositionManaged(widget->GetNativeView(), false); 136 } 137}; 138 139// EscapeTracker installs itself as a pre-target handler on aura::Env and runs a 140// callback when it receives the escape key. 141class EscapeTracker : public ui::EventHandler { 142 public: 143 explicit EscapeTracker(const base::Closure& callback) 144 : escape_callback_(callback) { 145 aura::Env::GetInstance()->AddPreTargetHandler(this); 146 } 147 148 virtual ~EscapeTracker() { 149 aura::Env::GetInstance()->RemovePreTargetHandler(this); 150 } 151 152 private: 153 // ui::EventHandler: 154 virtual void OnKeyEvent(ui::KeyEvent* key) OVERRIDE { 155 if (key->type() == ui::ET_KEY_PRESSED && 156 key->key_code() == ui::VKEY_ESCAPE) { 157 escape_callback_.Run(); 158 } 159 } 160 161 base::Closure escape_callback_; 162 163 DISALLOW_COPY_AND_ASSIGN(EscapeTracker); 164}; 165 166} // namespace 167 168TabDragController::TabDragData::TabDragData() 169 : contents(NULL), 170 original_delegate(NULL), 171 source_model_index(-1), 172 attached_tab(NULL), 173 pinned(false) { 174} 175 176TabDragController::TabDragData::~TabDragData() { 177} 178 179/////////////////////////////////////////////////////////////////////////////// 180// TabDragController, public: 181 182// static 183const int TabDragController::kTouchVerticalDetachMagnetism = 50; 184 185// static 186const int TabDragController::kVerticalDetachMagnetism = 15; 187 188TabDragController::TabDragController() 189 : detach_into_browser_(true), 190 event_source_(EVENT_SOURCE_MOUSE), 191 source_tabstrip_(NULL), 192 attached_tabstrip_(NULL), 193 screen_(NULL), 194 host_desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE), 195 use_aura_capture_policy_(false), 196 offset_to_width_ratio_(0), 197 old_focused_view_id_( 198 views::ViewStorage::GetInstance()->CreateStorageID()), 199 last_move_screen_loc_(0), 200 started_drag_(false), 201 active_(true), 202 source_tab_index_(std::numeric_limits<size_t>::max()), 203 initial_move_(true), 204 detach_behavior_(DETACHABLE), 205 move_behavior_(REORDER), 206 mouse_move_direction_(0), 207 is_dragging_window_(false), 208 is_dragging_new_browser_(false), 209 was_source_maximized_(false), 210 was_source_fullscreen_(false), 211 did_restore_window_(false), 212 end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING), 213 waiting_for_run_loop_to_exit_(false), 214 tab_strip_to_attach_to_after_exit_(NULL), 215 move_loop_widget_(NULL), 216 is_mutating_(false), 217 attach_x_(-1), 218 attach_index_(-1), 219 weak_factory_(this) { 220 instance_ = this; 221} 222 223TabDragController::~TabDragController() { 224 views::ViewStorage::GetInstance()->RemoveView(old_focused_view_id_); 225 226 if (instance_ == this) 227 instance_ = NULL; 228 229 if (move_loop_widget_) { 230 move_loop_widget_->RemoveObserver(this); 231 SetWindowPositionManaged(move_loop_widget_->GetNativeView(), true); 232 } 233 234 if (source_tabstrip_ && detach_into_browser_) 235 GetModel(source_tabstrip_)->RemoveObserver(this); 236 237 // Reset the delegate of the dragged WebContents. This ends up doing nothing 238 // if the drag was completed. 239 if (!detach_into_browser_) 240 ResetDelegates(); 241 242 if (event_source_ == EVENT_SOURCE_TOUCH) { 243 TabStrip* capture_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? 244 attached_tabstrip_ : source_tabstrip_; 245 capture_tabstrip->GetWidget()->ReleaseCapture(); 246 } 247} 248 249void TabDragController::Init( 250 TabStrip* source_tabstrip, 251 Tab* source_tab, 252 const std::vector<Tab*>& tabs, 253 const gfx::Point& mouse_offset, 254 int source_tab_offset, 255 const ui::ListSelectionModel& initial_selection_model, 256 DetachBehavior detach_behavior, 257 MoveBehavior move_behavior, 258 EventSource event_source) { 259 DCHECK(!tabs.empty()); 260 DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); 261 source_tabstrip_ = source_tabstrip; 262 was_source_maximized_ = source_tabstrip->GetWidget()->IsMaximized(); 263 was_source_fullscreen_ = source_tabstrip->GetWidget()->IsFullscreen(); 264 screen_ = gfx::Screen::GetScreenFor( 265 source_tabstrip->GetWidget()->GetNativeView()); 266 host_desktop_type_ = chrome::GetHostDesktopTypeForNativeView( 267 source_tabstrip->GetWidget()->GetNativeView()); 268#if defined(OS_LINUX) 269 use_aura_capture_policy_ = true; 270#else 271 use_aura_capture_policy_ = 272 (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH); 273#endif 274 start_point_in_screen_ = gfx::Point(source_tab_offset, mouse_offset.y()); 275 views::View::ConvertPointToScreen(source_tab, &start_point_in_screen_); 276 event_source_ = event_source; 277 mouse_offset_ = mouse_offset; 278 detach_behavior_ = detach_behavior; 279 move_behavior_ = move_behavior; 280 last_point_in_screen_ = start_point_in_screen_; 281 last_move_screen_loc_ = start_point_in_screen_.x(); 282 initial_tab_positions_ = source_tabstrip->GetTabXCoordinates(); 283 if (detach_behavior == NOT_DETACHABLE) 284 detach_into_browser_ = false; 285 286 if (detach_into_browser_) 287 GetModel(source_tabstrip_)->AddObserver(this); 288 289 drag_data_.resize(tabs.size()); 290 for (size_t i = 0; i < tabs.size(); ++i) 291 InitTabDragData(tabs[i], &(drag_data_[i])); 292 source_tab_index_ = 293 std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin(); 294 295 // Listen for Esc key presses. 296 escape_tracker_.reset( 297 new EscapeTracker(base::Bind(&TabDragController::EndDrag, 298 weak_factory_.GetWeakPtr(), 299 END_DRAG_CANCEL))); 300 301 if (source_tab->width() > 0) { 302 offset_to_width_ratio_ = static_cast<float>( 303 source_tab->GetMirroredXInView(source_tab_offset)) / 304 static_cast<float>(source_tab->width()); 305 } 306 InitWindowCreatePoint(); 307 initial_selection_model_.Copy(initial_selection_model); 308 309 // Gestures don't automatically do a capture. We don't allow multiple drags at 310 // the same time, so we explicitly capture. 311 if (event_source == EVENT_SOURCE_TOUCH) 312 source_tabstrip_->GetWidget()->SetCapture(source_tabstrip_); 313} 314 315// static 316bool TabDragController::IsAttachedTo(const TabStrip* tab_strip) { 317 return (instance_ && instance_->active() && 318 instance_->attached_tabstrip() == tab_strip); 319} 320 321// static 322bool TabDragController::IsActive() { 323 return instance_ && instance_->active(); 324} 325 326void TabDragController::SetMoveBehavior(MoveBehavior behavior) { 327 if (started_drag()) 328 return; 329 330 move_behavior_ = behavior; 331} 332 333void TabDragController::Drag(const gfx::Point& point_in_screen) { 334 TRACE_EVENT1("views", "TabDragController::Drag", 335 "point_in_screen", point_in_screen.ToString()); 336 337 bring_to_front_timer_.Stop(); 338 move_stacked_timer_.Stop(); 339 340 if (waiting_for_run_loop_to_exit_) 341 return; 342 343 if (!started_drag_) { 344 if (!CanStartDrag(point_in_screen)) 345 return; // User hasn't dragged far enough yet. 346 347 // On windows SaveFocus() may trigger a capture lost, which destroys us. 348 { 349 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 350 SaveFocus(); 351 if (!ref) 352 return; 353 } 354 started_drag_ = true; 355 Attach(source_tabstrip_, gfx::Point()); 356 if (detach_into_browser_ && static_cast<int>(drag_data_.size()) == 357 GetModel(source_tabstrip_)->count()) { 358 if (was_source_maximized_ || was_source_fullscreen_) { 359 did_restore_window_ = true; 360 // When all tabs in a maximized browser are dragged the browser gets 361 // restored during the drag and maximized back when the drag ends. 362 views::Widget* widget = GetAttachedBrowserWidget(); 363 const int last_tabstrip_width = attached_tabstrip_->tab_area_width(); 364 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 365 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds); 366 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source_tabstrip_, 367 point_in_screen, 368 &drag_bounds)); 369 new_bounds.Offset(-widget->GetRestoredBounds().x() + 370 point_in_screen.x() - 371 mouse_offset_.x(), 0); 372 widget->SetVisibilityChangedAnimationsEnabled(false); 373 widget->Restore(); 374 widget->SetBounds(new_bounds); 375 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, 376 point_in_screen, 377 &drag_bounds); 378 widget->SetVisibilityChangedAnimationsEnabled(true); 379 } 380 RunMoveLoop(GetWindowOffset(point_in_screen)); 381 return; 382 } 383 } 384 385 ContinueDragging(point_in_screen); 386} 387 388void TabDragController::EndDrag(EndDragReason reason) { 389 TRACE_EVENT0("views", "TabDragController::EndDrag"); 390 391 // If we're dragging a window ignore capture lost since it'll ultimately 392 // trigger the move loop to end and we'll revert the drag when RunMoveLoop() 393 // finishes. 394 if (reason == END_DRAG_CAPTURE_LOST && is_dragging_window_) 395 return; 396 EndDragImpl(reason != END_DRAG_COMPLETE && source_tabstrip_ ? 397 CANCELED : NORMAL); 398} 399 400void TabDragController::InitTabDragData(Tab* tab, 401 TabDragData* drag_data) { 402 TRACE_EVENT0("views", "TabDragController::InitTabDragData"); 403 drag_data->source_model_index = 404 source_tabstrip_->GetModelIndexOfTab(tab); 405 drag_data->contents = GetModel(source_tabstrip_)->GetWebContentsAt( 406 drag_data->source_model_index); 407 drag_data->pinned = source_tabstrip_->IsTabPinned(tab); 408 registrar_.Add( 409 this, 410 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 411 content::Source<WebContents>(drag_data->contents)); 412 413 if (!detach_into_browser_) { 414 drag_data->original_delegate = drag_data->contents->GetDelegate(); 415 drag_data->contents->SetDelegate(this); 416 } 417} 418 419/////////////////////////////////////////////////////////////////////////////// 420// TabDragController, PageNavigator implementation: 421 422WebContents* TabDragController::OpenURLFromTab( 423 WebContents* source, 424 const OpenURLParams& params) { 425 if (source_tab_drag_data()->original_delegate) { 426 OpenURLParams forward_params = params; 427 if (params.disposition == CURRENT_TAB) 428 forward_params.disposition = NEW_WINDOW; 429 430 return source_tab_drag_data()->original_delegate->OpenURLFromTab( 431 source, forward_params); 432 } 433 return NULL; 434} 435 436/////////////////////////////////////////////////////////////////////////////// 437// TabDragController, content::WebContentsDelegate implementation: 438 439void TabDragController::NavigationStateChanged(const WebContents* source, 440 unsigned changed_flags) { 441 if (attached_tabstrip_ || 442 changed_flags == content::INVALIDATE_TYPE_PAGE_ACTIONS) { 443 for (size_t i = 0; i < drag_data_.size(); ++i) { 444 if (drag_data_[i].contents == source) { 445 // Pass the NavigationStateChanged call to the original delegate so 446 // that the title is updated. Do this only when we are attached as 447 // otherwise the Tab isn't in the TabStrip (except for page action 448 // updates). 449 drag_data_[i].original_delegate->NavigationStateChanged(source, 450 changed_flags); 451 break; 452 } 453 } 454 } 455} 456 457void TabDragController::AddNewContents(WebContents* source, 458 WebContents* new_contents, 459 WindowOpenDisposition disposition, 460 const gfx::Rect& initial_pos, 461 bool user_gesture, 462 bool* was_blocked) { 463 DCHECK_NE(CURRENT_TAB, disposition); 464 465 // Theoretically could be called while dragging if the page tries to 466 // spawn a window. Route this message back to the browser in most cases. 467 if (source_tab_drag_data()->original_delegate) { 468 source_tab_drag_data()->original_delegate->AddNewContents( 469 source, new_contents, disposition, initial_pos, user_gesture, 470 was_blocked); 471 } 472} 473 474bool TabDragController::ShouldSuppressDialogs() { 475 // When a dialog is about to be shown we revert the drag. Otherwise a modal 476 // dialog might appear and attempt to parent itself to a hidden tabcontents. 477 EndDragImpl(CANCELED); 478 return false; 479} 480 481content::JavaScriptDialogManager* 482TabDragController::GetJavaScriptDialogManager() { 483 return GetJavaScriptDialogManagerInstance(); 484} 485 486void TabDragController::RequestMediaAccessPermission( 487 content::WebContents* web_contents, 488 const content::MediaStreamRequest& request, 489 const content::MediaResponseCallback& callback) { 490 ::RequestMediaAccessPermission( 491 web_contents, 492 Profile::FromBrowserContext(web_contents->GetBrowserContext()), 493 request, 494 callback); 495} 496 497/////////////////////////////////////////////////////////////////////////////// 498// TabDragController, content::NotificationObserver implementation: 499 500void TabDragController::Observe( 501 int type, 502 const content::NotificationSource& source, 503 const content::NotificationDetails& details) { 504 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); 505 WebContents* destroyed_web_contents = 506 content::Source<WebContents>(source).ptr(); 507 for (size_t i = 0; i < drag_data_.size(); ++i) { 508 if (drag_data_[i].contents == destroyed_web_contents) { 509 // One of the tabs we're dragging has been destroyed. Cancel the drag. 510 if (destroyed_web_contents->GetDelegate() == this) 511 destroyed_web_contents->SetDelegate(NULL); 512 drag_data_[i].contents = NULL; 513 drag_data_[i].original_delegate = NULL; 514 EndDragImpl(TAB_DESTROYED); 515 return; 516 } 517 } 518 // If we get here it means we got notification for a tab we don't know about. 519 NOTREACHED(); 520} 521 522void TabDragController::OnWidgetBoundsChanged(views::Widget* widget, 523 const gfx::Rect& new_bounds) { 524 TRACE_EVENT1("views", "TabDragController::OnWidgetBoundsChanged", 525 "new_bounds", new_bounds.ToString()); 526 527 Drag(GetCursorScreenPoint()); 528} 529 530void TabDragController::TabStripEmpty() { 531 DCHECK(detach_into_browser_); 532 GetModel(source_tabstrip_)->RemoveObserver(this); 533 // NULL out source_tabstrip_ so that we don't attempt to add back to it (in 534 // the case of a revert). 535 source_tabstrip_ = NULL; 536} 537 538/////////////////////////////////////////////////////////////////////////////// 539// TabDragController, private: 540 541void TabDragController::InitWindowCreatePoint() { 542 // window_create_point_ is only used in CompleteDrag() (through 543 // GetWindowCreatePoint() to get the start point of the docked window) when 544 // the attached_tabstrip_ is NULL and all the window's related bound 545 // information are obtained from source_tabstrip_. So, we need to get the 546 // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise, 547 // the window_create_point_ is not in the correct coordinate system. Please 548 // refer to http://crbug.com/6223 comment #15 for detailed information. 549 views::View* first_tab = source_tabstrip_->tab_at(0); 550 views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_); 551 window_create_point_ = first_source_tab_point_; 552 window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y()); 553} 554 555gfx::Point TabDragController::GetWindowCreatePoint( 556 const gfx::Point& origin) const { 557 // If the cursor is outside the monitor area, move it inside. For example, 558 // dropping a tab onto the task bar on Windows produces this situation. 559 gfx::Rect work_area = screen_->GetDisplayNearestPoint(origin).work_area(); 560 gfx::Point create_point(origin); 561 if (!work_area.IsEmpty()) { 562 if (create_point.x() < work_area.x()) 563 create_point.set_x(work_area.x()); 564 else if (create_point.x() > work_area.right()) 565 create_point.set_x(work_area.right()); 566 if (create_point.y() < work_area.y()) 567 create_point.set_y(work_area.y()); 568 else if (create_point.y() > work_area.bottom()) 569 create_point.set_y(work_area.bottom()); 570 } 571 return gfx::Point(create_point.x() - window_create_point_.x(), 572 create_point.y() - window_create_point_.y()); 573} 574 575void TabDragController::SaveFocus() { 576 DCHECK(source_tabstrip_); 577 views::View* focused_view = 578 source_tabstrip_->GetFocusManager()->GetFocusedView(); 579 if (focused_view) 580 views::ViewStorage::GetInstance()->StoreView(old_focused_view_id_, 581 focused_view); 582 source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_); 583 // WARNING: we may have been deleted. 584} 585 586void TabDragController::RestoreFocus() { 587 if (attached_tabstrip_ != source_tabstrip_) { 588 if (is_dragging_new_browser_) { 589 content::WebContents* active_contents = source_dragged_contents(); 590 if (active_contents && !active_contents->FocusLocationBarByDefault()) 591 active_contents->Focus(); 592 } 593 return; 594 } 595 views::View* old_focused_view = 596 views::ViewStorage::GetInstance()->RetrieveView(old_focused_view_id_); 597 if (!old_focused_view) 598 return; 599 old_focused_view->GetFocusManager()->SetFocusedView(old_focused_view); 600} 601 602bool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const { 603 // Determine if the mouse has moved beyond a minimum elasticity distance in 604 // any direction from the starting point. 605 static const int kMinimumDragDistance = 10; 606 int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x()); 607 int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y()); 608 return sqrt(pow(static_cast<float>(x_offset), 2) + 609 pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance; 610} 611 612void TabDragController::ContinueDragging(const gfx::Point& point_in_screen) { 613 TRACE_EVENT1("views", "TabDragController::ContinueDragging", 614 "point_in_screen", point_in_screen.ToString()); 615 616 DCHECK(!detach_into_browser_ || attached_tabstrip_); 617 618 TabStrip* target_tabstrip = detach_behavior_ == DETACHABLE ? 619 GetTargetTabStripForPoint(point_in_screen) : source_tabstrip_; 620 bool tab_strip_changed = (target_tabstrip != attached_tabstrip_); 621 622 if (attached_tabstrip_) { 623 int move_delta = point_in_screen.x() - last_point_in_screen_.x(); 624 if (move_delta > 0) 625 mouse_move_direction_ |= kMovedMouseRight; 626 else if (move_delta < 0) 627 mouse_move_direction_ |= kMovedMouseLeft; 628 } 629 last_point_in_screen_ = point_in_screen; 630 631 if (tab_strip_changed) { 632 is_dragging_new_browser_ = false; 633 did_restore_window_ = false; 634 if (detach_into_browser_ && 635 DragBrowserToNewTabStrip(target_tabstrip, point_in_screen) == 636 DRAG_BROWSER_RESULT_STOP) { 637 return; 638 } else if (!detach_into_browser_) { 639 if (attached_tabstrip_) 640 Detach(RELEASE_CAPTURE); 641 if (target_tabstrip) 642 Attach(target_tabstrip, point_in_screen); 643 } 644 } 645 if (is_dragging_window_) { 646 static_cast<base::Timer*>(&bring_to_front_timer_)->Start(FROM_HERE, 647 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), 648 base::Bind(&TabDragController::BringWindowUnderPointToFront, 649 base::Unretained(this), point_in_screen)); 650 } 651 652 if (!is_dragging_window_ && attached_tabstrip_) { 653 if (move_only()) { 654 DragActiveTabStacked(point_in_screen); 655 } else { 656 MoveAttached(point_in_screen); 657 if (tab_strip_changed) { 658 // Move the corresponding window to the front. We do this after the 659 // move as on windows activate triggers a synchronous paint. 660 attached_tabstrip_->GetWidget()->Activate(); 661 } 662 } 663 } 664} 665 666TabDragController::DragBrowserResultType 667TabDragController::DragBrowserToNewTabStrip( 668 TabStrip* target_tabstrip, 669 const gfx::Point& point_in_screen) { 670 TRACE_EVENT1("views", "TabDragController::DragBrowserToNewTabStrip", 671 "point_in_screen", point_in_screen.ToString()); 672 673 if (!target_tabstrip) { 674 DetachIntoNewBrowserAndRunMoveLoop(point_in_screen); 675 return DRAG_BROWSER_RESULT_STOP; 676 } 677 if (is_dragging_window_) { 678 // ReleaseCapture() is going to result in calling back to us (because it 679 // results in a move). That'll cause all sorts of problems. Reset the 680 // observer so we don't get notified and process the event. 681 if (use_aura_capture_policy_) { 682 move_loop_widget_->RemoveObserver(this); 683 move_loop_widget_ = NULL; 684 } 685 views::Widget* browser_widget = GetAttachedBrowserWidget(); 686 // Need to release the drag controller before starting the move loop as it's 687 // going to trigger capture lost, which cancels drag. 688 attached_tabstrip_->ReleaseDragController(); 689 target_tabstrip->OwnDragController(this); 690 // Disable animations so that we don't see a close animation on aero. 691 browser_widget->SetVisibilityChangedAnimationsEnabled(false); 692 // For aura we can't release capture, otherwise it'll cancel a gesture. 693 // Instead we have to directly change capture. 694 if (use_aura_capture_policy_) 695 target_tabstrip->GetWidget()->SetCapture(attached_tabstrip_); 696 else 697 browser_widget->ReleaseCapture(); 698#if defined(OS_WIN) 699 // The Gesture recognizer does not work well currently when capture changes 700 // while a touch gesture is in progress. So we need to manually transfer 701 // gesture sequence and the GR's touch events queue to the new window. This 702 // should really be done somewhere in capture change code and or inside the 703 // GR. But we currently do not have a consistent way for doing it that would 704 // work in all cases. Hence this hack. 705 ui::GestureRecognizer::Get()->TransferEventsTo( 706 browser_widget->GetNativeView(), 707 target_tabstrip->GetWidget()->GetNativeView()); 708#endif 709 710 // The window is going away. Since the drag is still on going we don't want 711 // that to effect the position of any windows. 712 SetWindowPositionManaged(browser_widget->GetNativeView(), false); 713 714#if !defined(OS_LINUX) || defined(OS_CHROMEOS) 715 // EndMoveLoop is going to snap the window back to its original location. 716 // Hide it so users don't see this. Hiding a window in Linux aura causes 717 // it to lose capture so skip it. 718 browser_widget->Hide(); 719#endif 720 browser_widget->EndMoveLoop(); 721 722 // Ideally we would always swap the tabs now, but on non-ash it seems that 723 // running the move loop implicitly activates the window when done, leading 724 // to all sorts of flicker. So, on non-ash, instead we process the move 725 // after the loop completes. But on chromeos, we can do tab swapping now to 726 // avoid the tab flashing issue(crbug.com/116329). 727 if (use_aura_capture_policy_) { 728 is_dragging_window_ = false; 729 Detach(DONT_RELEASE_CAPTURE); 730 Attach(target_tabstrip, point_in_screen); 731 // Move the tabs into position. 732 MoveAttached(point_in_screen); 733 attached_tabstrip_->GetWidget()->Activate(); 734 } else { 735 tab_strip_to_attach_to_after_exit_ = target_tabstrip; 736 } 737 738 waiting_for_run_loop_to_exit_ = true; 739 end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING; 740 return DRAG_BROWSER_RESULT_STOP; 741 } 742 Detach(DONT_RELEASE_CAPTURE); 743 Attach(target_tabstrip, point_in_screen); 744 return DRAG_BROWSER_RESULT_CONTINUE; 745} 746 747void TabDragController::DragActiveTabStacked( 748 const gfx::Point& point_in_screen) { 749 if (attached_tabstrip_->tab_count() != 750 static_cast<int>(initial_tab_positions_.size())) 751 return; // TODO: should cancel drag if this happens. 752 753 int delta = point_in_screen.x() - start_point_in_screen_.x(); 754 attached_tabstrip_->DragActiveTab(initial_tab_positions_, delta); 755} 756 757void TabDragController::MoveAttachedToNextStackedIndex( 758 const gfx::Point& point_in_screen) { 759 int index = attached_tabstrip_->touch_layout_->active_index(); 760 if (index + 1 >= attached_tabstrip_->tab_count()) 761 return; 762 763 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index + 1); 764 StartMoveStackedTimerIfNecessary(point_in_screen, 765 kMoveAttachedSubsequentDelay); 766} 767 768void TabDragController::MoveAttachedToPreviousStackedIndex( 769 const gfx::Point& point_in_screen) { 770 int index = attached_tabstrip_->touch_layout_->active_index(); 771 if (index <= attached_tabstrip_->GetMiniTabCount()) 772 return; 773 774 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index - 1); 775 StartMoveStackedTimerIfNecessary(point_in_screen, 776 kMoveAttachedSubsequentDelay); 777} 778 779void TabDragController::MoveAttached(const gfx::Point& point_in_screen) { 780 DCHECK(attached_tabstrip_); 781 DCHECK(!is_dragging_window_); 782 783 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen); 784 785 // Determine the horizontal move threshold. This is dependent on the width 786 // of tabs. The smaller the tabs compared to the standard size, the smaller 787 // the threshold. 788 int threshold = kHorizontalMoveThreshold; 789 if (!attached_tabstrip_->touch_layout_.get()) { 790 double unselected, selected; 791 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); 792 double ratio = unselected / Tab::GetStandardSize().width(); 793 threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); 794 } 795 // else case: touch tabs never shrink. 796 797 std::vector<Tab*> tabs(drag_data_.size()); 798 for (size_t i = 0; i < drag_data_.size(); ++i) 799 tabs[i] = drag_data_[i].attached_tab; 800 801 bool did_layout = false; 802 // Update the model, moving the WebContents from one index to another. Do this 803 // only if we have moved a minimum distance since the last reorder (to prevent 804 // jitter) or if this the first move and the tabs are not consecutive. 805 if ((abs(point_in_screen.x() - last_move_screen_loc_) > threshold || 806 (initial_move_ && !AreTabsConsecutive()))) { 807 TabStripModel* attached_model = GetModel(attached_tabstrip_); 808 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 809 int to_index = GetInsertionIndexForDraggedBounds(bounds); 810 bool do_move = true; 811 // While dragging within a tabstrip the expectation is the insertion index 812 // is based on the left edge of the tabs being dragged. OTOH when dragging 813 // into a new tabstrip (attaching) the expectation is the insertion index is 814 // based on the cursor. This proves problematic as insertion may change the 815 // size of the tabs, resulting in the index calculated before the insert 816 // differing from the index calculated after the insert. To alleviate this 817 // the index is chosen before insertion, and subsequently a new index is 818 // only used once the mouse moves enough such that the index changes based 819 // on the direction the mouse moved relative to |attach_x_| (smaller 820 // x-coordinate should yield a smaller index or larger x-coordinate yields a 821 // larger index). 822 if (attach_index_ != -1) { 823 gfx::Point tab_strip_point(point_in_screen); 824 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point); 825 const int new_x = 826 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()); 827 if (new_x < attach_x_) 828 to_index = std::min(to_index, attach_index_); 829 else 830 to_index = std::max(to_index, attach_index_); 831 if (to_index != attach_index_) 832 attach_index_ = -1; // Once a valid move is detected, don't constrain. 833 else 834 do_move = false; 835 } 836 if (do_move) { 837 WebContents* last_contents = drag_data_[drag_data_.size() - 1].contents; 838 int index_of_last_item = 839 attached_model->GetIndexOfWebContents(last_contents); 840 if (initial_move_) { 841 // TabStrip determines if the tabs needs to be animated based on model 842 // position. This means we need to invoke LayoutDraggedTabsAt before 843 // changing the model. 844 attached_tabstrip_->LayoutDraggedTabsAt( 845 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 846 initial_move_); 847 did_layout = true; 848 } 849 attached_model->MoveSelectedTabsTo(to_index); 850 851 // Move may do nothing in certain situations (such as when dragging pinned 852 // tabs). Make sure the tabstrip actually changed before updating 853 // last_move_screen_loc_. 854 if (index_of_last_item != 855 attached_model->GetIndexOfWebContents(last_contents)) { 856 last_move_screen_loc_ = point_in_screen.x(); 857 } 858 } 859 } 860 861 if (!did_layout) { 862 attached_tabstrip_->LayoutDraggedTabsAt( 863 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 864 initial_move_); 865 } 866 867 StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay); 868 869 initial_move_ = false; 870} 871 872void TabDragController::StartMoveStackedTimerIfNecessary( 873 const gfx::Point& point_in_screen, 874 int delay_ms) { 875 DCHECK(attached_tabstrip_); 876 877 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get(); 878 if (!touch_layout) 879 return; 880 881 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen); 882 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 883 int index = touch_layout->active_index(); 884 if (ShouldDragToNextStackedTab(bounds, index)) { 885 static_cast<base::Timer*>(&move_stacked_timer_)->Start( 886 FROM_HERE, 887 base::TimeDelta::FromMilliseconds(delay_ms), 888 base::Bind(&TabDragController::MoveAttachedToNextStackedIndex, 889 base::Unretained(this), point_in_screen)); 890 } else if (ShouldDragToPreviousStackedTab(bounds, index)) { 891 static_cast<base::Timer*>(&move_stacked_timer_)->Start( 892 FROM_HERE, 893 base::TimeDelta::FromMilliseconds(delay_ms), 894 base::Bind(&TabDragController::MoveAttachedToPreviousStackedIndex, 895 base::Unretained(this), point_in_screen)); 896 } 897} 898 899TabDragController::DetachPosition TabDragController::GetDetachPosition( 900 const gfx::Point& point_in_screen) { 901 DCHECK(attached_tabstrip_); 902 gfx::Point attached_point(point_in_screen); 903 views::View::ConvertPointFromScreen(attached_tabstrip_, &attached_point); 904 if (attached_point.x() < 0) 905 return DETACH_BEFORE; 906 if (attached_point.x() >= attached_tabstrip_->width()) 907 return DETACH_AFTER; 908 return DETACH_ABOVE_OR_BELOW; 909} 910 911TabStrip* TabDragController::GetTargetTabStripForPoint( 912 const gfx::Point& point_in_screen) { 913 TRACE_EVENT1("views", "TabDragController::GetTargetTabStripForPoint", 914 "point_in_screen", point_in_screen.ToString()); 915 916 if (move_only() && attached_tabstrip_) { 917 DCHECK_EQ(DETACHABLE, detach_behavior_); 918 // move_only() is intended for touch, in which case we only want to detach 919 // if the touch point moves significantly in the vertical distance. 920 gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_); 921 if (DoesRectContainVerticalPointExpanded(tabstrip_bounds, 922 kTouchVerticalDetachMagnetism, 923 point_in_screen.y())) 924 return attached_tabstrip_; 925 } 926 gfx::NativeWindow local_window = 927 GetLocalProcessWindow(point_in_screen, is_dragging_window_); 928 // Do not allow dragging into a window with a modal dialog, it causes a weird 929 // behavior. See crbug.com/336691 930 if (!wm::GetModalTransient(local_window)) { 931 TabStrip* tab_strip = GetTabStripForWindow(local_window); 932 if (tab_strip && DoesTabStripContain(tab_strip, point_in_screen)) 933 return tab_strip; 934 } 935 936 return is_dragging_window_ ? attached_tabstrip_ : NULL; 937} 938 939TabStrip* TabDragController::GetTabStripForWindow(gfx::NativeWindow window) { 940 if (!window) 941 return NULL; 942 BrowserView* browser_view = 943 BrowserView::GetBrowserViewForNativeWindow(window); 944 // We don't allow drops on windows that don't have tabstrips. 945 if (!browser_view || 946 !browser_view->browser()->SupportsWindowFeature( 947 Browser::FEATURE_TABSTRIP)) 948 return NULL; 949 950 TabStrip* other_tabstrip = browser_view->tabstrip(); 951 TabStrip* tab_strip = 952 attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_; 953 DCHECK(tab_strip); 954 955 return other_tabstrip->controller()->IsCompatibleWith(tab_strip) ? 956 other_tabstrip : NULL; 957} 958 959bool TabDragController::DoesTabStripContain( 960 TabStrip* tabstrip, 961 const gfx::Point& point_in_screen) const { 962 // Make sure the specified screen point is actually within the bounds of the 963 // specified tabstrip... 964 gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip); 965 return point_in_screen.x() < tabstrip_bounds.right() && 966 point_in_screen.x() >= tabstrip_bounds.x() && 967 DoesRectContainVerticalPointExpanded(tabstrip_bounds, 968 kVerticalDetachMagnetism, 969 point_in_screen.y()); 970} 971 972void TabDragController::Attach(TabStrip* attached_tabstrip, 973 const gfx::Point& point_in_screen) { 974 TRACE_EVENT1("views", "TabDragController::Attach", 975 "point_in_screen", point_in_screen.ToString()); 976 977 DCHECK(!attached_tabstrip_); // We should already have detached by the time 978 // we get here. 979 980 attached_tabstrip_ = attached_tabstrip; 981 982 std::vector<Tab*> tabs = 983 GetTabsMatchingDraggedContents(attached_tabstrip_); 984 985 if (tabs.empty()) { 986 // Transitioning from detached to attached to a new tabstrip. Add tabs to 987 // the new model. 988 989 selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel()); 990 991 if (!detach_into_browser_) { 992 // Remove ourselves as the delegate now that the dragged WebContents is 993 // being inserted back into a Browser. 994 for (size_t i = 0; i < drag_data_.size(); ++i) { 995 drag_data_[i].contents->SetDelegate(NULL); 996 drag_data_[i].original_delegate = NULL; 997 } 998 999 // Return the WebContents to normalcy. 1000 source_dragged_contents()->DecrementCapturerCount(); 1001 } 1002 1003 // Inserting counts as a move. We don't want the tabs to jitter when the 1004 // user moves the tab immediately after attaching it. 1005 last_move_screen_loc_ = point_in_screen.x(); 1006 1007 // Figure out where to insert the tab based on the bounds of the dragged 1008 // representation and the ideal bounds of the other Tabs already in the 1009 // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are 1010 // changing due to animation). 1011 gfx::Point tab_strip_point(point_in_screen); 1012 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point); 1013 tab_strip_point.set_x( 1014 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x())); 1015 tab_strip_point.Offset(0, -mouse_offset_.y()); 1016 gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point); 1017 int index = GetInsertionIndexForDraggedBounds(bounds); 1018 attach_index_ = index; 1019 attach_x_ = tab_strip_point.x(); 1020 base::AutoReset<bool> setter(&is_mutating_, true); 1021 for (size_t i = 0; i < drag_data_.size(); ++i) { 1022 int add_types = TabStripModel::ADD_NONE; 1023 if (attached_tabstrip_->touch_layout_.get()) { 1024 // StackedTabStripLayout positions relative to the active tab, if we 1025 // don't add the tab as active things bounce around. 1026 DCHECK_EQ(1u, drag_data_.size()); 1027 add_types |= TabStripModel::ADD_ACTIVE; 1028 } 1029 if (drag_data_[i].pinned) 1030 add_types |= TabStripModel::ADD_PINNED; 1031 GetModel(attached_tabstrip_)->InsertWebContentsAt( 1032 index + i, drag_data_[i].contents, add_types); 1033 } 1034 1035 tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); 1036 } 1037 DCHECK_EQ(tabs.size(), drag_data_.size()); 1038 for (size_t i = 0; i < drag_data_.size(); ++i) 1039 drag_data_[i].attached_tab = tabs[i]; 1040 1041 attached_tabstrip_->StartedDraggingTabs(tabs); 1042 1043 ResetSelection(GetModel(attached_tabstrip_)); 1044 1045 // The size of the dragged tab may have changed. Adjust the x offset so that 1046 // ratio of mouse_offset_ to original width is maintained. 1047 std::vector<Tab*> tabs_to_source(tabs); 1048 tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1, 1049 tabs_to_source.end()); 1050 int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) - 1051 tabs[source_tab_index_]->width() + 1052 static_cast<int>(offset_to_width_ratio_ * 1053 tabs[source_tab_index_]->width()); 1054 mouse_offset_.set_x(new_x); 1055 1056 // Transfer ownership of us to the new tabstrip as well as making sure the 1057 // window has capture. This is important so that if activation changes the 1058 // drag isn't prematurely canceled. 1059 if (detach_into_browser_) { 1060 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_); 1061 attached_tabstrip_->OwnDragController(this); 1062 } 1063 1064 // Redirect all mouse events to the TabStrip so that the tab that originated 1065 // the drag can safely be deleted. 1066 if (detach_into_browser_ || attached_tabstrip_ == source_tabstrip_) { 1067 static_cast<views::internal::RootView*>( 1068 attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler( 1069 attached_tabstrip_); 1070 } 1071} 1072 1073void TabDragController::Detach(ReleaseCapture release_capture) { 1074 TRACE_EVENT1("views", "TabDragController::Detach", 1075 "release_capture", release_capture); 1076 1077 attach_index_ = -1; 1078 1079 // When the user detaches we assume they want to reorder. 1080 move_behavior_ = REORDER; 1081 1082 // Release ownership of the drag controller and mouse capture. When we 1083 // reattach ownership is transfered. 1084 if (detach_into_browser_) { 1085 attached_tabstrip_->ReleaseDragController(); 1086 if (release_capture == RELEASE_CAPTURE) 1087 attached_tabstrip_->GetWidget()->ReleaseCapture(); 1088 } 1089 1090 mouse_move_direction_ = kMovedMouseLeft | kMovedMouseRight; 1091 1092 // Prevent the WebContents HWND from being hidden by any of the model 1093 // operations performed during the drag. 1094 if (!detach_into_browser_) 1095 source_dragged_contents()->IncrementCapturerCount(gfx::Size()); 1096 1097 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 1098 TabStripModel* attached_model = GetModel(attached_tabstrip_); 1099 std::vector<TabRendererData> tab_data; 1100 for (size_t i = 0; i < drag_data_.size(); ++i) { 1101 tab_data.push_back(drag_data_[i].attached_tab->data()); 1102 int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents); 1103 DCHECK_NE(-1, index); 1104 1105 // Hide the tab so that the user doesn't see it animate closed. 1106 drag_data_[i].attached_tab->SetVisible(false); 1107 1108 attached_model->DetachWebContentsAt(index); 1109 1110 // Detaching resets the delegate, but we still want to be the delegate. 1111 if (!detach_into_browser_) 1112 drag_data_[i].contents->SetDelegate(this); 1113 1114 // Detaching may end up deleting the tab, drop references to it. 1115 drag_data_[i].attached_tab = NULL; 1116 } 1117 1118 // If we've removed the last Tab from the TabStrip, hide the frame now. 1119 if (!attached_model->empty()) { 1120 if (!selection_model_before_attach_.empty() && 1121 selection_model_before_attach_.active() >= 0 && 1122 selection_model_before_attach_.active() < attached_model->count()) { 1123 // Restore the selection. 1124 attached_model->SetSelectionFromModel(selection_model_before_attach_); 1125 } else if (attached_tabstrip_ == source_tabstrip_ && 1126 !initial_selection_model_.empty()) { 1127 RestoreInitialSelection(); 1128 } 1129 } 1130 1131 attached_tabstrip_->DraggedTabsDetached(); 1132 attached_tabstrip_ = NULL; 1133} 1134 1135void TabDragController::DetachIntoNewBrowserAndRunMoveLoop( 1136 const gfx::Point& point_in_screen) { 1137 if (GetModel(attached_tabstrip_)->count() == 1138 static_cast<int>(drag_data_.size())) { 1139 // All the tabs in a browser are being dragged but all the tabs weren't 1140 // initially being dragged. For this to happen the user would have to 1141 // start dragging a set of tabs, the other tabs close, then detach. 1142 RunMoveLoop(GetWindowOffset(point_in_screen)); 1143 return; 1144 } 1145 1146 const int last_tabstrip_width = attached_tabstrip_->tab_area_width(); 1147 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 1148 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds); 1149 1150 gfx::Vector2d drag_offset; 1151 Browser* browser = CreateBrowserForDrag( 1152 attached_tabstrip_, point_in_screen, &drag_offset, &drag_bounds); 1153#if defined(OS_WIN) 1154 gfx::NativeView attached_native_view = 1155 attached_tabstrip_->GetWidget()->GetNativeView(); 1156#endif 1157 Detach(use_aura_capture_policy_ ? DONT_RELEASE_CAPTURE : RELEASE_CAPTURE); 1158 BrowserView* dragged_browser_view = 1159 BrowserView::GetBrowserViewForBrowser(browser); 1160 views::Widget* dragged_widget = dragged_browser_view->GetWidget(); 1161#if defined(OS_WIN) 1162 // The Gesture recognizer does not work well currently when capture changes 1163 // while a touch gesture is in progress. So we need to manually transfer 1164 // gesture sequence and the GR's touch events queue to the new window. This 1165 // should really be done somewhere in capture change code and or inside the 1166 // GR. But we currently do not have a consistent way for doing it that would 1167 // work in all cases. Hence this hack. 1168 ui::GestureRecognizer::Get()->TransferEventsTo( 1169 attached_native_view, 1170 dragged_widget->GetNativeView()); 1171#endif 1172 dragged_widget->SetVisibilityChangedAnimationsEnabled(false); 1173 Attach(dragged_browser_view->tabstrip(), gfx::Point()); 1174 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, 1175 point_in_screen, 1176 &drag_bounds); 1177 WindowPositionManagedUpdater updater; 1178 dragged_widget->AddObserver(&updater); 1179 browser->window()->Show(); 1180 dragged_widget->RemoveObserver(&updater); 1181 dragged_widget->SetVisibilityChangedAnimationsEnabled(true); 1182 // Activate may trigger a focus loss, destroying us. 1183 { 1184 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 1185 browser->window()->Activate(); 1186 if (!ref) 1187 return; 1188 } 1189 RunMoveLoop(drag_offset); 1190} 1191 1192void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) { 1193 // If the user drags the whole window we'll assume they are going to attach to 1194 // another window and therefore want to reorder. 1195 move_behavior_ = REORDER; 1196 1197 move_loop_widget_ = GetAttachedBrowserWidget(); 1198 DCHECK(move_loop_widget_); 1199 move_loop_widget_->AddObserver(this); 1200 is_dragging_window_ = true; 1201 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 1202 // Running the move loop releases mouse capture on non-ash, which triggers 1203 // destroying the drag loop. Release mouse capture ourself before this while 1204 // the DragController isn't owned by the TabStrip. 1205 if (host_desktop_type_ != chrome::HOST_DESKTOP_TYPE_ASH) { 1206 attached_tabstrip_->ReleaseDragController(); 1207 attached_tabstrip_->GetWidget()->ReleaseCapture(); 1208 attached_tabstrip_->OwnDragController(this); 1209 } 1210 const views::Widget::MoveLoopSource move_loop_source = 1211 event_source_ == EVENT_SOURCE_MOUSE ? 1212 views::Widget::MOVE_LOOP_SOURCE_MOUSE : 1213 views::Widget::MOVE_LOOP_SOURCE_TOUCH; 1214 const views::Widget::MoveLoopEscapeBehavior escape_behavior = 1215 is_dragging_new_browser_ ? 1216 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_HIDE : 1217 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_DONT_HIDE; 1218 views::Widget::MoveLoopResult result = 1219 move_loop_widget_->RunMoveLoop( 1220 drag_offset, move_loop_source, escape_behavior); 1221 content::NotificationService::current()->Notify( 1222 chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE, 1223 content::NotificationService::AllBrowserContextsAndSources(), 1224 content::NotificationService::NoDetails()); 1225 1226 if (!ref) 1227 return; 1228 // Under chromeos we immediately set the |move_loop_widget_| to NULL. 1229 if (move_loop_widget_) { 1230 move_loop_widget_->RemoveObserver(this); 1231 move_loop_widget_ = NULL; 1232 } 1233 is_dragging_window_ = false; 1234 waiting_for_run_loop_to_exit_ = false; 1235 if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) { 1236 end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING; 1237 if (tab_strip_to_attach_to_after_exit_) { 1238 gfx::Point point_in_screen(GetCursorScreenPoint()); 1239 Detach(DONT_RELEASE_CAPTURE); 1240 Attach(tab_strip_to_attach_to_after_exit_, point_in_screen); 1241 // Move the tabs into position. 1242 MoveAttached(point_in_screen); 1243 attached_tabstrip_->GetWidget()->Activate(); 1244 // Activate may trigger a focus loss, destroying us. 1245 if (!ref) 1246 return; 1247 tab_strip_to_attach_to_after_exit_ = NULL; 1248 } 1249 DCHECK(attached_tabstrip_); 1250 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_); 1251 } else if (active_) { 1252 EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ? 1253 END_DRAG_CANCEL : END_DRAG_COMPLETE); 1254 } 1255} 1256 1257int TabDragController::GetInsertionIndexFrom(const gfx::Rect& dragged_bounds, 1258 int start, 1259 int delta) const { 1260 for (int i = start, tab_count = attached_tabstrip_->tab_count(); 1261 i >= 0 && i < tab_count; i += delta) { 1262 const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i); 1263 gfx::Rect left_half, right_half; 1264 ideal_bounds.SplitVertically(&left_half, &right_half); 1265 if (dragged_bounds.x() >= right_half.x() && 1266 dragged_bounds.x() < right_half.right()) { 1267 return i + 1; 1268 } else if (dragged_bounds.x() >= left_half.x() && 1269 dragged_bounds.x() < left_half.right()) { 1270 return i; 1271 } 1272 } 1273 return -1; 1274} 1275 1276int TabDragController::GetInsertionIndexForDraggedBounds( 1277 const gfx::Rect& dragged_bounds) const { 1278 int index = -1; 1279 if (attached_tabstrip_->touch_layout_.get()) { 1280 index = GetInsertionIndexForDraggedBoundsStacked(dragged_bounds); 1281 if (index != -1) { 1282 // Only move the tab to the left/right if the user actually moved the 1283 // mouse that way. This is necessary as tabs with stacked tabs 1284 // before/after them have multiple drag positions. 1285 int active_index = attached_tabstrip_->touch_layout_->active_index(); 1286 if ((index < active_index && 1287 (mouse_move_direction_ & kMovedMouseLeft) == 0) || 1288 (index > active_index && 1289 (mouse_move_direction_ & kMovedMouseRight) == 0)) { 1290 index = active_index; 1291 } 1292 } 1293 } else { 1294 index = GetInsertionIndexFrom(dragged_bounds, 0, 1); 1295 } 1296 if (index == -1) { 1297 int tab_count = attached_tabstrip_->tab_count(); 1298 int right_tab_x = tab_count == 0 ? 0 : 1299 attached_tabstrip_->ideal_bounds(tab_count - 1).right(); 1300 if (dragged_bounds.right() > right_tab_x) { 1301 index = GetModel(attached_tabstrip_)->count(); 1302 } else { 1303 index = 0; 1304 } 1305 } 1306 1307 if (!drag_data_[0].attached_tab) { 1308 // If 'attached_tab' is NULL, it means we're in the process of attaching and 1309 // don't need to constrain the index. 1310 return index; 1311 } 1312 1313 int max_index = GetModel(attached_tabstrip_)->count() - 1314 static_cast<int>(drag_data_.size()); 1315 return std::max(0, std::min(max_index, index)); 1316} 1317 1318bool TabDragController::ShouldDragToNextStackedTab( 1319 const gfx::Rect& dragged_bounds, 1320 int index) const { 1321 if (index + 1 >= attached_tabstrip_->tab_count() || 1322 !attached_tabstrip_->touch_layout_->IsStacked(index + 1) || 1323 (mouse_move_direction_ & kMovedMouseRight) == 0) 1324 return false; 1325 1326 int active_x = attached_tabstrip_->ideal_bounds(index).x(); 1327 int next_x = attached_tabstrip_->ideal_bounds(index + 1).x(); 1328 int mid_x = std::min(next_x - kStackedDistance, 1329 active_x + (next_x - active_x) / 4); 1330 return dragged_bounds.x() >= mid_x; 1331} 1332 1333bool TabDragController::ShouldDragToPreviousStackedTab( 1334 const gfx::Rect& dragged_bounds, 1335 int index) const { 1336 if (index - 1 < attached_tabstrip_->GetMiniTabCount() || 1337 !attached_tabstrip_->touch_layout_->IsStacked(index - 1) || 1338 (mouse_move_direction_ & kMovedMouseLeft) == 0) 1339 return false; 1340 1341 int active_x = attached_tabstrip_->ideal_bounds(index).x(); 1342 int previous_x = attached_tabstrip_->ideal_bounds(index - 1).x(); 1343 int mid_x = std::max(previous_x + kStackedDistance, 1344 active_x - (active_x - previous_x) / 4); 1345 return dragged_bounds.x() <= mid_x; 1346} 1347 1348int TabDragController::GetInsertionIndexForDraggedBoundsStacked( 1349 const gfx::Rect& dragged_bounds) const { 1350 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get(); 1351 int active_index = touch_layout->active_index(); 1352 // Search from the active index to the front of the tabstrip. Do this as tabs 1353 // overlap each other from the active index. 1354 int index = GetInsertionIndexFrom(dragged_bounds, active_index, -1); 1355 if (index != active_index) 1356 return index; 1357 if (index == -1) 1358 return GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1); 1359 1360 // The position to drag to corresponds to the active tab. If the next/previous 1361 // tab is stacked, then shorten the distance used to determine insertion 1362 // bounds. We do this as GetInsertionIndexFrom() uses the bounds of the 1363 // tabs. When tabs are stacked the next/previous tab is on top of the tab. 1364 if (active_index + 1 < attached_tabstrip_->tab_count() && 1365 touch_layout->IsStacked(active_index + 1)) { 1366 index = GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1); 1367 if (index == -1 && ShouldDragToNextStackedTab(dragged_bounds, active_index)) 1368 index = active_index + 1; 1369 else if (index == -1) 1370 index = active_index; 1371 } else if (ShouldDragToPreviousStackedTab(dragged_bounds, active_index)) { 1372 index = active_index - 1; 1373 } 1374 return index; 1375} 1376 1377gfx::Rect TabDragController::GetDraggedViewTabStripBounds( 1378 const gfx::Point& tab_strip_point) { 1379 // attached_tab is NULL when inserting into a new tabstrip. 1380 if (source_tab_drag_data()->attached_tab) { 1381 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1382 source_tab_drag_data()->attached_tab->width(), 1383 source_tab_drag_data()->attached_tab->height()); 1384 } 1385 1386 double sel_width, unselected_width; 1387 attached_tabstrip_->GetCurrentTabWidths(&sel_width, &unselected_width); 1388 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1389 static_cast<int>(sel_width), 1390 Tab::GetStandardSize().height()); 1391} 1392 1393gfx::Point TabDragController::GetAttachedDragPoint( 1394 const gfx::Point& point_in_screen) { 1395 DCHECK(attached_tabstrip_); // The tab must be attached. 1396 1397 gfx::Point tab_loc(point_in_screen); 1398 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_loc); 1399 const int x = 1400 attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x(); 1401 1402 // TODO: consider caching this. 1403 std::vector<Tab*> attached_tabs; 1404 for (size_t i = 0; i < drag_data_.size(); ++i) 1405 attached_tabs.push_back(drag_data_[i].attached_tab); 1406 const int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs); 1407 const int max_x = attached_tabstrip_->width() - size; 1408 return gfx::Point(std::min(std::max(x, 0), max_x), 0); 1409} 1410 1411std::vector<Tab*> TabDragController::GetTabsMatchingDraggedContents( 1412 TabStrip* tabstrip) { 1413 TabStripModel* model = GetModel(attached_tabstrip_); 1414 std::vector<Tab*> tabs; 1415 for (size_t i = 0; i < drag_data_.size(); ++i) { 1416 int model_index = model->GetIndexOfWebContents(drag_data_[i].contents); 1417 if (model_index == TabStripModel::kNoTab) 1418 return std::vector<Tab*>(); 1419 tabs.push_back(tabstrip->tab_at(model_index)); 1420 } 1421 return tabs; 1422} 1423 1424std::vector<gfx::Rect> TabDragController::CalculateBoundsForDraggedTabs() { 1425 std::vector<gfx::Rect> drag_bounds; 1426 std::vector<Tab*> attached_tabs; 1427 for (size_t i = 0; i < drag_data_.size(); ++i) 1428 attached_tabs.push_back(drag_data_[i].attached_tab); 1429 attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs, 1430 &drag_bounds); 1431 return drag_bounds; 1432} 1433 1434void TabDragController::EndDragImpl(EndDragType type) { 1435 DCHECK(active_); 1436 active_ = false; 1437 1438 bring_to_front_timer_.Stop(); 1439 move_stacked_timer_.Stop(); 1440 1441 if (is_dragging_window_) { 1442 waiting_for_run_loop_to_exit_ = true; 1443 1444 if (type == NORMAL || (type == TAB_DESTROYED && drag_data_.size() > 1)) { 1445 SetWindowPositionManaged(GetAttachedBrowserWidget()->GetNativeView(), 1446 true); 1447 } 1448 1449 // End the nested drag loop. 1450 GetAttachedBrowserWidget()->EndMoveLoop(); 1451 } 1452 1453 if (type != TAB_DESTROYED) { 1454 // We only finish up the drag if we were actually dragging. If start_drag_ 1455 // is false, the user just clicked and released and didn't move the mouse 1456 // enough to trigger a drag. 1457 if (started_drag_) { 1458 RestoreFocus(); 1459 if (type == CANCELED) 1460 RevertDrag(); 1461 else 1462 CompleteDrag(); 1463 } 1464 } else if (drag_data_.size() > 1) { 1465 initial_selection_model_.Clear(); 1466 RevertDrag(); 1467 } // else case the only tab we were dragging was deleted. Nothing to do. 1468 1469 if (!detach_into_browser_) 1470 ResetDelegates(); 1471 1472 // Clear out drag data so we don't attempt to do anything with it. 1473 drag_data_.clear(); 1474 1475 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? 1476 attached_tabstrip_ : source_tabstrip_; 1477 owning_tabstrip->DestroyDragController(); 1478} 1479 1480void TabDragController::RevertDrag() { 1481 std::vector<Tab*> tabs; 1482 for (size_t i = 0; i < drag_data_.size(); ++i) { 1483 if (drag_data_[i].contents) { 1484 // Contents is NULL if a tab was destroyed while the drag was under way. 1485 tabs.push_back(drag_data_[i].attached_tab); 1486 RevertDragAt(i); 1487 } 1488 } 1489 1490 bool restore_frame = !detach_into_browser_ && 1491 attached_tabstrip_ != source_tabstrip_; 1492 if (attached_tabstrip_) { 1493 if (did_restore_window_) 1494 MaximizeAttachedWindow(); 1495 if (attached_tabstrip_ == source_tabstrip_) { 1496 source_tabstrip_->StoppedDraggingTabs( 1497 tabs, initial_tab_positions_, move_behavior_ == MOVE_VISIBILE_TABS, 1498 false); 1499 } else { 1500 attached_tabstrip_->DraggedTabsDetached(); 1501 } 1502 } 1503 1504 if (initial_selection_model_.empty()) 1505 ResetSelection(GetModel(source_tabstrip_)); 1506 else 1507 GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_); 1508 1509 // If we're not attached to any TabStrip, or attached to some other TabStrip, 1510 // we need to restore the bounds of the original TabStrip's frame, in case 1511 // it has been hidden. 1512 if (restore_frame && !restore_bounds_.IsEmpty()) 1513 source_tabstrip_->GetWidget()->SetBounds(restore_bounds_); 1514 1515 if (detach_into_browser_ && source_tabstrip_) 1516 source_tabstrip_->GetWidget()->Activate(); 1517 1518 // Return the WebContents to normalcy. If the tab was attached to a 1519 // TabStrip before the revert, the decrement has already occurred. 1520 // If the tab was destroyed, don't attempt to dereference the 1521 // WebContents pointer. 1522 if (!detach_into_browser_ && !attached_tabstrip_ && source_dragged_contents()) 1523 source_dragged_contents()->DecrementCapturerCount(); 1524} 1525 1526void TabDragController::ResetSelection(TabStripModel* model) { 1527 DCHECK(model); 1528 ui::ListSelectionModel selection_model; 1529 bool has_one_valid_tab = false; 1530 for (size_t i = 0; i < drag_data_.size(); ++i) { 1531 // |contents| is NULL if a tab was deleted out from under us. 1532 if (drag_data_[i].contents) { 1533 int index = model->GetIndexOfWebContents(drag_data_[i].contents); 1534 DCHECK_NE(-1, index); 1535 selection_model.AddIndexToSelection(index); 1536 if (!has_one_valid_tab || i == source_tab_index_) { 1537 // Reset the active/lead to the first tab. If the source tab is still 1538 // valid we'll reset these again later on. 1539 selection_model.set_active(index); 1540 selection_model.set_anchor(index); 1541 has_one_valid_tab = true; 1542 } 1543 } 1544 } 1545 if (!has_one_valid_tab) 1546 return; 1547 1548 model->SetSelectionFromModel(selection_model); 1549} 1550 1551void TabDragController::RestoreInitialSelection() { 1552 // First time detaching from the source tabstrip. Reset selection model to 1553 // initial_selection_model_. Before resetting though we have to remove all 1554 // the tabs from initial_selection_model_ as it was created with the tabs 1555 // still there. 1556 ui::ListSelectionModel selection_model; 1557 selection_model.Copy(initial_selection_model_); 1558 for (DragData::const_reverse_iterator i(drag_data_.rbegin()); 1559 i != drag_data_.rend(); ++i) { 1560 selection_model.DecrementFrom(i->source_model_index); 1561 } 1562 // We may have cleared out the selection model. Only reset it if it 1563 // contains something. 1564 if (selection_model.empty()) 1565 return; 1566 1567 // The anchor/active may have been among the tabs that were dragged out. Force 1568 // the anchor/active to be valid. 1569 if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex) 1570 selection_model.set_anchor(selection_model.selected_indices()[0]); 1571 if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex) 1572 selection_model.set_active(selection_model.selected_indices()[0]); 1573 GetModel(source_tabstrip_)->SetSelectionFromModel(selection_model); 1574} 1575 1576void TabDragController::RevertDragAt(size_t drag_index) { 1577 DCHECK(started_drag_); 1578 DCHECK(source_tabstrip_); 1579 1580 base::AutoReset<bool> setter(&is_mutating_, true); 1581 TabDragData* data = &(drag_data_[drag_index]); 1582 if (attached_tabstrip_) { 1583 int index = 1584 GetModel(attached_tabstrip_)->GetIndexOfWebContents(data->contents); 1585 if (attached_tabstrip_ != source_tabstrip_) { 1586 // The Tab was inserted into another TabStrip. We need to put it back 1587 // into the original one. 1588 GetModel(attached_tabstrip_)->DetachWebContentsAt(index); 1589 // TODO(beng): (Cleanup) seems like we should use Attach() for this 1590 // somehow. 1591 GetModel(source_tabstrip_)->InsertWebContentsAt( 1592 data->source_model_index, data->contents, 1593 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1594 } else { 1595 // The Tab was moved within the TabStrip where the drag was initiated. 1596 // Move it back to the starting location. 1597 GetModel(source_tabstrip_)->MoveWebContentsAt( 1598 index, data->source_model_index, false); 1599 } 1600 } else { 1601 // The Tab was detached from the TabStrip where the drag began, and has not 1602 // been attached to any other TabStrip. We need to put it back into the 1603 // source TabStrip. 1604 GetModel(source_tabstrip_)->InsertWebContentsAt( 1605 data->source_model_index, data->contents, 1606 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1607 } 1608} 1609 1610void TabDragController::CompleteDrag() { 1611 DCHECK(started_drag_); 1612 1613 if (attached_tabstrip_) { 1614 if (is_dragging_new_browser_ || did_restore_window_) { 1615 if (IsDockedOrSnapped(attached_tabstrip_)) { 1616 was_source_maximized_ = false; 1617 was_source_fullscreen_ = false; 1618 } 1619 1620 // If source window was maximized - maximize the new window as well. 1621 if (was_source_maximized_ || was_source_fullscreen_) 1622 MaximizeAttachedWindow(); 1623 } 1624 attached_tabstrip_->StoppedDraggingTabs( 1625 GetTabsMatchingDraggedContents(attached_tabstrip_), 1626 initial_tab_positions_, 1627 move_behavior_ == MOVE_VISIBILE_TABS, 1628 true); 1629 } else { 1630 // Compel the model to construct a new window for the detached 1631 // WebContentses. 1632 views::Widget* widget = source_tabstrip_->GetWidget(); 1633 gfx::Rect window_bounds(widget->GetRestoredBounds()); 1634 window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_)); 1635 1636 base::AutoReset<bool> setter(&is_mutating_, true); 1637 1638 std::vector<TabStripModelDelegate::NewStripContents> contentses; 1639 for (size_t i = 0; i < drag_data_.size(); ++i) { 1640 TabStripModelDelegate::NewStripContents item; 1641 item.web_contents = drag_data_[i].contents; 1642 item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED 1643 : TabStripModel::ADD_NONE; 1644 contentses.push_back(item); 1645 } 1646 1647 Browser* new_browser = 1648 GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents( 1649 contentses, window_bounds, widget->IsMaximized()); 1650 ResetSelection(new_browser->tab_strip_model()); 1651 new_browser->window()->Show(); 1652 1653 // Return the WebContents to normalcy. 1654 if (!detach_into_browser_) 1655 source_dragged_contents()->DecrementCapturerCount(); 1656 } 1657 1658 CleanUpHiddenFrame(); 1659} 1660 1661void TabDragController::MaximizeAttachedWindow() { 1662 GetAttachedBrowserWidget()->Maximize(); 1663#if defined(USE_ASH) 1664 if (was_source_fullscreen_ && 1665 host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 1666 // In fullscreen mode it is only possible to get here if the source 1667 // was in "immersive fullscreen" mode, so toggle it back on. 1668 ash::accelerators::ToggleFullscreen(); 1669 } 1670#endif 1671} 1672 1673void TabDragController::ResetDelegates() { 1674 DCHECK(!detach_into_browser_); 1675 for (size_t i = 0; i < drag_data_.size(); ++i) { 1676 if (drag_data_[i].contents && 1677 drag_data_[i].contents->GetDelegate() == this) { 1678 drag_data_[i].contents->SetDelegate( 1679 drag_data_[i].original_delegate); 1680 } 1681 } 1682} 1683 1684gfx::Rect TabDragController::GetViewScreenBounds( 1685 views::View* view) const { 1686 gfx::Point view_topleft; 1687 views::View::ConvertPointToScreen(view, &view_topleft); 1688 gfx::Rect view_screen_bounds = view->GetLocalBounds(); 1689 view_screen_bounds.Offset(view_topleft.x(), view_topleft.y()); 1690 return view_screen_bounds; 1691} 1692 1693void TabDragController::CleanUpHiddenFrame() { 1694 // If the model we started dragging from is now empty, we must ask the 1695 // delegate to close the frame. 1696 if (!detach_into_browser_ && GetModel(source_tabstrip_)->empty()) 1697 GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession(); 1698} 1699 1700void TabDragController::BringWindowUnderPointToFront( 1701 const gfx::Point& point_in_screen) { 1702 aura::Window* window = GetLocalProcessWindow(point_in_screen, true); 1703 1704 // Only bring browser windows to front - only windows with a TabStrip can 1705 // be tab drag targets. 1706 if (!GetTabStripForWindow(window)) 1707 return; 1708 1709 if (window) { 1710 views::Widget* widget_window = views::Widget::GetWidgetForNativeView( 1711 window); 1712 if (!widget_window) 1713 return; 1714 1715#if defined(USE_ASH) 1716 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 1717 // TODO(varkha): The code below ensures that the phantom drag widget 1718 // is shown on top of browser windows. The code should be moved to ash/ 1719 // and the phantom should be able to assert its top-most state on its own. 1720 // One strategy would be for DragWindowController to 1721 // be able to observe stacking changes to the phantom drag widget's 1722 // siblings in order to keep it on top. One way is to implement a 1723 // notification that is sent to a window parent's observers when a 1724 // stacking order is changed among the children of that same parent. 1725 // Note that OnWindowStackingChanged is sent only to the child that is the 1726 // argument of one of the Window::StackChildX calls and not to all its 1727 // siblings affected by the stacking change. 1728 aura::Window* browser_window = widget_window->GetNativeView(); 1729 // Find a topmost non-popup window and stack the recipient browser above 1730 // it in order to avoid stacking the browser window on top of the phantom 1731 // drag widget created by DragWindowController in a second display. 1732 for (aura::Window::Windows::const_reverse_iterator it = 1733 browser_window->parent()->children().rbegin(); 1734 it != browser_window->parent()->children().rend(); ++it) { 1735 // If the iteration reached the recipient browser window then it is 1736 // already topmost and it is safe to return with no stacking change. 1737 if (*it == browser_window) 1738 return; 1739 if ((*it)->type() != ui::wm::WINDOW_TYPE_POPUP) { 1740 widget_window->StackAbove(*it); 1741 break; 1742 } 1743 } 1744 } else { 1745 widget_window->StackAtTop(); 1746 } 1747#else 1748 widget_window->StackAtTop(); 1749#endif 1750 1751 // The previous call made the window appear on top of the dragged window, 1752 // move the dragged window to the front. 1753 if (is_dragging_window_) 1754 attached_tabstrip_->GetWidget()->StackAtTop(); 1755 } 1756} 1757 1758TabStripModel* TabDragController::GetModel( 1759 TabStrip* tabstrip) const { 1760 return static_cast<BrowserTabStripController*>(tabstrip->controller())-> 1761 model(); 1762} 1763 1764views::Widget* TabDragController::GetAttachedBrowserWidget() { 1765 return attached_tabstrip_->GetWidget(); 1766} 1767 1768bool TabDragController::AreTabsConsecutive() { 1769 for (size_t i = 1; i < drag_data_.size(); ++i) { 1770 if (drag_data_[i - 1].source_model_index + 1 != 1771 drag_data_[i].source_model_index) { 1772 return false; 1773 } 1774 } 1775 return true; 1776} 1777 1778gfx::Rect TabDragController::CalculateDraggedBrowserBounds( 1779 TabStrip* source, 1780 const gfx::Point& point_in_screen, 1781 std::vector<gfx::Rect>* drag_bounds) { 1782 gfx::Point center(0, source->height() / 2); 1783 views::View::ConvertPointToWidget(source, ¢er); 1784 gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds()); 1785 if (source->GetWidget()->IsMaximized()) { 1786 // If the restore bounds is really small, we don't want to honor it 1787 // (dragging a really small window looks wrong), instead make sure the new 1788 // window is at least 50% the size of the old. 1789 const gfx::Size max_size( 1790 source->GetWidget()->GetWindowBoundsInScreen().size()); 1791 new_bounds.set_width( 1792 std::max(max_size.width() / 2, new_bounds.width())); 1793 new_bounds.set_height( 1794 std::max(max_size.height() / 2, new_bounds.height())); 1795 } 1796 new_bounds.set_y(point_in_screen.y() - center.y()); 1797 switch (GetDetachPosition(point_in_screen)) { 1798 case DETACH_BEFORE: 1799 new_bounds.set_x(point_in_screen.x() - center.x()); 1800 new_bounds.Offset(-mouse_offset_.x(), 0); 1801 break; 1802 case DETACH_AFTER: { 1803 gfx::Point right_edge(source->width(), 0); 1804 views::View::ConvertPointToWidget(source, &right_edge); 1805 new_bounds.set_x(point_in_screen.x() - right_edge.x()); 1806 new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0); 1807 OffsetX(-(*drag_bounds)[0].x(), drag_bounds); 1808 break; 1809 } 1810 default: 1811 break; // Nothing to do for DETACH_ABOVE_OR_BELOW. 1812 } 1813 1814 // To account for the extra vertical on restored windows that is absent on 1815 // maximized windows, add an additional vertical offset extracted from the tab 1816 // strip. 1817 if (source->GetWidget()->IsMaximized()) 1818 new_bounds.Offset(0, -source->button_v_offset()); 1819 return new_bounds; 1820} 1821 1822void TabDragController::AdjustBrowserAndTabBoundsForDrag( 1823 int last_tabstrip_width, 1824 const gfx::Point& point_in_screen, 1825 std::vector<gfx::Rect>* drag_bounds) { 1826 attached_tabstrip_->InvalidateLayout(); 1827 attached_tabstrip_->DoLayout(); 1828 const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width(); 1829 1830 // If the new tabstrip is smaller than the old resize the tabs. 1831 if (dragged_tabstrip_width < last_tabstrip_width) { 1832 const float leading_ratio = 1833 drag_bounds->front().x() / static_cast<float>(last_tabstrip_width); 1834 *drag_bounds = CalculateBoundsForDraggedTabs(); 1835 1836 if (drag_bounds->back().right() < dragged_tabstrip_width) { 1837 const int delta_x = 1838 std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width), 1839 dragged_tabstrip_width - 1840 (drag_bounds->back().right() - 1841 drag_bounds->front().x())); 1842 OffsetX(delta_x, drag_bounds); 1843 } 1844 1845 // Reposition the restored window such that the tab that was dragged remains 1846 // under the mouse cursor. 1847 gfx::Point offset( 1848 static_cast<int>((*drag_bounds)[source_tab_index_].width() * 1849 offset_to_width_ratio_) + 1850 (*drag_bounds)[source_tab_index_].x(), 0); 1851 views::View::ConvertPointToWidget(attached_tabstrip_, &offset); 1852 gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen(); 1853 bounds.set_x(point_in_screen.x() - offset.x()); 1854 GetAttachedBrowserWidget()->SetBounds(bounds); 1855 } 1856 attached_tabstrip_->SetTabBoundsForDrag(*drag_bounds); 1857} 1858 1859Browser* TabDragController::CreateBrowserForDrag( 1860 TabStrip* source, 1861 const gfx::Point& point_in_screen, 1862 gfx::Vector2d* drag_offset, 1863 std::vector<gfx::Rect>* drag_bounds) { 1864 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source, 1865 point_in_screen, 1866 drag_bounds)); 1867 *drag_offset = point_in_screen - new_bounds.origin(); 1868 1869 Profile* profile = 1870 Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext()); 1871 Browser::CreateParams create_params(Browser::TYPE_TABBED, 1872 profile, 1873 host_desktop_type_); 1874 create_params.initial_bounds = new_bounds; 1875 Browser* browser = new Browser(create_params); 1876 is_dragging_new_browser_ = true; 1877 SetWindowPositionManaged(browser->window()->GetNativeWindow(), false); 1878 // If the window is created maximized then the bounds we supplied are ignored. 1879 // We need to reset them again so they are honored. 1880 browser->window()->SetBounds(new_bounds); 1881 1882 return browser; 1883} 1884 1885gfx::Point TabDragController::GetCursorScreenPoint() { 1886#if defined(USE_ASH) 1887 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH && 1888 event_source_ == EVENT_SOURCE_TOUCH && 1889 aura::Env::GetInstance()->is_touch_down()) { 1890 views::Widget* widget = GetAttachedBrowserWidget(); 1891 DCHECK(widget); 1892 aura::Window* widget_window = widget->GetNativeWindow(); 1893 DCHECK(widget_window->GetRootWindow()); 1894 gfx::PointF touch_point_f; 1895 bool got_touch_point = ui::GestureRecognizer::Get()-> 1896 GetLastTouchPointForTarget(widget_window, &touch_point_f); 1897 // TODO(tdresser): Switch to using gfx::PointF. See crbug.com/337824. 1898 gfx::Point touch_point = gfx::ToFlooredPoint(touch_point_f); 1899 DCHECK(got_touch_point); 1900 ash::wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point); 1901 return touch_point; 1902 } 1903#endif 1904 return screen_->GetCursorScreenPoint(); 1905} 1906 1907gfx::Vector2d TabDragController::GetWindowOffset( 1908 const gfx::Point& point_in_screen) { 1909 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? 1910 attached_tabstrip_ : source_tabstrip_; 1911 views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView(); 1912 1913 gfx::Point point = point_in_screen; 1914 views::View::ConvertPointFromScreen(toplevel_view, &point); 1915 return point.OffsetFromOrigin(); 1916} 1917 1918gfx::NativeWindow TabDragController::GetLocalProcessWindow( 1919 const gfx::Point& screen_point, 1920 bool exclude_dragged_view) { 1921 std::set<aura::Window*> exclude; 1922 if (exclude_dragged_view) { 1923 aura::Window* dragged_window = 1924 attached_tabstrip_->GetWidget()->GetNativeView(); 1925 if (dragged_window) 1926 exclude.insert(dragged_window); 1927 } 1928#if defined(OS_LINUX) && !defined(OS_CHROMEOS) 1929 // Exclude windows which are pending deletion via Browser::TabStripEmpty(). 1930 // These windows can be returned in the Linux Aura port because the browser 1931 // window which was used for dragging is not hidden once all of its tabs are 1932 // attached to another browser window in DragBrowserToNewTabStrip(). 1933 // TODO(pkotwicz): Fix this properly (crbug.com/358482) 1934 BrowserList* browser_list = BrowserList::GetInstance( 1935 chrome::HOST_DESKTOP_TYPE_NATIVE); 1936 for (BrowserList::const_iterator it = browser_list->begin(); 1937 it != browser_list->end(); ++it) { 1938 if ((*it)->tab_strip_model()->empty()) 1939 exclude.insert((*it)->window()->GetNativeWindow()); 1940 } 1941#endif 1942 return GetLocalProcessWindowAtPoint(host_desktop_type_, 1943 screen_point, 1944 exclude); 1945 1946} 1947