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