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