1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h" 6 7 #include <math.h> 8 #include <set> 9 10 #include "base/auto_reset.h" 11 #include "base/callback.h" 12 #include "base/command_line.h" 13 #include "base/i18n/rtl.h" 14 #include "chrome/browser/chrome_notification_types.h" 15 #include "chrome/browser/extensions/extension_function_dispatcher.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h" 18 #include "chrome/browser/ui/browser_window.h" 19 #include "chrome/browser/ui/media_utils.h" 20 #include "chrome/browser/ui/tabs/tab_strip_model.h" 21 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" 22 #include "chrome/browser/ui/views/frame/browser_view.h" 23 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" 24 #include "chrome/browser/ui/views/tabs/dragged_tab_view.h" 25 #include "chrome/browser/ui/views/tabs/native_view_photobooth.h" 26 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h" 27 #include "chrome/browser/ui/views/tabs/tab.h" 28 #include "chrome/browser/ui/views/tabs/tab_strip.h" 29 #include "chrome/common/chrome_switches.h" 30 #include "content/public/browser/invalidate_type.h" 31 #include "content/public/browser/notification_details.h" 32 #include "content/public/browser/notification_service.h" 33 #include "content/public/browser/notification_source.h" 34 #include "content/public/browser/notification_types.h" 35 #include "content/public/browser/user_metrics.h" 36 #include "content/public/browser/web_contents.h" 37 #include "content/public/browser/web_contents_view.h" 38 #include "grit/theme_resources.h" 39 #include "ui/base/resource/resource_bundle.h" 40 #include "ui/events/event_constants.h" 41 #include "ui/events/event_utils.h" 42 #include "ui/gfx/animation/animation.h" 43 #include "ui/gfx/animation/animation_delegate.h" 44 #include "ui/gfx/animation/slide_animation.h" 45 #include "ui/gfx/canvas.h" 46 #include "ui/gfx/image/image_skia.h" 47 #include "ui/gfx/screen.h" 48 #include "ui/views/focus/view_storage.h" 49 #include "ui/views/widget/root_view.h" 50 #include "ui/views/widget/widget.h" 51 52 #if defined(USE_ASH) 53 #include "ash/accelerators/accelerator_commands.h" 54 #include "ash/wm/coordinate_conversion.h" 55 #include "ash/wm/window_state.h" 56 #include "ui/aura/env.h" 57 #include "ui/aura/root_window.h" 58 #include "ui/aura/window.h" 59 #include "ui/events/gestures/gesture_recognizer.h" 60 #endif 61 62 #if defined(OS_WIN) && defined(USE_AURA) 63 #include "ui/aura/window.h" 64 #include "ui/events/gestures/gesture_recognizer.h" 65 #endif 66 67 using content::OpenURLParams; 68 using content::UserMetricsAction; 69 using content::WebContents; 70 71 static const int kHorizontalMoveThreshold = 16; // Pixels. 72 73 // Distance from the next/previous stacked before before we consider the tab 74 // close enough to trigger moving. 75 static const int kStackedDistance = 36; 76 77 // If non-null there is a drag underway. 78 static TabDragController* instance_ = NULL; 79 80 namespace { 81 82 // Delay, in ms, during dragging before we bring a window to front. 83 const int kBringToFrontDelay = 750; 84 85 // Initial delay before moving tabs when the dragged tab is close to the edge of 86 // the stacked tabs. 87 const int kMoveAttachedInitialDelay = 600; 88 89 // Delay for moving tabs after the initial delay has passed. 90 const int kMoveAttachedSubsequentDelay = 300; 91 92 // Radius of the rect drawn by DockView. 93 const int kRoundedRectRadius = 4; 94 95 // Spacing between tab icons when DockView is showing a docking location that 96 // contains more than one tab. 97 const int kTabSpacing = 4; 98 99 // DockView is the view responsible for giving a visual indicator of where a 100 // dock is going to occur. 101 102 class DockView : public views::View { 103 public: 104 explicit DockView(DockInfo::Type type) : type_(type) {} 105 106 virtual gfx::Size GetPreferredSize() OVERRIDE { 107 return gfx::Size(DockInfo::popup_width(), DockInfo::popup_height()); 108 } 109 110 virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE { 111 // Fill the background rect. 112 SkPaint paint; 113 paint.setColor(SkColorSetRGB(108, 108, 108)); 114 paint.setStyle(SkPaint::kFill_Style); 115 canvas->DrawRoundRect(GetLocalBounds(), kRoundedRectRadius, paint); 116 117 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 118 119 gfx::ImageSkia* high_icon = rb.GetImageSkiaNamed(IDR_DOCK_HIGH); 120 gfx::ImageSkia* wide_icon = rb.GetImageSkiaNamed(IDR_DOCK_WIDE); 121 122 canvas->Save(); 123 bool rtl_ui = base::i18n::IsRTL(); 124 if (rtl_ui) { 125 // Flip canvas to draw the mirrored tab images for RTL UI. 126 canvas->Translate(gfx::Vector2d(width(), 0)); 127 canvas->Scale(-1, 1); 128 } 129 int x_of_active_tab = width() / 2 + kTabSpacing / 2; 130 int x_of_inactive_tab = width() / 2 - high_icon->width() - kTabSpacing / 2; 131 switch (type_) { 132 case DockInfo::LEFT_OF_WINDOW: 133 case DockInfo::LEFT_HALF: 134 if (!rtl_ui) 135 std::swap(x_of_active_tab, x_of_inactive_tab); 136 canvas->DrawImageInt(*high_icon, x_of_active_tab, 137 (height() - high_icon->height()) / 2); 138 if (type_ == DockInfo::LEFT_OF_WINDOW) { 139 DrawImageWithAlpha(canvas, *high_icon, x_of_inactive_tab, 140 (height() - high_icon->height()) / 2); 141 } 142 break; 143 144 145 case DockInfo::RIGHT_OF_WINDOW: 146 case DockInfo::RIGHT_HALF: 147 if (rtl_ui) 148 std::swap(x_of_active_tab, x_of_inactive_tab); 149 canvas->DrawImageInt(*high_icon, x_of_active_tab, 150 (height() - high_icon->height()) / 2); 151 if (type_ == DockInfo::RIGHT_OF_WINDOW) { 152 DrawImageWithAlpha(canvas, *high_icon, x_of_inactive_tab, 153 (height() - high_icon->height()) / 2); 154 } 155 break; 156 157 case DockInfo::TOP_OF_WINDOW: 158 canvas->DrawImageInt(*wide_icon, (width() - wide_icon->width()) / 2, 159 height() / 2 - high_icon->height()); 160 break; 161 162 case DockInfo::MAXIMIZE: { 163 gfx::ImageSkia* max_icon = rb.GetImageSkiaNamed(IDR_DOCK_MAX); 164 canvas->DrawImageInt(*max_icon, (width() - max_icon->width()) / 2, 165 (height() - max_icon->height()) / 2); 166 break; 167 } 168 169 case DockInfo::BOTTOM_HALF: 170 case DockInfo::BOTTOM_OF_WINDOW: 171 canvas->DrawImageInt(*wide_icon, (width() - wide_icon->width()) / 2, 172 height() / 2 + kTabSpacing / 2); 173 if (type_ == DockInfo::BOTTOM_OF_WINDOW) { 174 DrawImageWithAlpha(canvas, *wide_icon, 175 (width() - wide_icon->width()) / 2, 176 height() / 2 - kTabSpacing / 2 - wide_icon->height()); 177 } 178 break; 179 180 default: 181 NOTREACHED(); 182 break; 183 } 184 canvas->Restore(); 185 } 186 187 private: 188 void DrawImageWithAlpha(gfx::Canvas* canvas, const gfx::ImageSkia& image, 189 int x, int y) { 190 SkPaint paint; 191 paint.setAlpha(128); 192 canvas->DrawImageInt(image, x, y, paint); 193 } 194 195 DockInfo::Type type_; 196 197 DISALLOW_COPY_AND_ASSIGN(DockView); 198 }; 199 200 void SetWindowPositionManaged(gfx::NativeWindow window, bool value) { 201 #if defined(USE_ASH) 202 ash::wm::GetWindowState(window)->set_window_position_managed(value); 203 #endif 204 } 205 206 // Returns true if |tab_strip| browser window is docked. 207 bool IsDockedOrSnapped(const TabStrip* tab_strip) { 208 #if defined(USE_ASH) 209 DCHECK(tab_strip); 210 ash::wm::WindowState* window_state = 211 ash::wm::GetWindowState(tab_strip->GetWidget()->GetNativeWindow()); 212 return window_state->IsDocked() || 213 window_state->window_show_type() == ash::wm::SHOW_TYPE_LEFT_SNAPPED || 214 window_state->window_show_type() == ash::wm::SHOW_TYPE_RIGHT_SNAPPED; 215 #endif 216 return false; 217 } 218 219 // Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate 220 // of |bounds| is adjusted by |vertical_adjustment|. 221 bool DoesRectContainVerticalPointExpanded( 222 const gfx::Rect& bounds, 223 int vertical_adjustment, 224 int y) { 225 int upper_threshold = bounds.bottom() + vertical_adjustment; 226 int lower_threshold = bounds.y() - vertical_adjustment; 227 return y >= lower_threshold && y <= upper_threshold; 228 } 229 230 // Adds |x_offset| to all the rectangles in |rects|. 231 void OffsetX(int x_offset, std::vector<gfx::Rect>* rects) { 232 if (x_offset == 0) 233 return; 234 235 for (size_t i = 0; i < rects->size(); ++i) 236 (*rects)[i].set_x((*rects)[i].x() + x_offset); 237 } 238 239 // WidgetObserver implementation that resets the window position managed 240 // property on Show. 241 // We're forced to do this here since BrowserFrameAsh resets the 'window 242 // position managed' property during a show and we need the property set to 243 // false before WorkspaceLayoutManager sees the visibility change. 244 class WindowPositionManagedUpdater : public views::WidgetObserver { 245 public: 246 virtual void OnWidgetVisibilityChanged(views::Widget* widget, 247 bool visible) OVERRIDE { 248 SetWindowPositionManaged(widget->GetNativeView(), false); 249 } 250 }; 251 252 } // namespace 253 254 /////////////////////////////////////////////////////////////////////////////// 255 // DockDisplayer 256 257 // DockDisplayer is responsible for giving the user a visual indication of a 258 // possible dock position (as represented by DockInfo). DockDisplayer shows 259 // a window with a DockView in it. Two animations are used that correspond to 260 // the state of DockInfo::in_enable_area. 261 class TabDragController::DockDisplayer : public gfx::AnimationDelegate { 262 public: 263 DockDisplayer(TabDragController* controller, const DockInfo& info) 264 : controller_(controller), 265 popup_(NULL), 266 popup_view_(NULL), 267 animation_(this), 268 hidden_(false), 269 in_enable_area_(info.in_enable_area()) { 270 popup_ = new views::Widget; 271 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 272 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; 273 params.keep_on_top = true; 274 params.bounds = info.GetPopupRect(); 275 popup_->Init(params); 276 popup_->SetContentsView(new DockView(info.type())); 277 popup_->SetOpacity(0x00); 278 if (info.in_enable_area()) 279 animation_.Reset(1); 280 else 281 animation_.Show(); 282 popup_->Show(); 283 popup_view_ = popup_->GetNativeView(); 284 } 285 286 virtual ~DockDisplayer() { 287 if (controller_) 288 controller_->DockDisplayerDestroyed(this); 289 } 290 291 // Updates the state based on |in_enable_area|. 292 void UpdateInEnabledArea(bool in_enable_area) { 293 if (in_enable_area != in_enable_area_) { 294 in_enable_area_ = in_enable_area; 295 UpdateLayeredAlpha(); 296 } 297 } 298 299 // Resets the reference to the hosting TabDragController. This is 300 // invoked when the TabDragController is destroyed. 301 void clear_controller() { controller_ = NULL; } 302 303 // NativeView of the window we create. 304 gfx::NativeView popup_view() { return popup_view_; } 305 306 // Starts the hide animation. When the window is closed the 307 // TabDragController is notified by way of the DockDisplayerDestroyed 308 // method 309 void Hide() { 310 if (hidden_) 311 return; 312 313 if (!popup_) { 314 delete this; 315 return; 316 } 317 hidden_ = true; 318 animation_.Hide(); 319 } 320 321 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { 322 UpdateLayeredAlpha(); 323 } 324 325 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE { 326 if (!hidden_) 327 return; 328 popup_->Close(); 329 delete this; 330 } 331 332 private: 333 void UpdateLayeredAlpha() { 334 double scale = in_enable_area_ ? 1 : .5; 335 popup_->SetOpacity(static_cast<unsigned char>(animation_.GetCurrentValue() * 336 scale * 255.0)); 337 } 338 339 // TabDragController that created us. 340 TabDragController* controller_; 341 342 // Window we're showing. 343 views::Widget* popup_; 344 345 // NativeView of |popup_|. We cache this to avoid the possibility of 346 // invoking a method on popup_ after we close it. 347 gfx::NativeView popup_view_; 348 349 // Animation for when first made visible. 350 gfx::SlideAnimation animation_; 351 352 // Have we been hidden? 353 bool hidden_; 354 355 // Value of DockInfo::in_enable_area. 356 bool in_enable_area_; 357 }; 358 359 TabDragController::TabDragData::TabDragData() 360 : contents(NULL), 361 original_delegate(NULL), 362 source_model_index(-1), 363 attached_tab(NULL), 364 pinned(false) { 365 } 366 367 TabDragController::TabDragData::~TabDragData() { 368 } 369 370 /////////////////////////////////////////////////////////////////////////////// 371 // TabDragController, public: 372 373 // static 374 const int TabDragController::kTouchVerticalDetachMagnetism = 50; 375 376 // static 377 const int TabDragController::kVerticalDetachMagnetism = 15; 378 379 TabDragController::TabDragController() 380 : detach_into_browser_(ShouldDetachIntoNewBrowser()), 381 event_source_(EVENT_SOURCE_MOUSE), 382 source_tabstrip_(NULL), 383 attached_tabstrip_(NULL), 384 screen_(NULL), 385 host_desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE), 386 offset_to_width_ratio_(0), 387 old_focused_view_id_( 388 views::ViewStorage::GetInstance()->CreateStorageID()), 389 last_move_screen_loc_(0), 390 started_drag_(false), 391 active_(true), 392 source_tab_index_(std::numeric_limits<size_t>::max()), 393 initial_move_(true), 394 detach_behavior_(DETACHABLE), 395 move_behavior_(REORDER), 396 mouse_move_direction_(0), 397 is_dragging_window_(false), 398 is_dragging_new_browser_(false), 399 was_source_maximized_(false), 400 was_source_fullscreen_(false), 401 end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING), 402 waiting_for_run_loop_to_exit_(false), 403 tab_strip_to_attach_to_after_exit_(NULL), 404 move_loop_widget_(NULL), 405 is_mutating_(false), 406 attach_x_(-1), 407 attach_index_(-1), 408 weak_factory_(this) { 409 instance_ = this; 410 } 411 412 TabDragController::~TabDragController() { 413 views::ViewStorage::GetInstance()->RemoveView(old_focused_view_id_); 414 415 if (instance_ == this) 416 instance_ = NULL; 417 418 if (move_loop_widget_) { 419 move_loop_widget_->RemoveObserver(this); 420 SetWindowPositionManaged(move_loop_widget_->GetNativeView(), true); 421 } 422 423 if (source_tabstrip_ && detach_into_browser_) 424 GetModel(source_tabstrip_)->RemoveObserver(this); 425 426 base::MessageLoopForUI::current()->RemoveObserver(this); 427 428 // Need to delete the view here manually _before_ we reset the dragged 429 // contents to NULL, otherwise if the view is animating to its destination 430 // bounds, it won't be able to clean up properly since its cleanup routine 431 // uses GetIndexForDraggedContents, which will be invalid. 432 view_.reset(NULL); 433 434 // Reset the delegate of the dragged WebContents. This ends up doing nothing 435 // if the drag was completed. 436 if (!detach_into_browser_) 437 ResetDelegates(); 438 } 439 440 void TabDragController::Init( 441 TabStrip* source_tabstrip, 442 Tab* source_tab, 443 const std::vector<Tab*>& tabs, 444 const gfx::Point& mouse_offset, 445 int source_tab_offset, 446 const ui::ListSelectionModel& initial_selection_model, 447 DetachBehavior detach_behavior, 448 MoveBehavior move_behavior, 449 EventSource event_source) { 450 DCHECK(!tabs.empty()); 451 DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); 452 source_tabstrip_ = source_tabstrip; 453 was_source_maximized_ = source_tabstrip->GetWidget()->IsMaximized(); 454 was_source_fullscreen_ = source_tabstrip->GetWidget()->IsFullscreen(); 455 screen_ = gfx::Screen::GetScreenFor( 456 source_tabstrip->GetWidget()->GetNativeView()); 457 host_desktop_type_ = chrome::GetHostDesktopTypeForNativeView( 458 source_tabstrip->GetWidget()->GetNativeView()); 459 start_point_in_screen_ = gfx::Point(source_tab_offset, mouse_offset.y()); 460 views::View::ConvertPointToScreen(source_tab, &start_point_in_screen_); 461 event_source_ = event_source; 462 mouse_offset_ = mouse_offset; 463 detach_behavior_ = detach_behavior; 464 move_behavior_ = move_behavior; 465 last_point_in_screen_ = start_point_in_screen_; 466 last_move_screen_loc_ = start_point_in_screen_.x(); 467 initial_tab_positions_ = source_tabstrip->GetTabXCoordinates(); 468 if (detach_behavior == NOT_DETACHABLE) 469 detach_into_browser_ = false; 470 471 if (detach_into_browser_) 472 GetModel(source_tabstrip_)->AddObserver(this); 473 474 drag_data_.resize(tabs.size()); 475 for (size_t i = 0; i < tabs.size(); ++i) 476 InitTabDragData(tabs[i], &(drag_data_[i])); 477 source_tab_index_ = 478 std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin(); 479 480 // Listen for Esc key presses. 481 base::MessageLoopForUI::current()->AddObserver(this); 482 483 if (source_tab->width() > 0) { 484 offset_to_width_ratio_ = static_cast<float>( 485 source_tab->GetMirroredXInView(source_tab_offset)) / 486 static_cast<float>(source_tab->width()); 487 } 488 InitWindowCreatePoint(); 489 initial_selection_model_.Copy(initial_selection_model); 490 } 491 492 // static 493 bool TabDragController::IsAttachedTo(const TabStrip* tab_strip) { 494 return (instance_ && instance_->active() && 495 instance_->attached_tabstrip() == tab_strip); 496 } 497 498 // static 499 bool TabDragController::IsActive() { 500 return instance_ && instance_->active(); 501 } 502 503 // static 504 bool TabDragController::ShouldDetachIntoNewBrowser() { 505 #if defined(USE_AURA) 506 return true; 507 #else 508 return CommandLine::ForCurrentProcess()->HasSwitch( 509 switches::kTabBrowserDragging); 510 #endif 511 } 512 513 void TabDragController::SetMoveBehavior(MoveBehavior behavior) { 514 if (started_drag()) 515 return; 516 517 move_behavior_ = behavior; 518 } 519 520 void TabDragController::Drag(const gfx::Point& point_in_screen) { 521 TRACE_EVENT1("views", "TabDragController::Drag", 522 "point_in_screen", point_in_screen.ToString()); 523 524 bring_to_front_timer_.Stop(); 525 move_stacked_timer_.Stop(); 526 527 if (waiting_for_run_loop_to_exit_) 528 return; 529 530 if (!started_drag_) { 531 if (!CanStartDrag(point_in_screen)) 532 return; // User hasn't dragged far enough yet. 533 534 // On windows SaveFocus() may trigger a capture lost, which destroys us. 535 { 536 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 537 SaveFocus(); 538 if (!ref) 539 return; 540 } 541 started_drag_ = true; 542 Attach(source_tabstrip_, gfx::Point()); 543 if (detach_into_browser_ && static_cast<int>(drag_data_.size()) == 544 GetModel(source_tabstrip_)->count()) { 545 #if defined(USE_ASH) 546 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH && 547 (was_source_maximized_ || was_source_fullscreen_)) { 548 // When all tabs in a maximized browser are dragged the browser gets 549 // restored during the drag and maximized back when the drag ends. 550 views::Widget* widget = GetAttachedBrowserWidget(); 551 const int last_tabstrip_width = attached_tabstrip_->tab_area_width(); 552 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 553 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds); 554 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source_tabstrip_, 555 point_in_screen, 556 &drag_bounds)); 557 new_bounds.Offset(-widget->GetRestoredBounds().x() + 558 point_in_screen.x() - 559 mouse_offset_.x(), 0); 560 widget->SetVisibilityChangedAnimationsEnabled(false); 561 widget->Restore(); 562 widget->SetBounds(new_bounds); 563 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, 564 point_in_screen, 565 &drag_bounds); 566 widget->SetVisibilityChangedAnimationsEnabled(true); 567 is_dragging_new_browser_ = true; 568 } 569 #endif 570 RunMoveLoop(GetWindowOffset(point_in_screen)); 571 return; 572 } 573 } 574 575 ContinueDragging(point_in_screen); 576 } 577 578 void TabDragController::EndDrag(EndDragReason reason) { 579 TRACE_EVENT0("views", "TabDragController::EndDrag"); 580 581 // If we're dragging a window ignore capture lost since it'll ultimately 582 // trigger the move loop to end and we'll revert the drag when RunMoveLoop() 583 // finishes. 584 if (reason == END_DRAG_CAPTURE_LOST && is_dragging_window_) 585 return; 586 EndDragImpl(reason != END_DRAG_COMPLETE && source_tabstrip_ ? 587 CANCELED : NORMAL); 588 } 589 590 void TabDragController::InitTabDragData(Tab* tab, 591 TabDragData* drag_data) { 592 TRACE_EVENT0("views", "TabDragController::InitTabDragData"); 593 drag_data->source_model_index = 594 source_tabstrip_->GetModelIndexOfTab(tab); 595 drag_data->contents = GetModel(source_tabstrip_)->GetWebContentsAt( 596 drag_data->source_model_index); 597 drag_data->pinned = source_tabstrip_->IsTabPinned(tab); 598 registrar_.Add( 599 this, 600 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 601 content::Source<WebContents>(drag_data->contents)); 602 603 if (!detach_into_browser_) { 604 drag_data->original_delegate = drag_data->contents->GetDelegate(); 605 drag_data->contents->SetDelegate(this); 606 } 607 } 608 609 /////////////////////////////////////////////////////////////////////////////// 610 // TabDragController, PageNavigator implementation: 611 612 WebContents* TabDragController::OpenURLFromTab( 613 WebContents* source, 614 const OpenURLParams& params) { 615 if (source_tab_drag_data()->original_delegate) { 616 OpenURLParams forward_params = params; 617 if (params.disposition == CURRENT_TAB) 618 forward_params.disposition = NEW_WINDOW; 619 620 return source_tab_drag_data()->original_delegate->OpenURLFromTab( 621 source, forward_params); 622 } 623 return NULL; 624 } 625 626 /////////////////////////////////////////////////////////////////////////////// 627 // TabDragController, content::WebContentsDelegate implementation: 628 629 void TabDragController::NavigationStateChanged(const WebContents* source, 630 unsigned changed_flags) { 631 if (attached_tabstrip_ || 632 changed_flags == content::INVALIDATE_TYPE_PAGE_ACTIONS) { 633 for (size_t i = 0; i < drag_data_.size(); ++i) { 634 if (drag_data_[i].contents == source) { 635 // Pass the NavigationStateChanged call to the original delegate so 636 // that the title is updated. Do this only when we are attached as 637 // otherwise the Tab isn't in the TabStrip (except for page action 638 // updates). 639 drag_data_[i].original_delegate->NavigationStateChanged(source, 640 changed_flags); 641 break; 642 } 643 } 644 } 645 if (view_.get()) 646 view_->Update(); 647 } 648 649 void TabDragController::AddNewContents(WebContents* source, 650 WebContents* new_contents, 651 WindowOpenDisposition disposition, 652 const gfx::Rect& initial_pos, 653 bool user_gesture, 654 bool* was_blocked) { 655 DCHECK_NE(CURRENT_TAB, disposition); 656 657 // Theoretically could be called while dragging if the page tries to 658 // spawn a window. Route this message back to the browser in most cases. 659 if (source_tab_drag_data()->original_delegate) { 660 source_tab_drag_data()->original_delegate->AddNewContents( 661 source, new_contents, disposition, initial_pos, user_gesture, 662 was_blocked); 663 } 664 } 665 666 void TabDragController::LoadingStateChanged(WebContents* source) { 667 // It would be nice to respond to this message by changing the 668 // screen shot in the dragged tab. 669 if (view_.get()) 670 view_->Update(); 671 } 672 673 bool TabDragController::ShouldSuppressDialogs() { 674 // When a dialog is about to be shown we revert the drag. Otherwise a modal 675 // dialog might appear and attempt to parent itself to a hidden tabcontents. 676 EndDragImpl(CANCELED); 677 return false; 678 } 679 680 content::JavaScriptDialogManager* 681 TabDragController::GetJavaScriptDialogManager() { 682 return GetJavaScriptDialogManagerInstance(); 683 } 684 685 void TabDragController::RequestMediaAccessPermission( 686 content::WebContents* web_contents, 687 const content::MediaStreamRequest& request, 688 const content::MediaResponseCallback& callback) { 689 ::RequestMediaAccessPermission( 690 web_contents, 691 Profile::FromBrowserContext(web_contents->GetBrowserContext()), 692 request, 693 callback); 694 } 695 696 /////////////////////////////////////////////////////////////////////////////// 697 // TabDragController, content::NotificationObserver implementation: 698 699 void TabDragController::Observe( 700 int type, 701 const content::NotificationSource& source, 702 const content::NotificationDetails& details) { 703 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); 704 WebContents* destroyed_web_contents = 705 content::Source<WebContents>(source).ptr(); 706 for (size_t i = 0; i < drag_data_.size(); ++i) { 707 if (drag_data_[i].contents == destroyed_web_contents) { 708 // One of the tabs we're dragging has been destroyed. Cancel the drag. 709 if (destroyed_web_contents->GetDelegate() == this) 710 destroyed_web_contents->SetDelegate(NULL); 711 drag_data_[i].contents = NULL; 712 drag_data_[i].original_delegate = NULL; 713 EndDragImpl(TAB_DESTROYED); 714 return; 715 } 716 } 717 // If we get here it means we got notification for a tab we don't know about. 718 NOTREACHED(); 719 } 720 721 /////////////////////////////////////////////////////////////////////////////// 722 // TabDragController, MessageLoop::Observer implementation: 723 724 base::EventStatus TabDragController::WillProcessEvent( 725 const base::NativeEvent& event) { 726 return base::EVENT_CONTINUE; 727 } 728 729 void TabDragController::DidProcessEvent(const base::NativeEvent& event) { 730 // If the user presses ESC during a drag, we need to abort and revert things 731 // to the way they were. This is the most reliable way to do this since no 732 // single view or window reliably receives events throughout all the various 733 // kinds of tab dragging. 734 if (ui::EventTypeFromNative(event) == ui::ET_KEY_PRESSED && 735 ui::KeyboardCodeFromNative(event) == ui::VKEY_ESCAPE) { 736 EndDrag(END_DRAG_CANCEL); 737 } 738 } 739 740 void TabDragController::OnWidgetBoundsChanged(views::Widget* widget, 741 const gfx::Rect& new_bounds) { 742 TRACE_EVENT1("views", "TabDragController::OnWidgetBoundsChanged", 743 "new_bounds", new_bounds.ToString()); 744 745 Drag(GetCursorScreenPoint()); 746 } 747 748 void TabDragController::TabStripEmpty() { 749 DCHECK(detach_into_browser_); 750 GetModel(source_tabstrip_)->RemoveObserver(this); 751 // NULL out source_tabstrip_ so that we don't attempt to add back to it (in 752 // the case of a revert). 753 source_tabstrip_ = NULL; 754 } 755 756 /////////////////////////////////////////////////////////////////////////////// 757 // TabDragController, private: 758 759 void TabDragController::InitWindowCreatePoint() { 760 // window_create_point_ is only used in CompleteDrag() (through 761 // GetWindowCreatePoint() to get the start point of the docked window) when 762 // the attached_tabstrip_ is NULL and all the window's related bound 763 // information are obtained from source_tabstrip_. So, we need to get the 764 // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise, 765 // the window_create_point_ is not in the correct coordinate system. Please 766 // refer to http://crbug.com/6223 comment #15 for detailed information. 767 views::View* first_tab = source_tabstrip_->tab_at(0); 768 views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_); 769 window_create_point_ = first_source_tab_point_; 770 window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y()); 771 } 772 773 gfx::Point TabDragController::GetWindowCreatePoint( 774 const gfx::Point& origin) const { 775 if (dock_info_.type() != DockInfo::NONE && dock_info_.in_enable_area()) { 776 // If we're going to dock, we need to return the exact coordinate, 777 // otherwise we may attempt to maximize on the wrong monitor. 778 return origin; 779 } 780 781 // If the cursor is outside the monitor area, move it inside. For example, 782 // dropping a tab onto the task bar on Windows produces this situation. 783 gfx::Rect work_area = screen_->GetDisplayNearestPoint(origin).work_area(); 784 gfx::Point create_point(origin); 785 if (!work_area.IsEmpty()) { 786 if (create_point.x() < work_area.x()) 787 create_point.set_x(work_area.x()); 788 else if (create_point.x() > work_area.right()) 789 create_point.set_x(work_area.right()); 790 if (create_point.y() < work_area.y()) 791 create_point.set_y(work_area.y()); 792 else if (create_point.y() > work_area.bottom()) 793 create_point.set_y(work_area.bottom()); 794 } 795 return gfx::Point(create_point.x() - window_create_point_.x(), 796 create_point.y() - window_create_point_.y()); 797 } 798 799 void TabDragController::UpdateDockInfo(const gfx::Point& point_in_screen) { 800 TRACE_EVENT1("views", "TabDragController::UpdateDockInfo", 801 "point_in_screen", point_in_screen.ToString()); 802 803 // Update the DockInfo for the current mouse coordinates. 804 DockInfo dock_info = GetDockInfoAtPoint(point_in_screen); 805 if (!dock_info.equals(dock_info_)) { 806 // DockInfo for current position differs. 807 if (dock_info_.type() != DockInfo::NONE && 808 !dock_controllers_.empty()) { 809 // Hide old visual indicator. 810 dock_controllers_.back()->Hide(); 811 } 812 dock_info_ = dock_info; 813 if (dock_info_.type() != DockInfo::NONE) { 814 // Show new docking position. 815 DockDisplayer* controller = new DockDisplayer(this, dock_info_); 816 if (controller->popup_view()) { 817 dock_controllers_.push_back(controller); 818 dock_windows_.insert(controller->popup_view()); 819 } else { 820 delete controller; 821 } 822 } 823 } else if (dock_info_.type() != DockInfo::NONE && 824 !dock_controllers_.empty()) { 825 // Current dock position is the same as last, update the controller's 826 // in_enable_area state as it may have changed. 827 dock_controllers_.back()->UpdateInEnabledArea(dock_info_.in_enable_area()); 828 } 829 } 830 831 void TabDragController::SaveFocus() { 832 DCHECK(source_tabstrip_); 833 views::View* focused_view = 834 source_tabstrip_->GetFocusManager()->GetFocusedView(); 835 if (focused_view) 836 views::ViewStorage::GetInstance()->StoreView(old_focused_view_id_, 837 focused_view); 838 source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_); 839 // WARNING: we may have been deleted. 840 } 841 842 void TabDragController::RestoreFocus() { 843 if (attached_tabstrip_ != source_tabstrip_) { 844 if (is_dragging_new_browser_) { 845 content::WebContents* active_contents = source_dragged_contents(); 846 if (active_contents && !active_contents->FocusLocationBarByDefault()) 847 active_contents->GetView()->Focus(); 848 } 849 return; 850 } 851 views::View* old_focused_view = 852 views::ViewStorage::GetInstance()->RetrieveView( 853 old_focused_view_id_); 854 if (!old_focused_view) 855 return; 856 old_focused_view->GetFocusManager()->SetFocusedView(old_focused_view); 857 } 858 859 bool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const { 860 // Determine if the mouse has moved beyond a minimum elasticity distance in 861 // any direction from the starting point. 862 static const int kMinimumDragDistance = 10; 863 int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x()); 864 int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y()); 865 return sqrt(pow(static_cast<float>(x_offset), 2) + 866 pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance; 867 } 868 869 void TabDragController::ContinueDragging(const gfx::Point& point_in_screen) { 870 TRACE_EVENT1("views", "TabDragController::ContinueDragging", 871 "point_in_screen", point_in_screen.ToString()); 872 873 DCHECK(!detach_into_browser_ || attached_tabstrip_); 874 875 TabStrip* target_tabstrip = detach_behavior_ == DETACHABLE ? 876 GetTargetTabStripForPoint(point_in_screen) : source_tabstrip_; 877 bool tab_strip_changed = (target_tabstrip != attached_tabstrip_); 878 879 if (attached_tabstrip_) { 880 int move_delta = point_in_screen.x() - last_point_in_screen_.x(); 881 if (move_delta > 0) 882 mouse_move_direction_ |= kMovedMouseRight; 883 else if (move_delta < 0) 884 mouse_move_direction_ |= kMovedMouseLeft; 885 } 886 last_point_in_screen_ = point_in_screen; 887 888 if (tab_strip_changed) { 889 is_dragging_new_browser_ = false; 890 if (detach_into_browser_ && 891 DragBrowserToNewTabStrip(target_tabstrip, point_in_screen) == 892 DRAG_BROWSER_RESULT_STOP) { 893 return; 894 } else if (!detach_into_browser_) { 895 if (attached_tabstrip_) 896 Detach(RELEASE_CAPTURE); 897 if (target_tabstrip) 898 Attach(target_tabstrip, point_in_screen); 899 } 900 } 901 if (view_.get() || is_dragging_window_) { 902 static_cast<base::Timer*>(&bring_to_front_timer_)->Start(FROM_HERE, 903 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), 904 base::Bind(&TabDragController::BringWindowUnderPointToFront, 905 base::Unretained(this), point_in_screen)); 906 } 907 908 UpdateDockInfo(point_in_screen); 909 910 if (!is_dragging_window_) { 911 if (attached_tabstrip_) { 912 if (move_only()) { 913 DragActiveTabStacked(point_in_screen); 914 } else { 915 MoveAttached(point_in_screen); 916 if (tab_strip_changed) { 917 // Move the corresponding window to the front. We do this after the 918 // move as on windows activate triggers a synchronous paint. 919 attached_tabstrip_->GetWidget()->Activate(); 920 } 921 } 922 } else { 923 MoveDetached(point_in_screen); 924 } 925 } 926 } 927 928 TabDragController::DragBrowserResultType 929 TabDragController::DragBrowserToNewTabStrip( 930 TabStrip* target_tabstrip, 931 const gfx::Point& point_in_screen) { 932 TRACE_EVENT1("views", "TabDragController::DragBrowserToNewTabStrip", 933 "point_in_screen", point_in_screen.ToString()); 934 935 if (!target_tabstrip) { 936 DetachIntoNewBrowserAndRunMoveLoop(point_in_screen); 937 return DRAG_BROWSER_RESULT_STOP; 938 } 939 if (is_dragging_window_) { 940 // ReleaseCapture() is going to result in calling back to us (because it 941 // results in a move). That'll cause all sorts of problems. Reset the 942 // observer so we don't get notified and process the event. 943 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 944 move_loop_widget_->RemoveObserver(this); 945 move_loop_widget_ = NULL; 946 } 947 views::Widget* browser_widget = GetAttachedBrowserWidget(); 948 // Need to release the drag controller before starting the move loop as it's 949 // going to trigger capture lost, which cancels drag. 950 attached_tabstrip_->ReleaseDragController(); 951 target_tabstrip->OwnDragController(this); 952 // Disable animations so that we don't see a close animation on aero. 953 browser_widget->SetVisibilityChangedAnimationsEnabled(false); 954 // For aura we can't release capture, otherwise it'll cancel a gesture. 955 // Instead we have to directly change capture. 956 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) 957 target_tabstrip->GetWidget()->SetCapture(attached_tabstrip_); 958 else 959 browser_widget->ReleaseCapture(); 960 #if defined(OS_WIN) && defined(USE_AURA) 961 // The Gesture recognizer does not work well currently when capture changes 962 // while a touch gesture is in progress. So we need to manually transfer 963 // gesture sequence and the GR's touch events queue to the new window. This 964 // should really be done somewhere in capture change code and or inside the 965 // GR. But we currently do not have a consistent way for doing it that would 966 // work in all cases. Hence this hack. 967 ui::GestureRecognizer::Get()->TransferEventsTo( 968 browser_widget->GetNativeView(), 969 target_tabstrip->GetWidget()->GetNativeView()); 970 #endif 971 972 // The window is going away. Since the drag is still on going we don't want 973 // that to effect the position of any windows. 974 SetWindowPositionManaged(browser_widget->GetNativeView(), false); 975 976 // EndMoveLoop is going to snap the window back to its original location. 977 // Hide it so users don't see this. 978 browser_widget->Hide(); 979 browser_widget->EndMoveLoop(); 980 981 // Ideally we would always swap the tabs now, but on non-ash it seems that 982 // running the move loop implicitly activates the window when done, leading 983 // to all sorts of flicker. So, on non-ash, instead we process the move 984 // after the loop completes. But on chromeos, we can do tab swapping now to 985 // avoid the tab flashing issue(crbug.com/116329). 986 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 987 is_dragging_window_ = false; 988 Detach(DONT_RELEASE_CAPTURE); 989 Attach(target_tabstrip, point_in_screen); 990 // Move the tabs into position. 991 MoveAttached(point_in_screen); 992 attached_tabstrip_->GetWidget()->Activate(); 993 } else { 994 tab_strip_to_attach_to_after_exit_ = target_tabstrip; 995 } 996 997 waiting_for_run_loop_to_exit_ = true; 998 end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING; 999 return DRAG_BROWSER_RESULT_STOP; 1000 } 1001 Detach(DONT_RELEASE_CAPTURE); 1002 Attach(target_tabstrip, point_in_screen); 1003 return DRAG_BROWSER_RESULT_CONTINUE; 1004 } 1005 1006 void TabDragController::DragActiveTabStacked( 1007 const gfx::Point& point_in_screen) { 1008 if (attached_tabstrip_->tab_count() != 1009 static_cast<int>(initial_tab_positions_.size())) 1010 return; // TODO: should cancel drag if this happens. 1011 1012 int delta = point_in_screen.x() - start_point_in_screen_.x(); 1013 attached_tabstrip_->DragActiveTab(initial_tab_positions_, delta); 1014 } 1015 1016 void TabDragController::MoveAttachedToNextStackedIndex( 1017 const gfx::Point& point_in_screen) { 1018 int index = attached_tabstrip_->touch_layout_->active_index(); 1019 if (index + 1 >= attached_tabstrip_->tab_count()) 1020 return; 1021 1022 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index + 1); 1023 StartMoveStackedTimerIfNecessary(point_in_screen, 1024 kMoveAttachedSubsequentDelay); 1025 } 1026 1027 void TabDragController::MoveAttachedToPreviousStackedIndex( 1028 const gfx::Point& point_in_screen) { 1029 int index = attached_tabstrip_->touch_layout_->active_index(); 1030 if (index <= attached_tabstrip_->GetMiniTabCount()) 1031 return; 1032 1033 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index - 1); 1034 StartMoveStackedTimerIfNecessary(point_in_screen, 1035 kMoveAttachedSubsequentDelay); 1036 } 1037 1038 void TabDragController::MoveAttached(const gfx::Point& point_in_screen) { 1039 DCHECK(attached_tabstrip_); 1040 DCHECK(!view_.get()); 1041 DCHECK(!is_dragging_window_); 1042 1043 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen); 1044 1045 // Determine the horizontal move threshold. This is dependent on the width 1046 // of tabs. The smaller the tabs compared to the standard size, the smaller 1047 // the threshold. 1048 int threshold = kHorizontalMoveThreshold; 1049 if (!attached_tabstrip_->touch_layout_.get()) { 1050 double unselected, selected; 1051 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); 1052 double ratio = unselected / Tab::GetStandardSize().width(); 1053 threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); 1054 } 1055 // else case: touch tabs never shrink. 1056 1057 std::vector<Tab*> tabs(drag_data_.size()); 1058 for (size_t i = 0; i < drag_data_.size(); ++i) 1059 tabs[i] = drag_data_[i].attached_tab; 1060 1061 bool did_layout = false; 1062 // Update the model, moving the WebContents from one index to another. Do this 1063 // only if we have moved a minimum distance since the last reorder (to prevent 1064 // jitter) or if this the first move and the tabs are not consecutive. 1065 if ((abs(point_in_screen.x() - last_move_screen_loc_) > threshold || 1066 (initial_move_ && !AreTabsConsecutive()))) { 1067 TabStripModel* attached_model = GetModel(attached_tabstrip_); 1068 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 1069 int to_index = GetInsertionIndexForDraggedBounds(bounds); 1070 bool do_move = true; 1071 // While dragging within a tabstrip the expectation is the insertion index 1072 // is based on the left edge of the tabs being dragged. OTOH when dragging 1073 // into a new tabstrip (attaching) the expectation is the insertion index is 1074 // based on the cursor. This proves problematic as insertion may change the 1075 // size of the tabs, resulting in the index calculated before the insert 1076 // differing from the index calculated after the insert. To alleviate this 1077 // the index is chosen before insertion, and subsequently a new index is 1078 // only used once the mouse moves enough such that the index changes based 1079 // on the direction the mouse moved relative to |attach_x_| (smaller 1080 // x-coordinate should yield a smaller index or larger x-coordinate yields a 1081 // larger index). 1082 if (attach_index_ != -1) { 1083 gfx::Point tab_strip_point(point_in_screen); 1084 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point); 1085 const int new_x = 1086 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()); 1087 if (new_x < attach_x_) 1088 to_index = std::min(to_index, attach_index_); 1089 else 1090 to_index = std::max(to_index, attach_index_); 1091 if (to_index != attach_index_) 1092 attach_index_ = -1; // Once a valid move is detected, don't constrain. 1093 else 1094 do_move = false; 1095 } 1096 if (do_move) { 1097 WebContents* last_contents = drag_data_[drag_data_.size() - 1].contents; 1098 int index_of_last_item = 1099 attached_model->GetIndexOfWebContents(last_contents); 1100 if (initial_move_) { 1101 // TabStrip determines if the tabs needs to be animated based on model 1102 // position. This means we need to invoke LayoutDraggedTabsAt before 1103 // changing the model. 1104 attached_tabstrip_->LayoutDraggedTabsAt( 1105 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 1106 initial_move_); 1107 did_layout = true; 1108 } 1109 attached_model->MoveSelectedTabsTo(to_index); 1110 1111 // Move may do nothing in certain situations (such as when dragging pinned 1112 // tabs). Make sure the tabstrip actually changed before updating 1113 // last_move_screen_loc_. 1114 if (index_of_last_item != 1115 attached_model->GetIndexOfWebContents(last_contents)) { 1116 last_move_screen_loc_ = point_in_screen.x(); 1117 } 1118 } 1119 } 1120 1121 if (!did_layout) { 1122 attached_tabstrip_->LayoutDraggedTabsAt( 1123 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 1124 initial_move_); 1125 } 1126 1127 StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay); 1128 1129 initial_move_ = false; 1130 } 1131 1132 void TabDragController::MoveDetached(const gfx::Point& point_in_screen) { 1133 DCHECK(!attached_tabstrip_); 1134 DCHECK(view_.get()); 1135 DCHECK(!is_dragging_window_); 1136 1137 // Move the View. There are no changes to the model if we're detached. 1138 view_->MoveTo(point_in_screen); 1139 } 1140 1141 void TabDragController::StartMoveStackedTimerIfNecessary( 1142 const gfx::Point& point_in_screen, 1143 int delay_ms) { 1144 DCHECK(attached_tabstrip_); 1145 1146 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get(); 1147 if (!touch_layout) 1148 return; 1149 1150 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen); 1151 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 1152 int index = touch_layout->active_index(); 1153 if (ShouldDragToNextStackedTab(bounds, index)) { 1154 static_cast<base::Timer*>(&move_stacked_timer_)->Start( 1155 FROM_HERE, 1156 base::TimeDelta::FromMilliseconds(delay_ms), 1157 base::Bind(&TabDragController::MoveAttachedToNextStackedIndex, 1158 base::Unretained(this), point_in_screen)); 1159 } else if (ShouldDragToPreviousStackedTab(bounds, index)) { 1160 static_cast<base::Timer*>(&move_stacked_timer_)->Start( 1161 FROM_HERE, 1162 base::TimeDelta::FromMilliseconds(delay_ms), 1163 base::Bind(&TabDragController::MoveAttachedToPreviousStackedIndex, 1164 base::Unretained(this), point_in_screen)); 1165 } 1166 } 1167 1168 TabDragController::DetachPosition TabDragController::GetDetachPosition( 1169 const gfx::Point& point_in_screen) { 1170 DCHECK(attached_tabstrip_); 1171 gfx::Point attached_point(point_in_screen); 1172 views::View::ConvertPointFromScreen(attached_tabstrip_, &attached_point); 1173 if (attached_point.x() < 0) 1174 return DETACH_BEFORE; 1175 if (attached_point.x() >= attached_tabstrip_->width()) 1176 return DETACH_AFTER; 1177 return DETACH_ABOVE_OR_BELOW; 1178 } 1179 1180 DockInfo TabDragController::GetDockInfoAtPoint( 1181 const gfx::Point& point_in_screen) { 1182 // TODO: add support for dock info when |detach_into_browser_| is true. 1183 if (attached_tabstrip_ || detach_into_browser_) { 1184 // If the mouse is over a tab strip, don't offer a dock position. 1185 return DockInfo(); 1186 } 1187 1188 if (dock_info_.IsValidForPoint(point_in_screen)) { 1189 // It's possible any given screen coordinate has multiple docking 1190 // positions. Check the current info first to avoid having the docking 1191 // position bounce around. 1192 return dock_info_; 1193 } 1194 1195 gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView(); 1196 dock_windows_.insert(dragged_view); 1197 DockInfo info = DockInfo::GetDockInfoAtPoint( 1198 host_desktop_type_, 1199 point_in_screen, 1200 dock_windows_); 1201 dock_windows_.erase(dragged_view); 1202 return info; 1203 } 1204 1205 TabStrip* TabDragController::GetTargetTabStripForPoint( 1206 const gfx::Point& point_in_screen) { 1207 TRACE_EVENT1("views", "TabDragController::GetTargetTabStripForPoint", 1208 "point_in_screen", point_in_screen.ToString()); 1209 1210 if (move_only() && attached_tabstrip_) { 1211 DCHECK_EQ(DETACHABLE, detach_behavior_); 1212 // move_only() is intended for touch, in which case we only want to detach 1213 // if the touch point moves significantly in the vertical distance. 1214 gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_); 1215 if (DoesRectContainVerticalPointExpanded(tabstrip_bounds, 1216 kTouchVerticalDetachMagnetism, 1217 point_in_screen.y())) 1218 return attached_tabstrip_; 1219 } 1220 gfx::NativeView dragged_view = NULL; 1221 if (view_.get()) 1222 dragged_view = view_->GetWidget()->GetNativeView(); 1223 else if (is_dragging_window_) 1224 dragged_view = attached_tabstrip_->GetWidget()->GetNativeView(); 1225 if (dragged_view) 1226 dock_windows_.insert(dragged_view); 1227 gfx::NativeWindow local_window = 1228 DockInfo::GetLocalProcessWindowAtPoint( 1229 host_desktop_type_, 1230 point_in_screen, 1231 dock_windows_); 1232 if (dragged_view) 1233 dock_windows_.erase(dragged_view); 1234 TabStrip* tab_strip = GetTabStripForWindow(local_window); 1235 if (tab_strip && DoesTabStripContain(tab_strip, point_in_screen)) 1236 return tab_strip; 1237 return is_dragging_window_ ? attached_tabstrip_ : NULL; 1238 } 1239 1240 TabStrip* TabDragController::GetTabStripForWindow(gfx::NativeWindow window) { 1241 if (!window) 1242 return NULL; 1243 BrowserView* browser_view = 1244 BrowserView::GetBrowserViewForNativeWindow(window); 1245 // We don't allow drops on windows that don't have tabstrips. 1246 if (!browser_view || 1247 !browser_view->browser()->SupportsWindowFeature( 1248 Browser::FEATURE_TABSTRIP)) 1249 return NULL; 1250 1251 TabStrip* other_tabstrip = browser_view->tabstrip(); 1252 TabStrip* tab_strip = 1253 attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_; 1254 DCHECK(tab_strip); 1255 1256 return other_tabstrip->controller()->IsCompatibleWith(tab_strip) ? 1257 other_tabstrip : NULL; 1258 } 1259 1260 bool TabDragController::DoesTabStripContain( 1261 TabStrip* tabstrip, 1262 const gfx::Point& point_in_screen) const { 1263 // Make sure the specified screen point is actually within the bounds of the 1264 // specified tabstrip... 1265 gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip); 1266 return point_in_screen.x() < tabstrip_bounds.right() && 1267 point_in_screen.x() >= tabstrip_bounds.x() && 1268 DoesRectContainVerticalPointExpanded(tabstrip_bounds, 1269 kVerticalDetachMagnetism, 1270 point_in_screen.y()); 1271 } 1272 1273 void TabDragController::Attach(TabStrip* attached_tabstrip, 1274 const gfx::Point& point_in_screen) { 1275 TRACE_EVENT1("views", "TabDragController::Attach", 1276 "point_in_screen", point_in_screen.ToString()); 1277 1278 DCHECK(!attached_tabstrip_); // We should already have detached by the time 1279 // we get here. 1280 1281 attached_tabstrip_ = attached_tabstrip; 1282 1283 // And we don't need the dragged view. 1284 view_.reset(); 1285 1286 std::vector<Tab*> tabs = 1287 GetTabsMatchingDraggedContents(attached_tabstrip_); 1288 1289 if (tabs.empty()) { 1290 // Transitioning from detached to attached to a new tabstrip. Add tabs to 1291 // the new model. 1292 1293 selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel()); 1294 1295 if (!detach_into_browser_) { 1296 // Remove ourselves as the delegate now that the dragged WebContents is 1297 // being inserted back into a Browser. 1298 for (size_t i = 0; i < drag_data_.size(); ++i) { 1299 drag_data_[i].contents->SetDelegate(NULL); 1300 drag_data_[i].original_delegate = NULL; 1301 } 1302 1303 // Return the WebContents to normalcy. 1304 source_dragged_contents()->DecrementCapturerCount(); 1305 } 1306 1307 // Inserting counts as a move. We don't want the tabs to jitter when the 1308 // user moves the tab immediately after attaching it. 1309 last_move_screen_loc_ = point_in_screen.x(); 1310 1311 // Figure out where to insert the tab based on the bounds of the dragged 1312 // representation and the ideal bounds of the other Tabs already in the 1313 // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are 1314 // changing due to animation). 1315 gfx::Point tab_strip_point(point_in_screen); 1316 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point); 1317 tab_strip_point.set_x( 1318 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x())); 1319 tab_strip_point.Offset(0, -mouse_offset_.y()); 1320 gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point); 1321 int index = GetInsertionIndexForDraggedBounds(bounds); 1322 attach_index_ = index; 1323 attach_x_ = tab_strip_point.x(); 1324 base::AutoReset<bool> setter(&is_mutating_, true); 1325 for (size_t i = 0; i < drag_data_.size(); ++i) { 1326 int add_types = TabStripModel::ADD_NONE; 1327 if (attached_tabstrip_->touch_layout_.get()) { 1328 // StackedTabStripLayout positions relative to the active tab, if we 1329 // don't add the tab as active things bounce around. 1330 DCHECK_EQ(1u, drag_data_.size()); 1331 add_types |= TabStripModel::ADD_ACTIVE; 1332 } 1333 if (drag_data_[i].pinned) 1334 add_types |= TabStripModel::ADD_PINNED; 1335 GetModel(attached_tabstrip_)->InsertWebContentsAt( 1336 index + i, drag_data_[i].contents, add_types); 1337 } 1338 1339 tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); 1340 } 1341 DCHECK_EQ(tabs.size(), drag_data_.size()); 1342 for (size_t i = 0; i < drag_data_.size(); ++i) 1343 drag_data_[i].attached_tab = tabs[i]; 1344 1345 attached_tabstrip_->StartedDraggingTabs(tabs); 1346 1347 ResetSelection(GetModel(attached_tabstrip_)); 1348 1349 // The size of the dragged tab may have changed. Adjust the x offset so that 1350 // ratio of mouse_offset_ to original width is maintained. 1351 std::vector<Tab*> tabs_to_source(tabs); 1352 tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1, 1353 tabs_to_source.end()); 1354 int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) - 1355 tabs[source_tab_index_]->width() + 1356 static_cast<int>(offset_to_width_ratio_ * 1357 tabs[source_tab_index_]->width()); 1358 mouse_offset_.set_x(new_x); 1359 1360 // Transfer ownership of us to the new tabstrip as well as making sure the 1361 // window has capture. This is important so that if activation changes the 1362 // drag isn't prematurely canceled. 1363 if (detach_into_browser_) { 1364 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_); 1365 attached_tabstrip_->OwnDragController(this); 1366 } 1367 1368 // Redirect all mouse events to the TabStrip so that the tab that originated 1369 // the drag can safely be deleted. 1370 if (detach_into_browser_ || attached_tabstrip_ == source_tabstrip_) { 1371 static_cast<views::internal::RootView*>( 1372 attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler( 1373 attached_tabstrip_); 1374 } 1375 } 1376 1377 void TabDragController::Detach(ReleaseCapture release_capture) { 1378 TRACE_EVENT1("views", "TabDragController::Detach", 1379 "release_capture", release_capture); 1380 1381 attach_index_ = -1; 1382 1383 // When the user detaches we assume they want to reorder. 1384 move_behavior_ = REORDER; 1385 1386 // Release ownership of the drag controller and mouse capture. When we 1387 // reattach ownership is transfered. 1388 if (detach_into_browser_) { 1389 attached_tabstrip_->ReleaseDragController(); 1390 if (release_capture == RELEASE_CAPTURE) 1391 attached_tabstrip_->GetWidget()->ReleaseCapture(); 1392 } 1393 1394 mouse_move_direction_ = kMovedMouseLeft | kMovedMouseRight; 1395 1396 // Prevent the WebContents HWND from being hidden by any of the model 1397 // operations performed during the drag. 1398 if (!detach_into_browser_) 1399 source_dragged_contents()->IncrementCapturerCount(); 1400 1401 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 1402 TabStripModel* attached_model = GetModel(attached_tabstrip_); 1403 std::vector<TabRendererData> tab_data; 1404 for (size_t i = 0; i < drag_data_.size(); ++i) { 1405 tab_data.push_back(drag_data_[i].attached_tab->data()); 1406 int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents); 1407 DCHECK_NE(-1, index); 1408 1409 // Hide the tab so that the user doesn't see it animate closed. 1410 drag_data_[i].attached_tab->SetVisible(false); 1411 1412 attached_model->DetachWebContentsAt(index); 1413 1414 // Detaching resets the delegate, but we still want to be the delegate. 1415 if (!detach_into_browser_) 1416 drag_data_[i].contents->SetDelegate(this); 1417 1418 // Detaching may end up deleting the tab, drop references to it. 1419 drag_data_[i].attached_tab = NULL; 1420 } 1421 1422 // If we've removed the last Tab from the TabStrip, hide the frame now. 1423 if (!attached_model->empty()) { 1424 if (!selection_model_before_attach_.empty() && 1425 selection_model_before_attach_.active() >= 0 && 1426 selection_model_before_attach_.active() < attached_model->count()) { 1427 // Restore the selection. 1428 attached_model->SetSelectionFromModel(selection_model_before_attach_); 1429 } else if (attached_tabstrip_ == source_tabstrip_ && 1430 !initial_selection_model_.empty()) { 1431 // First time detaching from the source tabstrip. Reset selection model to 1432 // initial_selection_model_. Before resetting though we have to remove all 1433 // the tabs from initial_selection_model_ as it was created with the tabs 1434 // still there. 1435 ui::ListSelectionModel selection_model; 1436 selection_model.Copy(initial_selection_model_); 1437 for (DragData::const_reverse_iterator i(drag_data_.rbegin()); 1438 i != drag_data_.rend(); ++i) { 1439 selection_model.DecrementFrom(i->source_model_index); 1440 } 1441 // We may have cleared out the selection model. Only reset it if it 1442 // contains something. 1443 if (!selection_model.empty()) 1444 attached_model->SetSelectionFromModel(selection_model); 1445 } 1446 } else if (!detach_into_browser_) { 1447 HideFrame(); 1448 } 1449 1450 // Create the dragged view. 1451 if (!detach_into_browser_) 1452 CreateDraggedView(tab_data, drag_bounds); 1453 1454 attached_tabstrip_->DraggedTabsDetached(); 1455 attached_tabstrip_ = NULL; 1456 } 1457 1458 void TabDragController::DetachIntoNewBrowserAndRunMoveLoop( 1459 const gfx::Point& point_in_screen) { 1460 if (GetModel(attached_tabstrip_)->count() == 1461 static_cast<int>(drag_data_.size())) { 1462 // All the tabs in a browser are being dragged but all the tabs weren't 1463 // initially being dragged. For this to happen the user would have to 1464 // start dragging a set of tabs, the other tabs close, then detach. 1465 RunMoveLoop(GetWindowOffset(point_in_screen)); 1466 return; 1467 } 1468 1469 const int last_tabstrip_width = attached_tabstrip_->tab_area_width(); 1470 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(); 1471 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds); 1472 1473 gfx::Vector2d drag_offset; 1474 Browser* browser = CreateBrowserForDrag( 1475 attached_tabstrip_, point_in_screen, &drag_offset, &drag_bounds); 1476 #if defined(OS_WIN) && defined(USE_AURA) 1477 gfx::NativeView attached_native_view = 1478 attached_tabstrip_->GetWidget()->GetNativeView(); 1479 #endif 1480 Detach(host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH ? 1481 DONT_RELEASE_CAPTURE : RELEASE_CAPTURE); 1482 BrowserView* dragged_browser_view = 1483 BrowserView::GetBrowserViewForBrowser(browser); 1484 views::Widget* dragged_widget = dragged_browser_view->GetWidget(); 1485 #if defined(OS_WIN) && defined(USE_AURA) 1486 // The Gesture recognizer does not work well currently when capture changes 1487 // while a touch gesture is in progress. So we need to manually transfer 1488 // gesture sequence and the GR's touch events queue to the new window. This 1489 // should really be done somewhere in capture change code and or inside the 1490 // GR. But we currently do not have a consistent way for doing it that would 1491 // work in all cases. Hence this hack. 1492 ui::GestureRecognizer::Get()->TransferEventsTo( 1493 attached_native_view, 1494 dragged_widget->GetNativeView()); 1495 #endif 1496 dragged_widget->SetVisibilityChangedAnimationsEnabled(false); 1497 Attach(dragged_browser_view->tabstrip(), gfx::Point()); 1498 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width, 1499 point_in_screen, 1500 &drag_bounds); 1501 WindowPositionManagedUpdater updater; 1502 dragged_widget->AddObserver(&updater); 1503 browser->window()->Show(); 1504 dragged_widget->RemoveObserver(&updater); 1505 dragged_widget->SetVisibilityChangedAnimationsEnabled(true); 1506 // Activate may trigger a focus loss, destroying us. 1507 { 1508 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 1509 browser->window()->Activate(); 1510 if (!ref) 1511 return; 1512 } 1513 RunMoveLoop(drag_offset); 1514 } 1515 1516 void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) { 1517 // If the user drags the whole window we'll assume they are going to attach to 1518 // another window and therefore want to reorder. 1519 move_behavior_ = REORDER; 1520 1521 move_loop_widget_ = GetAttachedBrowserWidget(); 1522 DCHECK(move_loop_widget_); 1523 move_loop_widget_->AddObserver(this); 1524 is_dragging_window_ = true; 1525 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr()); 1526 // Running the move loop releases mouse capture on non-ash, which triggers 1527 // destroying the drag loop. Release mouse capture ourself before this while 1528 // the DragController isn't owned by the TabStrip. 1529 if (host_desktop_type_ != chrome::HOST_DESKTOP_TYPE_ASH) { 1530 attached_tabstrip_->ReleaseDragController(); 1531 attached_tabstrip_->GetWidget()->ReleaseCapture(); 1532 attached_tabstrip_->OwnDragController(this); 1533 } 1534 const views::Widget::MoveLoopSource move_loop_source = 1535 event_source_ == EVENT_SOURCE_MOUSE ? 1536 views::Widget::MOVE_LOOP_SOURCE_MOUSE : 1537 views::Widget::MOVE_LOOP_SOURCE_TOUCH; 1538 const views::Widget::MoveLoopEscapeBehavior escape_behavior = 1539 is_dragging_new_browser_ ? 1540 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_HIDE : 1541 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_DONT_HIDE; 1542 views::Widget::MoveLoopResult result = 1543 move_loop_widget_->RunMoveLoop( 1544 drag_offset, move_loop_source, escape_behavior); 1545 content::NotificationService::current()->Notify( 1546 chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE, 1547 content::NotificationService::AllBrowserContextsAndSources(), 1548 content::NotificationService::NoDetails()); 1549 1550 if (!ref) 1551 return; 1552 // Under chromeos we immediately set the |move_loop_widget_| to NULL. 1553 if (move_loop_widget_) { 1554 move_loop_widget_->RemoveObserver(this); 1555 move_loop_widget_ = NULL; 1556 } 1557 is_dragging_window_ = false; 1558 waiting_for_run_loop_to_exit_ = false; 1559 if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) { 1560 end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING; 1561 if (tab_strip_to_attach_to_after_exit_) { 1562 gfx::Point point_in_screen(GetCursorScreenPoint()); 1563 Detach(DONT_RELEASE_CAPTURE); 1564 Attach(tab_strip_to_attach_to_after_exit_, point_in_screen); 1565 // Move the tabs into position. 1566 MoveAttached(point_in_screen); 1567 attached_tabstrip_->GetWidget()->Activate(); 1568 // Activate may trigger a focus loss, destroying us. 1569 if (!ref) 1570 return; 1571 tab_strip_to_attach_to_after_exit_ = NULL; 1572 } 1573 DCHECK(attached_tabstrip_); 1574 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_); 1575 } else if (active_) { 1576 EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ? 1577 END_DRAG_CANCEL : END_DRAG_COMPLETE); 1578 } 1579 } 1580 1581 int TabDragController::GetInsertionIndexFrom(const gfx::Rect& dragged_bounds, 1582 int start, 1583 int delta) const { 1584 for (int i = start, tab_count = attached_tabstrip_->tab_count(); 1585 i >= 0 && i < tab_count; i += delta) { 1586 const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i); 1587 gfx::Rect left_half, right_half; 1588 ideal_bounds.SplitVertically(&left_half, &right_half); 1589 if (dragged_bounds.x() >= right_half.x() && 1590 dragged_bounds.x() < right_half.right()) { 1591 return i + 1; 1592 } else if (dragged_bounds.x() >= left_half.x() && 1593 dragged_bounds.x() < left_half.right()) { 1594 return i; 1595 } 1596 } 1597 return -1; 1598 } 1599 1600 int TabDragController::GetInsertionIndexForDraggedBounds( 1601 const gfx::Rect& dragged_bounds) const { 1602 int index = -1; 1603 if (attached_tabstrip_->touch_layout_.get()) { 1604 index = GetInsertionIndexForDraggedBoundsStacked(dragged_bounds); 1605 if (index != -1) { 1606 // Only move the tab to the left/right if the user actually moved the 1607 // mouse that way. This is necessary as tabs with stacked tabs 1608 // before/after them have multiple drag positions. 1609 int active_index = attached_tabstrip_->touch_layout_->active_index(); 1610 if ((index < active_index && 1611 (mouse_move_direction_ & kMovedMouseLeft) == 0) || 1612 (index > active_index && 1613 (mouse_move_direction_ & kMovedMouseRight) == 0)) { 1614 index = active_index; 1615 } 1616 } 1617 } else { 1618 index = GetInsertionIndexFrom(dragged_bounds, 0, 1); 1619 } 1620 if (index == -1) { 1621 int tab_count = attached_tabstrip_->tab_count(); 1622 int right_tab_x = tab_count == 0 ? 0 : 1623 attached_tabstrip_->ideal_bounds(tab_count - 1).right(); 1624 if (dragged_bounds.right() > right_tab_x) { 1625 index = GetModel(attached_tabstrip_)->count(); 1626 } else { 1627 index = 0; 1628 } 1629 } 1630 1631 if (!drag_data_[0].attached_tab) { 1632 // If 'attached_tab' is NULL, it means we're in the process of attaching and 1633 // don't need to constrain the index. 1634 return index; 1635 } 1636 1637 int max_index = GetModel(attached_tabstrip_)->count() - 1638 static_cast<int>(drag_data_.size()); 1639 return std::max(0, std::min(max_index, index)); 1640 } 1641 1642 bool TabDragController::ShouldDragToNextStackedTab( 1643 const gfx::Rect& dragged_bounds, 1644 int index) const { 1645 if (index + 1 >= attached_tabstrip_->tab_count() || 1646 !attached_tabstrip_->touch_layout_->IsStacked(index + 1) || 1647 (mouse_move_direction_ & kMovedMouseRight) == 0) 1648 return false; 1649 1650 int active_x = attached_tabstrip_->ideal_bounds(index).x(); 1651 int next_x = attached_tabstrip_->ideal_bounds(index + 1).x(); 1652 int mid_x = std::min(next_x - kStackedDistance, 1653 active_x + (next_x - active_x) / 4); 1654 return dragged_bounds.x() >= mid_x; 1655 } 1656 1657 bool TabDragController::ShouldDragToPreviousStackedTab( 1658 const gfx::Rect& dragged_bounds, 1659 int index) const { 1660 if (index - 1 < attached_tabstrip_->GetMiniTabCount() || 1661 !attached_tabstrip_->touch_layout_->IsStacked(index - 1) || 1662 (mouse_move_direction_ & kMovedMouseLeft) == 0) 1663 return false; 1664 1665 int active_x = attached_tabstrip_->ideal_bounds(index).x(); 1666 int previous_x = attached_tabstrip_->ideal_bounds(index - 1).x(); 1667 int mid_x = std::max(previous_x + kStackedDistance, 1668 active_x - (active_x - previous_x) / 4); 1669 return dragged_bounds.x() <= mid_x; 1670 } 1671 1672 int TabDragController::GetInsertionIndexForDraggedBoundsStacked( 1673 const gfx::Rect& dragged_bounds) const { 1674 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get(); 1675 int active_index = touch_layout->active_index(); 1676 // Search from the active index to the front of the tabstrip. Do this as tabs 1677 // overlap each other from the active index. 1678 int index = GetInsertionIndexFrom(dragged_bounds, active_index, -1); 1679 if (index != active_index) 1680 return index; 1681 if (index == -1) 1682 return GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1); 1683 1684 // The position to drag to corresponds to the active tab. If the next/previous 1685 // tab is stacked, then shorten the distance used to determine insertion 1686 // bounds. We do this as GetInsertionIndexFrom() uses the bounds of the 1687 // tabs. When tabs are stacked the next/previous tab is on top of the tab. 1688 if (active_index + 1 < attached_tabstrip_->tab_count() && 1689 touch_layout->IsStacked(active_index + 1)) { 1690 index = GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1); 1691 if (index == -1 && ShouldDragToNextStackedTab(dragged_bounds, active_index)) 1692 index = active_index + 1; 1693 else if (index == -1) 1694 index = active_index; 1695 } else if (ShouldDragToPreviousStackedTab(dragged_bounds, active_index)) { 1696 index = active_index - 1; 1697 } 1698 return index; 1699 } 1700 1701 gfx::Rect TabDragController::GetDraggedViewTabStripBounds( 1702 const gfx::Point& tab_strip_point) { 1703 // attached_tab is NULL when inserting into a new tabstrip. 1704 if (source_tab_drag_data()->attached_tab) { 1705 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1706 source_tab_drag_data()->attached_tab->width(), 1707 source_tab_drag_data()->attached_tab->height()); 1708 } 1709 1710 double sel_width, unselected_width; 1711 attached_tabstrip_->GetCurrentTabWidths(&sel_width, &unselected_width); 1712 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1713 static_cast<int>(sel_width), 1714 Tab::GetStandardSize().height()); 1715 } 1716 1717 gfx::Point TabDragController::GetAttachedDragPoint( 1718 const gfx::Point& point_in_screen) { 1719 DCHECK(attached_tabstrip_); // The tab must be attached. 1720 1721 gfx::Point tab_loc(point_in_screen); 1722 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_loc); 1723 const int x = 1724 attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x(); 1725 1726 // TODO: consider caching this. 1727 std::vector<Tab*> attached_tabs; 1728 for (size_t i = 0; i < drag_data_.size(); ++i) 1729 attached_tabs.push_back(drag_data_[i].attached_tab); 1730 const int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs); 1731 const int max_x = attached_tabstrip_->width() - size; 1732 return gfx::Point(std::min(std::max(x, 0), max_x), 0); 1733 } 1734 1735 std::vector<Tab*> TabDragController::GetTabsMatchingDraggedContents( 1736 TabStrip* tabstrip) { 1737 TabStripModel* model = GetModel(attached_tabstrip_); 1738 std::vector<Tab*> tabs; 1739 for (size_t i = 0; i < drag_data_.size(); ++i) { 1740 int model_index = model->GetIndexOfWebContents(drag_data_[i].contents); 1741 if (model_index == TabStripModel::kNoTab) 1742 return std::vector<Tab*>(); 1743 tabs.push_back(tabstrip->tab_at(model_index)); 1744 } 1745 return tabs; 1746 } 1747 1748 std::vector<gfx::Rect> TabDragController::CalculateBoundsForDraggedTabs() { 1749 std::vector<gfx::Rect> drag_bounds; 1750 std::vector<Tab*> attached_tabs; 1751 for (size_t i = 0; i < drag_data_.size(); ++i) 1752 attached_tabs.push_back(drag_data_[i].attached_tab); 1753 attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs, 1754 &drag_bounds); 1755 return drag_bounds; 1756 } 1757 1758 void TabDragController::EndDragImpl(EndDragType type) { 1759 DCHECK(active_); 1760 active_ = false; 1761 1762 bring_to_front_timer_.Stop(); 1763 move_stacked_timer_.Stop(); 1764 1765 if (is_dragging_window_) { 1766 waiting_for_run_loop_to_exit_ = true; 1767 1768 if (type == NORMAL || (type == TAB_DESTROYED && drag_data_.size() > 1)) { 1769 SetWindowPositionManaged(GetAttachedBrowserWidget()->GetNativeView(), 1770 true); 1771 } 1772 1773 // End the nested drag loop. 1774 GetAttachedBrowserWidget()->EndMoveLoop(); 1775 } 1776 1777 // Hide the current dock controllers. 1778 for (size_t i = 0; i < dock_controllers_.size(); ++i) { 1779 // Be sure and clear the controller first, that way if Hide ends up 1780 // deleting the controller it won't call us back. 1781 dock_controllers_[i]->clear_controller(); 1782 dock_controllers_[i]->Hide(); 1783 } 1784 dock_controllers_.clear(); 1785 dock_windows_.clear(); 1786 1787 if (type != TAB_DESTROYED) { 1788 // We only finish up the drag if we were actually dragging. If start_drag_ 1789 // is false, the user just clicked and released and didn't move the mouse 1790 // enough to trigger a drag. 1791 if (started_drag_) { 1792 RestoreFocus(); 1793 if (type == CANCELED) 1794 RevertDrag(); 1795 else 1796 CompleteDrag(); 1797 } 1798 } else if (drag_data_.size() > 1) { 1799 initial_selection_model_.Clear(); 1800 RevertDrag(); 1801 } // else case the only tab we were dragging was deleted. Nothing to do. 1802 1803 if (!detach_into_browser_) 1804 ResetDelegates(); 1805 1806 // Clear out drag data so we don't attempt to do anything with it. 1807 drag_data_.clear(); 1808 1809 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? 1810 attached_tabstrip_ : source_tabstrip_; 1811 owning_tabstrip->DestroyDragController(); 1812 } 1813 1814 void TabDragController::RevertDrag() { 1815 std::vector<Tab*> tabs; 1816 for (size_t i = 0; i < drag_data_.size(); ++i) { 1817 if (drag_data_[i].contents) { 1818 // Contents is NULL if a tab was destroyed while the drag was under way. 1819 tabs.push_back(drag_data_[i].attached_tab); 1820 RevertDragAt(i); 1821 } 1822 } 1823 1824 bool restore_frame = !detach_into_browser_ && 1825 attached_tabstrip_ != source_tabstrip_; 1826 if (attached_tabstrip_) { 1827 if (attached_tabstrip_ == source_tabstrip_) { 1828 source_tabstrip_->StoppedDraggingTabs( 1829 tabs, initial_tab_positions_, move_behavior_ == MOVE_VISIBILE_TABS, 1830 false); 1831 } else { 1832 attached_tabstrip_->DraggedTabsDetached(); 1833 } 1834 } 1835 1836 if (initial_selection_model_.empty()) 1837 ResetSelection(GetModel(source_tabstrip_)); 1838 else 1839 GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_); 1840 1841 // If we're not attached to any TabStrip, or attached to some other TabStrip, 1842 // we need to restore the bounds of the original TabStrip's frame, in case 1843 // it has been hidden. 1844 if (restore_frame && !restore_bounds_.IsEmpty()) 1845 source_tabstrip_->GetWidget()->SetBounds(restore_bounds_); 1846 1847 if (detach_into_browser_ && source_tabstrip_) 1848 source_tabstrip_->GetWidget()->Activate(); 1849 1850 // Return the WebContents to normalcy. If the tab was attached to a 1851 // TabStrip before the revert, the decrement has already occurred. 1852 // If the tab was destroyed, don't attempt to dereference the 1853 // WebContents pointer. 1854 if (!detach_into_browser_ && !attached_tabstrip_ && source_dragged_contents()) 1855 source_dragged_contents()->DecrementCapturerCount(); 1856 } 1857 1858 void TabDragController::ResetSelection(TabStripModel* model) { 1859 DCHECK(model); 1860 ui::ListSelectionModel selection_model; 1861 bool has_one_valid_tab = false; 1862 for (size_t i = 0; i < drag_data_.size(); ++i) { 1863 // |contents| is NULL if a tab was deleted out from under us. 1864 if (drag_data_[i].contents) { 1865 int index = model->GetIndexOfWebContents(drag_data_[i].contents); 1866 DCHECK_NE(-1, index); 1867 selection_model.AddIndexToSelection(index); 1868 if (!has_one_valid_tab || i == source_tab_index_) { 1869 // Reset the active/lead to the first tab. If the source tab is still 1870 // valid we'll reset these again later on. 1871 selection_model.set_active(index); 1872 selection_model.set_anchor(index); 1873 has_one_valid_tab = true; 1874 } 1875 } 1876 } 1877 if (!has_one_valid_tab) 1878 return; 1879 1880 model->SetSelectionFromModel(selection_model); 1881 } 1882 1883 void TabDragController::RevertDragAt(size_t drag_index) { 1884 DCHECK(started_drag_); 1885 DCHECK(source_tabstrip_); 1886 1887 base::AutoReset<bool> setter(&is_mutating_, true); 1888 TabDragData* data = &(drag_data_[drag_index]); 1889 if (attached_tabstrip_) { 1890 int index = 1891 GetModel(attached_tabstrip_)->GetIndexOfWebContents(data->contents); 1892 if (attached_tabstrip_ != source_tabstrip_) { 1893 // The Tab was inserted into another TabStrip. We need to put it back 1894 // into the original one. 1895 GetModel(attached_tabstrip_)->DetachWebContentsAt(index); 1896 // TODO(beng): (Cleanup) seems like we should use Attach() for this 1897 // somehow. 1898 GetModel(source_tabstrip_)->InsertWebContentsAt( 1899 data->source_model_index, data->contents, 1900 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1901 } else { 1902 // The Tab was moved within the TabStrip where the drag was initiated. 1903 // Move it back to the starting location. 1904 GetModel(source_tabstrip_)->MoveWebContentsAt( 1905 index, data->source_model_index, false); 1906 } 1907 } else { 1908 // The Tab was detached from the TabStrip where the drag began, and has not 1909 // been attached to any other TabStrip. We need to put it back into the 1910 // source TabStrip. 1911 GetModel(source_tabstrip_)->InsertWebContentsAt( 1912 data->source_model_index, data->contents, 1913 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1914 } 1915 } 1916 1917 void TabDragController::CompleteDrag() { 1918 DCHECK(started_drag_); 1919 1920 if (attached_tabstrip_) { 1921 if (is_dragging_new_browser_) { 1922 if (IsDockedOrSnapped(attached_tabstrip_)) { 1923 DCHECK_EQ(host_desktop_type_, chrome::HOST_DESKTOP_TYPE_ASH); 1924 was_source_maximized_ = false; 1925 was_source_fullscreen_ = false; 1926 } 1927 // If source window was maximized - maximize the new window as well. 1928 if (was_source_maximized_ || was_source_fullscreen_) 1929 GetAttachedBrowserWidget()->Maximize(); 1930 #if defined(USE_ASH) 1931 if (was_source_fullscreen_ && 1932 host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 1933 // In fullscreen mode it is only possible to get here if the source 1934 // was in "immersive fullscreen" mode, so toggle it back on. 1935 ash::accelerators::ToggleFullscreen(); 1936 } 1937 #endif 1938 } 1939 attached_tabstrip_->StoppedDraggingTabs( 1940 GetTabsMatchingDraggedContents(attached_tabstrip_), 1941 initial_tab_positions_, 1942 move_behavior_ == MOVE_VISIBILE_TABS, 1943 true); 1944 } else { 1945 if (dock_info_.type() != DockInfo::NONE) { 1946 switch (dock_info_.type()) { 1947 case DockInfo::LEFT_OF_WINDOW: 1948 content::RecordAction(UserMetricsAction("DockingWindow_Left")); 1949 break; 1950 1951 case DockInfo::RIGHT_OF_WINDOW: 1952 content::RecordAction(UserMetricsAction("DockingWindow_Right")); 1953 break; 1954 1955 case DockInfo::BOTTOM_OF_WINDOW: 1956 content::RecordAction(UserMetricsAction("DockingWindow_Bottom")); 1957 break; 1958 1959 case DockInfo::TOP_OF_WINDOW: 1960 content::RecordAction(UserMetricsAction("DockingWindow_Top")); 1961 break; 1962 1963 case DockInfo::MAXIMIZE: 1964 content::RecordAction( 1965 UserMetricsAction("DockingWindow_Maximize")); 1966 break; 1967 1968 case DockInfo::LEFT_HALF: 1969 content::RecordAction( 1970 UserMetricsAction("DockingWindow_LeftHalf")); 1971 break; 1972 1973 case DockInfo::RIGHT_HALF: 1974 content::RecordAction( 1975 UserMetricsAction("DockingWindow_RightHalf")); 1976 break; 1977 1978 case DockInfo::BOTTOM_HALF: 1979 content::RecordAction( 1980 UserMetricsAction("DockingWindow_BottomHalf")); 1981 break; 1982 1983 default: 1984 NOTREACHED(); 1985 break; 1986 } 1987 } 1988 // Compel the model to construct a new window for the detached 1989 // WebContentses. 1990 views::Widget* widget = source_tabstrip_->GetWidget(); 1991 gfx::Rect window_bounds(widget->GetRestoredBounds()); 1992 window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_)); 1993 1994 // When modifying the following if statement, please make sure not to 1995 // introduce issue listed in http://crbug.com/6223 comment #11. 1996 bool rtl_ui = base::i18n::IsRTL(); 1997 bool has_dock_position = (dock_info_.type() != DockInfo::NONE); 1998 if (rtl_ui && has_dock_position) { 1999 // Mirror X axis so the docked tab is aligned using the mouse click as 2000 // the top-right corner. 2001 window_bounds.set_x(window_bounds.x() - window_bounds.width()); 2002 } 2003 base::AutoReset<bool> setter(&is_mutating_, true); 2004 2005 std::vector<TabStripModelDelegate::NewStripContents> contentses; 2006 for (size_t i = 0; i < drag_data_.size(); ++i) { 2007 TabStripModelDelegate::NewStripContents item; 2008 item.web_contents = drag_data_[i].contents; 2009 item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED 2010 : TabStripModel::ADD_NONE; 2011 contentses.push_back(item); 2012 } 2013 2014 Browser* new_browser = 2015 GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents( 2016 contentses, window_bounds, dock_info_, widget->IsMaximized()); 2017 ResetSelection(new_browser->tab_strip_model()); 2018 new_browser->window()->Show(); 2019 2020 // Return the WebContents to normalcy. 2021 if (!detach_into_browser_) 2022 source_dragged_contents()->DecrementCapturerCount(); 2023 } 2024 2025 CleanUpHiddenFrame(); 2026 } 2027 2028 void TabDragController::ResetDelegates() { 2029 DCHECK(!detach_into_browser_); 2030 for (size_t i = 0; i < drag_data_.size(); ++i) { 2031 if (drag_data_[i].contents && 2032 drag_data_[i].contents->GetDelegate() == this) { 2033 drag_data_[i].contents->SetDelegate( 2034 drag_data_[i].original_delegate); 2035 } 2036 } 2037 } 2038 2039 void TabDragController::CreateDraggedView( 2040 const std::vector<TabRendererData>& data, 2041 const std::vector<gfx::Rect>& renderer_bounds) { 2042 #if !defined(USE_AURA) 2043 DCHECK(!view_.get()); 2044 DCHECK_EQ(data.size(), drag_data_.size()); 2045 2046 // Set up the photo booth to start capturing the contents of the dragged 2047 // WebContents. 2048 NativeViewPhotobooth* photobooth = NativeViewPhotobooth::Create( 2049 source_dragged_contents()->GetView()->GetNativeView()); 2050 2051 gfx::Rect content_bounds; 2052 source_dragged_contents()->GetView()->GetContainerBounds(&content_bounds); 2053 2054 std::vector<views::View*> renderers; 2055 for (size_t i = 0; i < drag_data_.size(); ++i) { 2056 Tab* renderer = source_tabstrip_->CreateTabForDragging(); 2057 renderer->SetData(data[i]); 2058 renderers.push_back(renderer); 2059 } 2060 // DraggedTabView takes ownership of the renderers. 2061 view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_, 2062 content_bounds.size(), photobooth)); 2063 #else 2064 // Aura always hits the |detach_into_browser_| path. 2065 NOTREACHED(); 2066 #endif 2067 } 2068 2069 gfx::Rect TabDragController::GetViewScreenBounds( 2070 views::View* view) const { 2071 gfx::Point view_topleft; 2072 views::View::ConvertPointToScreen(view, &view_topleft); 2073 gfx::Rect view_screen_bounds = view->GetLocalBounds(); 2074 view_screen_bounds.Offset(view_topleft.x(), view_topleft.y()); 2075 return view_screen_bounds; 2076 } 2077 2078 void TabDragController::HideFrame() { 2079 #if defined(OS_WIN) && !defined(USE_AURA) 2080 // We don't actually hide the window, rather we just move it way off-screen. 2081 // If we actually hide it, we stop receiving drag events. 2082 // 2083 // Windows coordinates are 16 bit values. Additionally mouse events are 2084 // relative, this means if we move this window to the max position it is easy 2085 // to trigger overflow. To avoid this we don't move to the max position, 2086 // rather some where reasonably large. This should avoid common overflow 2087 // problems. 2088 // An alternative approach is to query the mouse pointer and ignore the 2089 // location on the mouse (early versions did this). This proves problematic as 2090 // if we happen to get behind in event processing it is all to easy to process 2091 // a release in the wrong location, triggering either an unexpected move or an 2092 // unexpected detach. 2093 HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView(); 2094 RECT wr; 2095 GetWindowRect(frame_hwnd, &wr); 2096 MoveWindow(frame_hwnd, 0x3FFF, 0x3FFF, wr.right - wr.left, 2097 wr.bottom - wr.top, TRUE); 2098 2099 // We also save the bounds of the window prior to it being moved, so that if 2100 // the drag session is aborted we can restore them. 2101 restore_bounds_ = gfx::Rect(wr); 2102 #else 2103 // Shouldn't hit as aura triggers the |detach_into_browser_| path. 2104 NOTREACHED(); 2105 #endif 2106 } 2107 2108 void TabDragController::CleanUpHiddenFrame() { 2109 // If the model we started dragging from is now empty, we must ask the 2110 // delegate to close the frame. 2111 if (!detach_into_browser_ && GetModel(source_tabstrip_)->empty()) 2112 GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession(); 2113 } 2114 2115 void TabDragController::DockDisplayerDestroyed( 2116 DockDisplayer* controller) { 2117 DockWindows::iterator dock_i = 2118 dock_windows_.find(controller->popup_view()); 2119 if (dock_i != dock_windows_.end()) 2120 dock_windows_.erase(dock_i); 2121 else 2122 NOTREACHED(); 2123 2124 std::vector<DockDisplayer*>::iterator i = 2125 std::find(dock_controllers_.begin(), dock_controllers_.end(), 2126 controller); 2127 if (i != dock_controllers_.end()) 2128 dock_controllers_.erase(i); 2129 else 2130 NOTREACHED(); 2131 } 2132 2133 void TabDragController::BringWindowUnderPointToFront( 2134 const gfx::Point& point_in_screen) { 2135 // If we're going to dock to another window, bring it to the front. 2136 gfx::NativeWindow window = dock_info_.window(); 2137 if (!window) { 2138 views::View* dragged_view; 2139 if (view_.get()) 2140 dragged_view = view_.get(); 2141 else 2142 dragged_view = attached_tabstrip_; 2143 gfx::NativeView dragged_native_view = 2144 dragged_view->GetWidget()->GetNativeView(); 2145 dock_windows_.insert(dragged_native_view); 2146 window = DockInfo::GetLocalProcessWindowAtPoint( 2147 host_desktop_type_, 2148 point_in_screen, 2149 dock_windows_); 2150 dock_windows_.erase(dragged_native_view); 2151 // Only bring browser windows to front - only windows with a TabStrip can 2152 // be tab drag targets. 2153 if (!GetTabStripForWindow(window)) 2154 return; 2155 } 2156 if (window) { 2157 views::Widget* widget_window = views::Widget::GetWidgetForNativeView( 2158 window); 2159 if (!widget_window) 2160 return; 2161 2162 #if defined(USE_ASH) 2163 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) { 2164 // TODO(varkha): The code below ensures that the phantom drag widget 2165 // is shown on top of browser windows. The code should be moved to ash/ 2166 // and the phantom should be able to assert its top-most state on its own. 2167 // One strategy would be for DragWindowController to 2168 // be able to observe stacking changes to the phantom drag widget's 2169 // siblings in order to keep it on top. One way is to implement a 2170 // notification that is sent to a window parent's observers when a 2171 // stacking order is changed among the children of that same parent. 2172 // Note that OnWindowStackingChanged is sent only to the child that is the 2173 // argument of one of the Window::StackChildX calls and not to all its 2174 // siblings affected by the stacking change. 2175 aura::Window* browser_window = widget_window->GetNativeView(); 2176 // Find a topmost non-popup window and stack the recipient browser above 2177 // it in order to avoid stacking the browser window on top of the phantom 2178 // drag widget created by DragWindowController in a second display. 2179 for (aura::Window::Windows::const_reverse_iterator it = 2180 browser_window->parent()->children().rbegin(); 2181 it != browser_window->parent()->children().rend(); ++it) { 2182 // If the iteration reached the recipient browser window then it is 2183 // already topmost and it is safe to return with no stacking change. 2184 if (*it == browser_window) 2185 return; 2186 if ((*it)->type() != aura::client::WINDOW_TYPE_POPUP) { 2187 widget_window->StackAbove(*it); 2188 break; 2189 } 2190 } 2191 } else { 2192 widget_window->StackAtTop(); 2193 } 2194 #else 2195 widget_window->StackAtTop(); 2196 #endif 2197 2198 // The previous call made the window appear on top of the dragged window, 2199 // move the dragged window to the front. 2200 if (view_.get()) 2201 view_->GetWidget()->StackAtTop(); 2202 else if (is_dragging_window_) 2203 attached_tabstrip_->GetWidget()->StackAtTop(); 2204 } 2205 } 2206 2207 TabStripModel* TabDragController::GetModel( 2208 TabStrip* tabstrip) const { 2209 return static_cast<BrowserTabStripController*>(tabstrip->controller())-> 2210 model(); 2211 } 2212 2213 views::Widget* TabDragController::GetAttachedBrowserWidget() { 2214 return attached_tabstrip_->GetWidget(); 2215 } 2216 2217 bool TabDragController::AreTabsConsecutive() { 2218 for (size_t i = 1; i < drag_data_.size(); ++i) { 2219 if (drag_data_[i - 1].source_model_index + 1 != 2220 drag_data_[i].source_model_index) { 2221 return false; 2222 } 2223 } 2224 return true; 2225 } 2226 2227 gfx::Rect TabDragController::CalculateDraggedBrowserBounds( 2228 TabStrip* source, 2229 const gfx::Point& point_in_screen, 2230 std::vector<gfx::Rect>* drag_bounds) { 2231 gfx::Point center(0, source->height() / 2); 2232 views::View::ConvertPointToWidget(source, ¢er); 2233 gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds()); 2234 if (source->GetWidget()->IsMaximized()) { 2235 // If the restore bounds is really small, we don't want to honor it 2236 // (dragging a really small window looks wrong), instead make sure the new 2237 // window is at least 50% the size of the old. 2238 const gfx::Size max_size( 2239 source->GetWidget()->GetWindowBoundsInScreen().size()); 2240 new_bounds.set_width( 2241 std::max(max_size.width() / 2, new_bounds.width())); 2242 new_bounds.set_height( 2243 std::max(max_size.height() / 2, new_bounds.height())); 2244 } 2245 new_bounds.set_y(point_in_screen.y() - center.y()); 2246 switch (GetDetachPosition(point_in_screen)) { 2247 case DETACH_BEFORE: 2248 new_bounds.set_x(point_in_screen.x() - center.x()); 2249 new_bounds.Offset(-mouse_offset_.x(), 0); 2250 break; 2251 case DETACH_AFTER: { 2252 gfx::Point right_edge(source->width(), 0); 2253 views::View::ConvertPointToWidget(source, &right_edge); 2254 new_bounds.set_x(point_in_screen.x() - right_edge.x()); 2255 new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0); 2256 OffsetX(-(*drag_bounds)[0].x(), drag_bounds); 2257 break; 2258 } 2259 default: 2260 break; // Nothing to do for DETACH_ABOVE_OR_BELOW. 2261 } 2262 2263 // To account for the extra vertical on restored windows that is absent on 2264 // maximized windows, add an additional vertical offset extracted from the tab 2265 // strip. 2266 if (source->GetWidget()->IsMaximized()) 2267 new_bounds.Offset(0, -source->button_v_offset()); 2268 return new_bounds; 2269 } 2270 2271 void TabDragController::AdjustBrowserAndTabBoundsForDrag( 2272 int last_tabstrip_width, 2273 const gfx::Point& point_in_screen, 2274 std::vector<gfx::Rect>* drag_bounds) { 2275 attached_tabstrip_->InvalidateLayout(); 2276 attached_tabstrip_->DoLayout(); 2277 const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width(); 2278 2279 // If the new tabstrip is smaller than the old resize the tabs. 2280 if (dragged_tabstrip_width < last_tabstrip_width) { 2281 const float leading_ratio = 2282 drag_bounds->front().x() / static_cast<float>(last_tabstrip_width); 2283 *drag_bounds = CalculateBoundsForDraggedTabs(); 2284 2285 if (drag_bounds->back().right() < dragged_tabstrip_width) { 2286 const int delta_x = 2287 std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width), 2288 dragged_tabstrip_width - 2289 (drag_bounds->back().right() - 2290 drag_bounds->front().x())); 2291 OffsetX(delta_x, drag_bounds); 2292 } 2293 2294 // Reposition the restored window such that the tab that was dragged remains 2295 // under the mouse cursor. 2296 gfx::Point offset( 2297 static_cast<int>((*drag_bounds)[source_tab_index_].width() * 2298 offset_to_width_ratio_) + 2299 (*drag_bounds)[source_tab_index_].x(), 0); 2300 views::View::ConvertPointToWidget(attached_tabstrip_, &offset); 2301 gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen(); 2302 bounds.set_x(point_in_screen.x() - offset.x()); 2303 GetAttachedBrowserWidget()->SetBounds(bounds); 2304 } 2305 attached_tabstrip_->SetTabBoundsForDrag(*drag_bounds); 2306 } 2307 2308 Browser* TabDragController::CreateBrowserForDrag( 2309 TabStrip* source, 2310 const gfx::Point& point_in_screen, 2311 gfx::Vector2d* drag_offset, 2312 std::vector<gfx::Rect>* drag_bounds) { 2313 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source, 2314 point_in_screen, 2315 drag_bounds)); 2316 *drag_offset = point_in_screen - new_bounds.origin(); 2317 2318 Profile* profile = 2319 Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext()); 2320 Browser::CreateParams create_params(Browser::TYPE_TABBED, 2321 profile, 2322 host_desktop_type_); 2323 create_params.initial_bounds = new_bounds; 2324 Browser* browser = new Browser(create_params); 2325 is_dragging_new_browser_ = true; 2326 SetWindowPositionManaged(browser->window()->GetNativeWindow(), false); 2327 // If the window is created maximized then the bounds we supplied are ignored. 2328 // We need to reset them again so they are honored. 2329 browser->window()->SetBounds(new_bounds); 2330 2331 return browser; 2332 } 2333 2334 gfx::Point TabDragController::GetCursorScreenPoint() { 2335 #if defined(USE_ASH) 2336 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH && 2337 event_source_ == EVENT_SOURCE_TOUCH && 2338 aura::Env::GetInstance()->is_touch_down()) { 2339 views::Widget* widget = GetAttachedBrowserWidget(); 2340 DCHECK(widget); 2341 aura::Window* widget_window = widget->GetNativeWindow(); 2342 DCHECK(widget_window->GetRootWindow()); 2343 gfx::Point touch_point; 2344 bool got_touch_point = ui::GestureRecognizer::Get()-> 2345 GetLastTouchPointForTarget(widget_window, &touch_point); 2346 DCHECK(got_touch_point); 2347 ash::wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point); 2348 return touch_point; 2349 } 2350 #endif 2351 return screen_->GetCursorScreenPoint(); 2352 } 2353 2354 gfx::Vector2d TabDragController::GetWindowOffset( 2355 const gfx::Point& point_in_screen) { 2356 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ? 2357 attached_tabstrip_ : source_tabstrip_; 2358 views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView(); 2359 2360 gfx::Point point = point_in_screen; 2361 views::View::ConvertPointFromScreen(toplevel_view, &point); 2362 return point.OffsetFromOrigin(); 2363 } 2364