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