1 // Copyright (c) 2011 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/dragged_tab_controller.h" 6 7 #include <math.h> 8 #include <set> 9 10 #include "base/callback.h" 11 #include "base/i18n/rtl.h" 12 #include "chrome/browser/extensions/extension_function_dispatcher.h" 13 #include "chrome/browser/metrics/user_metrics.h" 14 #include "chrome/browser/tabs/tab_strip_model.h" 15 #include "chrome/browser/ui/browser_window.h" 16 #include "chrome/browser/ui/views/frame/browser_view.h" 17 #include "chrome/browser/ui/views/tabs/base_tab.h" 18 #include "chrome/browser/ui/views/tabs/base_tab_strip.h" 19 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" 20 #include "chrome/browser/ui/views/tabs/dragged_tab_view.h" 21 #include "chrome/browser/ui/views/tabs/native_view_photobooth.h" 22 #include "chrome/browser/ui/views/tabs/side_tab.h" 23 #include "chrome/browser/ui/views/tabs/side_tab_strip.h" 24 #include "chrome/browser/ui/views/tabs/tab.h" 25 #include "chrome/browser/ui/views/tabs/tab_strip.h" 26 #include "content/browser/tab_contents/tab_contents.h" 27 #include "content/common/notification_details.h" 28 #include "content/common/notification_source.h" 29 #include "grit/theme_resources.h" 30 #include "third_party/skia/include/core/SkBitmap.h" 31 #include "ui/base/animation/animation.h" 32 #include "ui/base/animation/animation_delegate.h" 33 #include "ui/base/animation/slide_animation.h" 34 #include "ui/base/resource/resource_bundle.h" 35 #include "ui/gfx/canvas_skia.h" 36 #include "views/events/event.h" 37 #include "views/screen.h" 38 #include "views/widget/root_view.h" 39 #include "views/widget/widget.h" 40 #include "views/window/window.h" 41 42 #if defined(OS_WIN) 43 #include "views/widget/widget_win.h" 44 #endif 45 46 #if defined(OS_LINUX) 47 #include <gdk/gdk.h> // NOLINT 48 #include <gdk/gdkkeysyms.h> // NOLINT 49 #endif 50 51 static const int kHorizontalMoveThreshold = 16; // Pixels. 52 53 // Distance in pixels the user must move the mouse before we consider moving 54 // an attached vertical tab. 55 static const int kVerticalMoveThreshold = 8; 56 57 // If non-null there is a drag underway. 58 static DraggedTabController* instance_; 59 60 namespace { 61 62 // Delay, in ms, during dragging before we bring a window to front. 63 const int kBringToFrontDelay = 750; 64 65 // Radius of the rect drawn by DockView. 66 const int kRoundedRectRadius = 4; 67 68 // Spacing between tab icons when DockView is showing a docking location that 69 // contains more than one tab. 70 const int kTabSpacing = 4; 71 72 // DockView is the view responsible for giving a visual indicator of where a 73 // dock is going to occur. 74 75 class DockView : public views::View { 76 public: 77 explicit DockView(DockInfo::Type type) : type_(type) {} 78 79 virtual gfx::Size GetPreferredSize() { 80 return gfx::Size(DockInfo::popup_width(), DockInfo::popup_height()); 81 } 82 83 virtual void OnPaintBackground(gfx::Canvas* canvas) { 84 SkRect outer_rect = { SkIntToScalar(0), SkIntToScalar(0), 85 SkIntToScalar(width()), 86 SkIntToScalar(height()) }; 87 88 // Fill the background rect. 89 SkPaint paint; 90 paint.setColor(SkColorSetRGB(108, 108, 108)); 91 paint.setStyle(SkPaint::kFill_Style); 92 canvas->AsCanvasSkia()->drawRoundRect( 93 outer_rect, SkIntToScalar(kRoundedRectRadius), 94 SkIntToScalar(kRoundedRectRadius), paint); 95 96 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 97 98 SkBitmap* high_icon = rb.GetBitmapNamed(IDR_DOCK_HIGH); 99 SkBitmap* wide_icon = rb.GetBitmapNamed(IDR_DOCK_WIDE); 100 101 canvas->Save(); 102 bool rtl_ui = base::i18n::IsRTL(); 103 if (rtl_ui) { 104 // Flip canvas to draw the mirrored tab images for RTL UI. 105 canvas->TranslateInt(width(), 0); 106 canvas->ScaleInt(-1, 1); 107 } 108 int x_of_active_tab = width() / 2 + kTabSpacing / 2; 109 int x_of_inactive_tab = width() / 2 - high_icon->width() - kTabSpacing / 2; 110 switch (type_) { 111 case DockInfo::LEFT_OF_WINDOW: 112 case DockInfo::LEFT_HALF: 113 if (!rtl_ui) 114 std::swap(x_of_active_tab, x_of_inactive_tab); 115 canvas->DrawBitmapInt(*high_icon, x_of_active_tab, 116 (height() - high_icon->height()) / 2); 117 if (type_ == DockInfo::LEFT_OF_WINDOW) { 118 DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab, 119 (height() - high_icon->height()) / 2); 120 } 121 break; 122 123 124 case DockInfo::RIGHT_OF_WINDOW: 125 case DockInfo::RIGHT_HALF: 126 if (rtl_ui) 127 std::swap(x_of_active_tab, x_of_inactive_tab); 128 canvas->DrawBitmapInt(*high_icon, x_of_active_tab, 129 (height() - high_icon->height()) / 2); 130 if (type_ == DockInfo::RIGHT_OF_WINDOW) { 131 DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab, 132 (height() - high_icon->height()) / 2); 133 } 134 break; 135 136 case DockInfo::TOP_OF_WINDOW: 137 canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2, 138 height() / 2 - high_icon->height()); 139 break; 140 141 case DockInfo::MAXIMIZE: { 142 SkBitmap* max_icon = rb.GetBitmapNamed(IDR_DOCK_MAX); 143 canvas->DrawBitmapInt(*max_icon, (width() - max_icon->width()) / 2, 144 (height() - max_icon->height()) / 2); 145 break; 146 } 147 148 case DockInfo::BOTTOM_HALF: 149 case DockInfo::BOTTOM_OF_WINDOW: 150 canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2, 151 height() / 2 + kTabSpacing / 2); 152 if (type_ == DockInfo::BOTTOM_OF_WINDOW) { 153 DrawBitmapWithAlpha(canvas, *wide_icon, 154 (width() - wide_icon->width()) / 2, 155 height() / 2 - kTabSpacing / 2 - wide_icon->height()); 156 } 157 break; 158 159 default: 160 NOTREACHED(); 161 break; 162 } 163 canvas->Restore(); 164 } 165 166 private: 167 void DrawBitmapWithAlpha(gfx::Canvas* canvas, const SkBitmap& image, 168 int x, int y) { 169 SkPaint paint; 170 paint.setAlpha(128); 171 canvas->DrawBitmapInt(image, x, y, paint); 172 } 173 174 DockInfo::Type type_; 175 176 DISALLOW_COPY_AND_ASSIGN(DockView); 177 }; 178 179 // Returns the the x-coordinate of |point| if the type of tabstrip is horizontal 180 // otherwise returns the y-coordinate. 181 int MajorAxisValue(const gfx::Point& point, BaseTabStrip* tabstrip) { 182 return (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) ? 183 point.x() : point.y(); 184 } 185 186 } // namespace 187 188 /////////////////////////////////////////////////////////////////////////////// 189 // DockDisplayer 190 191 // DockDisplayer is responsible for giving the user a visual indication of a 192 // possible dock position (as represented by DockInfo). DockDisplayer shows 193 // a window with a DockView in it. Two animations are used that correspond to 194 // the state of DockInfo::in_enable_area. 195 class DraggedTabController::DockDisplayer : public ui::AnimationDelegate { 196 public: 197 DockDisplayer(DraggedTabController* controller, 198 const DockInfo& info) 199 : controller_(controller), 200 popup_(NULL), 201 popup_view_(NULL), 202 ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)), 203 hidden_(false), 204 in_enable_area_(info.in_enable_area()) { 205 #if defined(OS_WIN) 206 // TODO(sky): This should "just work" on Gtk now. 207 views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP); 208 params.transparent = true; 209 params.keep_on_top = true; 210 popup_ = views::Widget::CreateWidget(params); 211 popup_->SetOpacity(0x00); 212 popup_->Init(NULL, info.GetPopupRect()); 213 popup_->SetContentsView(new DockView(info.type())); 214 if (info.in_enable_area()) 215 animation_.Reset(1); 216 else 217 animation_.Show(); 218 popup_->Show(); 219 #else 220 NOTIMPLEMENTED(); 221 #endif 222 popup_view_ = popup_->GetNativeView(); 223 } 224 225 ~DockDisplayer() { 226 if (controller_) 227 controller_->DockDisplayerDestroyed(this); 228 } 229 230 // Updates the state based on |in_enable_area|. 231 void UpdateInEnabledArea(bool in_enable_area) { 232 if (in_enable_area != in_enable_area_) { 233 in_enable_area_ = in_enable_area; 234 UpdateLayeredAlpha(); 235 } 236 } 237 238 // Resets the reference to the hosting DraggedTabController. This is invoked 239 // when the DraggedTabController is destoryed. 240 void clear_controller() { controller_ = NULL; } 241 242 // NativeView of the window we create. 243 gfx::NativeView popup_view() { return popup_view_; } 244 245 // Starts the hide animation. When the window is closed the 246 // DraggedTabController is notified by way of the DockDisplayerDestroyed 247 // method 248 void Hide() { 249 if (hidden_) 250 return; 251 252 if (!popup_) { 253 delete this; 254 return; 255 } 256 hidden_ = true; 257 animation_.Hide(); 258 } 259 260 virtual void AnimationProgressed(const ui::Animation* animation) { 261 UpdateLayeredAlpha(); 262 } 263 264 virtual void AnimationEnded(const ui::Animation* animation) { 265 if (!hidden_) 266 return; 267 #if defined(OS_WIN) 268 static_cast<views::WidgetWin*>(popup_)->Close(); 269 #else 270 NOTIMPLEMENTED(); 271 #endif 272 delete this; 273 } 274 275 virtual void UpdateLayeredAlpha() { 276 #if defined(OS_WIN) 277 double scale = in_enable_area_ ? 1 : .5; 278 static_cast<views::WidgetWin*>(popup_)->SetOpacity( 279 static_cast<BYTE>(animation_.GetCurrentValue() * scale * 255.0)); 280 popup_->GetRootView()->SchedulePaint(); 281 #else 282 NOTIMPLEMENTED(); 283 #endif 284 } 285 286 private: 287 // DraggedTabController that created us. 288 DraggedTabController* controller_; 289 290 // Window we're showing. 291 views::Widget* popup_; 292 293 // NativeView of |popup_|. We cache this to avoid the possibility of 294 // invoking a method on popup_ after we close it. 295 gfx::NativeView popup_view_; 296 297 // Animation for when first made visible. 298 ui::SlideAnimation animation_; 299 300 // Have we been hidden? 301 bool hidden_; 302 303 // Value of DockInfo::in_enable_area. 304 bool in_enable_area_; 305 }; 306 307 DraggedTabController::TabDragData::TabDragData() 308 : contents(NULL), 309 original_delegate(NULL), 310 source_model_index(-1), 311 attached_tab(NULL), 312 pinned(false) { 313 } 314 315 DraggedTabController::TabDragData::~TabDragData() { 316 } 317 318 /////////////////////////////////////////////////////////////////////////////// 319 // DraggedTabController, public: 320 321 DraggedTabController::DraggedTabController() 322 : source_tabstrip_(NULL), 323 attached_tabstrip_(NULL), 324 source_tab_offset_(0), 325 offset_to_width_ratio_(0), 326 old_focused_view_(NULL), 327 last_move_screen_loc_(0), 328 started_drag_(false), 329 active_(true), 330 source_tab_index_(std::numeric_limits<size_t>::max()), 331 initial_move_(true) { 332 instance_ = this; 333 } 334 335 DraggedTabController::~DraggedTabController() { 336 if (instance_ == this) 337 instance_ = NULL; 338 339 MessageLoopForUI::current()->RemoveObserver(this); 340 // Need to delete the view here manually _before_ we reset the dragged 341 // contents to NULL, otherwise if the view is animating to its destination 342 // bounds, it won't be able to clean up properly since its cleanup routine 343 // uses GetIndexForDraggedContents, which will be invalid. 344 view_.reset(NULL); 345 346 // Reset the delegate of the dragged TabContents. This ends up doing nothing 347 // if the drag was completed. 348 ResetDelegates(); 349 } 350 351 void DraggedTabController::Init(BaseTabStrip* source_tabstrip, 352 BaseTab* source_tab, 353 const std::vector<BaseTab*>& tabs, 354 const gfx::Point& mouse_offset, 355 int source_tab_offset) { 356 DCHECK(!tabs.empty()); 357 DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); 358 source_tabstrip_ = source_tabstrip; 359 source_tab_offset_ = source_tab_offset; 360 start_screen_point_ = GetCursorScreenPoint(); 361 mouse_offset_ = mouse_offset; 362 363 drag_data_.resize(tabs.size()); 364 for (size_t i = 0; i < tabs.size(); ++i) 365 InitTabDragData(tabs[i], &(drag_data_[i])); 366 source_tab_index_ = 367 std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin(); 368 369 // Listen for Esc key presses. 370 MessageLoopForUI::current()->AddObserver(this); 371 372 if (source_tab->width() > 0) { 373 offset_to_width_ratio_ = static_cast<float>(source_tab_offset_) / 374 static_cast<float>(source_tab->width()); 375 } 376 InitWindowCreatePoint(); 377 } 378 379 // static 380 bool DraggedTabController::IsAttachedTo(BaseTabStrip* tab_strip) { 381 return instance_ && instance_->active_ && 382 instance_->attached_tabstrip_ == tab_strip; 383 } 384 385 void DraggedTabController::Drag() { 386 bring_to_front_timer_.Stop(); 387 388 if (!started_drag_) { 389 if (!CanStartDrag()) 390 return; // User hasn't dragged far enough yet. 391 392 started_drag_ = true; 393 SaveFocus(); 394 Attach(source_tabstrip_, gfx::Point()); 395 } 396 397 ContinueDragging(); 398 } 399 400 void DraggedTabController::EndDrag(bool canceled) { 401 EndDragImpl(canceled ? CANCELED : NORMAL); 402 } 403 404 void DraggedTabController::InitTabDragData(BaseTab* tab, 405 TabDragData* drag_data) { 406 drag_data->source_model_index = 407 source_tabstrip_->GetModelIndexOfBaseTab(tab); 408 drag_data->contents = GetModel(source_tabstrip_)->GetTabContentsAt( 409 drag_data->source_model_index); 410 drag_data->pinned = source_tabstrip_->IsTabPinned(tab); 411 registrar_.Add(this, 412 NotificationType::TAB_CONTENTS_DESTROYED, 413 Source<TabContents>(drag_data->contents->tab_contents())); 414 415 // We need to be the delegate so we receive messages about stuff, otherwise 416 // our dragged TabContents may be replaced and subsequently 417 // collected/destroyed while the drag is in process, leading to nasty crashes. 418 drag_data->original_delegate = 419 drag_data->contents->tab_contents()->delegate(); 420 drag_data->contents->tab_contents()->set_delegate(this); 421 } 422 423 /////////////////////////////////////////////////////////////////////////////// 424 // DraggedTabController, PageNavigator implementation: 425 426 void DraggedTabController::OpenURLFromTab(TabContents* source, 427 const GURL& url, 428 const GURL& referrer, 429 WindowOpenDisposition disposition, 430 PageTransition::Type transition) { 431 if (source_tab_drag_data()->original_delegate) { 432 if (disposition == CURRENT_TAB) 433 disposition = NEW_WINDOW; 434 435 source_tab_drag_data()->original_delegate->OpenURLFromTab( 436 source, url, referrer, disposition, transition); 437 } 438 } 439 440 /////////////////////////////////////////////////////////////////////////////// 441 // DraggedTabController, TabContentsDelegate implementation: 442 443 void DraggedTabController::NavigationStateChanged(const TabContents* source, 444 unsigned changed_flags) { 445 if (view_.get()) 446 view_->Update(); 447 } 448 449 void DraggedTabController::AddNewContents(TabContents* source, 450 TabContents* new_contents, 451 WindowOpenDisposition disposition, 452 const gfx::Rect& initial_pos, 453 bool user_gesture) { 454 DCHECK_NE(CURRENT_TAB, disposition); 455 456 // Theoretically could be called while dragging if the page tries to 457 // spawn a window. Route this message back to the browser in most cases. 458 if (source_tab_drag_data()->original_delegate) { 459 source_tab_drag_data()->original_delegate->AddNewContents( 460 source, new_contents, disposition, initial_pos, user_gesture); 461 } 462 } 463 464 void DraggedTabController::ActivateContents(TabContents* contents) { 465 // Ignored. 466 } 467 468 void DraggedTabController::DeactivateContents(TabContents* contents) { 469 // Ignored. 470 } 471 472 void DraggedTabController::LoadingStateChanged(TabContents* source) { 473 // It would be nice to respond to this message by changing the 474 // screen shot in the dragged tab. 475 if (view_.get()) 476 view_->Update(); 477 } 478 479 void DraggedTabController::CloseContents(TabContents* source) { 480 // Theoretically could be called by a window. Should be ignored 481 // because window.close() is ignored (usually, even though this 482 // method gets called.) 483 } 484 485 void DraggedTabController::MoveContents(TabContents* source, 486 const gfx::Rect& pos) { 487 // Theoretically could be called by a web page trying to move its 488 // own window. Should be ignored since we're moving the window... 489 } 490 491 void DraggedTabController::UpdateTargetURL(TabContents* source, 492 const GURL& url) { 493 // Ignored. 494 } 495 496 bool DraggedTabController::ShouldSuppressDialogs() { 497 // When a dialog is about to be shown we revert the drag. Otherwise a modal 498 // dialog might appear and attempt to parent itself to a hidden tabcontents. 499 EndDragImpl(CANCELED); 500 return false; 501 } 502 503 /////////////////////////////////////////////////////////////////////////////// 504 // DraggedTabController, NotificationObserver implementation: 505 506 void DraggedTabController::Observe(NotificationType type, 507 const NotificationSource& source, 508 const NotificationDetails& details) { 509 DCHECK_EQ(type.value, NotificationType::TAB_CONTENTS_DESTROYED); 510 TabContents* destroyed_contents = Source<TabContents>(source).ptr(); 511 for (size_t i = 0; i < drag_data_.size(); ++i) { 512 if (drag_data_[i].contents->tab_contents() == destroyed_contents) { 513 // One of the tabs we're dragging has been destroyed. Cancel the drag. 514 if (destroyed_contents->delegate() == this) 515 destroyed_contents->set_delegate(NULL); 516 drag_data_[i].contents = NULL; 517 drag_data_[i].original_delegate = NULL; 518 EndDragImpl(TAB_DESTROYED); 519 return; 520 } 521 } 522 // If we get here it means we got notification for a tab we don't know about. 523 NOTREACHED(); 524 } 525 526 /////////////////////////////////////////////////////////////////////////////// 527 // DraggedTabController, MessageLoop::Observer implementation: 528 529 #if defined(OS_WIN) 530 void DraggedTabController::WillProcessMessage(const MSG& msg) { 531 } 532 533 void DraggedTabController::DidProcessMessage(const MSG& msg) { 534 // If the user presses ESC during a drag, we need to abort and revert things 535 // to the way they were. This is the most reliable way to do this since no 536 // single view or window reliably receives events throughout all the various 537 // kinds of tab dragging. 538 if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) 539 EndDrag(true); 540 } 541 #else 542 void DraggedTabController::WillProcessEvent(GdkEvent* event) { 543 } 544 545 void DraggedTabController::DidProcessEvent(GdkEvent* event) { 546 if (event->type == GDK_KEY_PRESS && 547 reinterpret_cast<GdkEventKey*>(event)->keyval == GDK_Escape) { 548 EndDrag(true); 549 } 550 } 551 #endif 552 553 /////////////////////////////////////////////////////////////////////////////// 554 // DraggedTabController, private: 555 556 void DraggedTabController::InitWindowCreatePoint() { 557 // window_create_point_ is only used in CompleteDrag() (through 558 // GetWindowCreatePoint() to get the start point of the docked window) when 559 // the attached_tabstrip_ is NULL and all the window's related bound 560 // information are obtained from source_tabstrip_. So, we need to get the 561 // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise, 562 // the window_create_point_ is not in the correct coordinate system. Please 563 // refer to http://crbug.com/6223 comment #15 for detailed information. 564 views::View* first_tab = source_tabstrip_->base_tab_at_tab_index(0); 565 views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_); 566 window_create_point_ = first_source_tab_point_; 567 window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y()); 568 } 569 570 gfx::Point DraggedTabController::GetWindowCreatePoint() const { 571 gfx::Point cursor_point = GetCursorScreenPoint(); 572 if (dock_info_.type() != DockInfo::NONE && dock_info_.in_enable_area()) { 573 // If we're going to dock, we need to return the exact coordinate, 574 // otherwise we may attempt to maximize on the wrong monitor. 575 return cursor_point; 576 } 577 // If the cursor is outside the monitor area, move it inside. For example, 578 // dropping a tab onto the task bar on Windows produces this situation. 579 gfx::Rect work_area = views::Screen::GetMonitorWorkAreaNearestPoint( 580 cursor_point); 581 if (!work_area.IsEmpty()) { 582 if (cursor_point.x() < work_area.x()) 583 cursor_point.set_x(work_area.x()); 584 else if (cursor_point.x() > work_area.right()) 585 cursor_point.set_x(work_area.right()); 586 if (cursor_point.y() < work_area.y()) 587 cursor_point.set_y(work_area.y()); 588 else if (cursor_point.y() > work_area.bottom()) 589 cursor_point.set_y(work_area.bottom()); 590 } 591 return gfx::Point(cursor_point.x() - window_create_point_.x(), 592 cursor_point.y() - window_create_point_.y()); 593 } 594 595 void DraggedTabController::UpdateDockInfo(const gfx::Point& screen_point) { 596 // Update the DockInfo for the current mouse coordinates. 597 DockInfo dock_info = GetDockInfoAtPoint(screen_point); 598 if (source_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP && 599 ((dock_info.type() == DockInfo::LEFT_OF_WINDOW && 600 !base::i18n::IsRTL()) || 601 (dock_info.type() == DockInfo::RIGHT_OF_WINDOW && 602 base::i18n::IsRTL()))) { 603 // For side tabs it's way to easy to trigger to docking along the left/right 604 // edge, so we disable it. 605 dock_info = DockInfo(); 606 } 607 if (!dock_info.equals(dock_info_)) { 608 // DockInfo for current position differs. 609 if (dock_info_.type() != DockInfo::NONE && 610 !dock_controllers_.empty()) { 611 // Hide old visual indicator. 612 dock_controllers_.back()->Hide(); 613 } 614 dock_info_ = dock_info; 615 if (dock_info_.type() != DockInfo::NONE) { 616 // Show new docking position. 617 DockDisplayer* controller = new DockDisplayer(this, dock_info_); 618 if (controller->popup_view()) { 619 dock_controllers_.push_back(controller); 620 dock_windows_.insert(controller->popup_view()); 621 } else { 622 delete controller; 623 } 624 } 625 } else if (dock_info_.type() != DockInfo::NONE && 626 !dock_controllers_.empty()) { 627 // Current dock position is the same as last, update the controller's 628 // in_enable_area state as it may have changed. 629 dock_controllers_.back()->UpdateInEnabledArea(dock_info_.in_enable_area()); 630 } 631 } 632 633 void DraggedTabController::SaveFocus() { 634 DCHECK(!old_focused_view_); // This should only be invoked once. 635 old_focused_view_ = source_tabstrip_->GetFocusManager()->GetFocusedView(); 636 source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_); 637 } 638 639 void DraggedTabController::RestoreFocus() { 640 if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_) 641 old_focused_view_->GetFocusManager()->SetFocusedView(old_focused_view_); 642 old_focused_view_ = NULL; 643 } 644 645 bool DraggedTabController::CanStartDrag() const { 646 // Determine if the mouse has moved beyond a minimum elasticity distance in 647 // any direction from the starting point. 648 static const int kMinimumDragDistance = 10; 649 gfx::Point screen_point = GetCursorScreenPoint(); 650 int x_offset = abs(screen_point.x() - start_screen_point_.x()); 651 int y_offset = abs(screen_point.y() - start_screen_point_.y()); 652 return sqrt(pow(static_cast<float>(x_offset), 2) + 653 pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance; 654 } 655 656 void DraggedTabController::ContinueDragging() { 657 // Note that the coordinates given to us by |drag_event| are basically 658 // useless, since they're in source_tab_ coordinates. On the surface, you'd 659 // think we could just convert them to screen coordinates, however in the 660 // situation where we're dragging the last tab in a window when multiple 661 // windows are open, the coordinates of |source_tab_| are way off in 662 // hyperspace since the window was moved there instead of being closed so 663 // that we'd keep receiving events. And our ConvertPointToScreen methods 664 // aren't really multi-screen aware. So really it's just safer to get the 665 // actual position of the mouse cursor directly from Windows here, which is 666 // guaranteed to be correct regardless of monitor config. 667 gfx::Point screen_point = GetCursorScreenPoint(); 668 669 #if defined(OS_LINUX) 670 // We don't allow detaching in chrome os. 671 BaseTabStrip* target_tabstrip = source_tabstrip_; 672 #else 673 // Determine whether or not we have dragged over a compatible TabStrip in 674 // another browser window. If we have, we should attach to it and start 675 // dragging within it. 676 BaseTabStrip* target_tabstrip = GetTabStripForPoint(screen_point); 677 #endif 678 if (target_tabstrip != attached_tabstrip_) { 679 // Make sure we're fully detached from whatever TabStrip we're attached to 680 // (if any). 681 if (attached_tabstrip_) 682 Detach(); 683 if (target_tabstrip) 684 Attach(target_tabstrip, screen_point); 685 } 686 if (!target_tabstrip) { 687 bring_to_front_timer_.Start( 688 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, 689 &DraggedTabController::BringWindowUnderMouseToFront); 690 } 691 692 UpdateDockInfo(screen_point); 693 694 if (attached_tabstrip_) 695 MoveAttached(screen_point); 696 else 697 MoveDetached(screen_point); 698 } 699 700 void DraggedTabController::MoveAttached(const gfx::Point& screen_point) { 701 DCHECK(attached_tabstrip_); 702 DCHECK(!view_.get()); 703 704 gfx::Point dragged_view_point = GetAttachedDragPoint(screen_point); 705 706 int threshold = kVerticalMoveThreshold; 707 if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 708 TabStrip* tab_strip = static_cast<TabStrip*>(attached_tabstrip_); 709 710 // Determine the horizontal move threshold. This is dependent on the width 711 // of tabs. The smaller the tabs compared to the standard size, the smaller 712 // the threshold. 713 double unselected, selected; 714 tab_strip->GetCurrentTabWidths(&unselected, &selected); 715 double ratio = unselected / Tab::GetStandardSize().width(); 716 threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); 717 } 718 719 std::vector<BaseTab*> tabs(drag_data_.size()); 720 for (size_t i = 0; i < drag_data_.size(); ++i) 721 tabs[i] = drag_data_[i].attached_tab; 722 723 bool did_layout = false; 724 // Update the model, moving the TabContents from one index to another. Do this 725 // only if we have moved a minimum distance since the last reorder (to prevent 726 // jitter) or if this the first move and the tabs are not consecutive. 727 if (abs(MajorAxisValue(screen_point, attached_tabstrip_) - 728 last_move_screen_loc_) > threshold || 729 (initial_move_ && !AreTabsConsecutive())) { 730 TabStripModel* attached_model = GetModel(attached_tabstrip_); 731 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 732 int to_index = GetInsertionIndexForDraggedBounds(bounds); 733 TabContentsWrapper* last_contents = 734 drag_data_[drag_data_.size() - 1].contents; 735 int index_of_last_item = 736 attached_model->GetIndexOfTabContents(last_contents); 737 if (initial_move_) { 738 // TabStrip determines if the tabs needs to be animated based on model 739 // position. This means we need to invoke LayoutDraggedTabsAt before 740 // changing the model. 741 attached_tabstrip_->LayoutDraggedTabsAt( 742 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 743 initial_move_); 744 did_layout = true; 745 } 746 attached_model->MoveSelectedTabsTo(to_index); 747 // Move may do nothing in certain situations (such as when dragging pinned 748 // tabs). Make sure the tabstrip actually changed before updating 749 // last_move_screen_loc_. 750 if (index_of_last_item != 751 attached_model->GetIndexOfTabContents(last_contents)) { 752 last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip_); 753 } 754 } 755 756 if (!did_layout) { 757 attached_tabstrip_->LayoutDraggedTabsAt( 758 tabs, source_tab_drag_data()->attached_tab, dragged_view_point, 759 initial_move_); 760 } 761 762 initial_move_ = false; 763 } 764 765 void DraggedTabController::MoveDetached(const gfx::Point& screen_point) { 766 DCHECK(!attached_tabstrip_); 767 DCHECK(view_.get()); 768 769 // Move the View. There are no changes to the model if we're detached. 770 view_->MoveTo(screen_point); 771 } 772 773 DockInfo DraggedTabController::GetDockInfoAtPoint( 774 const gfx::Point& screen_point) { 775 if (attached_tabstrip_) { 776 // If the mouse is over a tab strip, don't offer a dock position. 777 return DockInfo(); 778 } 779 780 if (dock_info_.IsValidForPoint(screen_point)) { 781 // It's possible any given screen coordinate has multiple docking 782 // positions. Check the current info first to avoid having the docking 783 // position bounce around. 784 return dock_info_; 785 } 786 787 gfx::NativeView dragged_hwnd = view_->GetWidget()->GetNativeView(); 788 dock_windows_.insert(dragged_hwnd); 789 DockInfo info = DockInfo::GetDockInfoAtPoint(screen_point, dock_windows_); 790 dock_windows_.erase(dragged_hwnd); 791 return info; 792 } 793 794 BaseTabStrip* DraggedTabController::GetTabStripForPoint( 795 const gfx::Point& screen_point) { 796 gfx::NativeView dragged_view = NULL; 797 if (view_.get()) { 798 dragged_view = view_->GetWidget()->GetNativeView(); 799 dock_windows_.insert(dragged_view); 800 } 801 gfx::NativeWindow local_window = 802 DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_); 803 if (dragged_view) 804 dock_windows_.erase(dragged_view); 805 if (!local_window) 806 return NULL; 807 BrowserView* browser = 808 BrowserView::GetBrowserViewForNativeWindow(local_window); 809 // We don't allow drops on windows that don't have tabstrips. 810 if (!browser || 811 !browser->browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP)) 812 return NULL; 813 814 // This cast seems ugly, but the controller and the view are tighly coupled at 815 // creation time, so it will be okay. 816 BaseTabStrip* other_tabstrip = 817 static_cast<BaseTabStrip*>(browser->tabstrip()); 818 819 if (!other_tabstrip->controller()->IsCompatibleWith(source_tabstrip_)) 820 return NULL; 821 return GetTabStripIfItContains(other_tabstrip, screen_point); 822 } 823 824 BaseTabStrip* DraggedTabController::GetTabStripIfItContains( 825 BaseTabStrip* tabstrip, 826 const gfx::Point& screen_point) const { 827 static const int kVerticalDetachMagnetism = 15; 828 static const int kHorizontalDetachMagnetism = 15; 829 // Make sure the specified screen point is actually within the bounds of the 830 // specified tabstrip... 831 gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip); 832 if (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 833 if (screen_point.x() < tabstrip_bounds.right() && 834 screen_point.x() >= tabstrip_bounds.x()) { 835 // TODO(beng): make this be relative to the start position of the mouse 836 // for the source TabStrip. 837 int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism; 838 int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism; 839 if (screen_point.y() >= lower_threshold && 840 screen_point.y() <= upper_threshold) { 841 return tabstrip; 842 } 843 } 844 } else { 845 if (screen_point.y() < tabstrip_bounds.bottom() && 846 screen_point.y() >= tabstrip_bounds.y()) { 847 int upper_threshold = tabstrip_bounds.right() + 848 kHorizontalDetachMagnetism; 849 int lower_threshold = tabstrip_bounds.x() - kHorizontalDetachMagnetism; 850 if (screen_point.x() >= lower_threshold && 851 screen_point.x() <= upper_threshold) { 852 return tabstrip; 853 } 854 } 855 } 856 return NULL; 857 } 858 859 void DraggedTabController::Attach(BaseTabStrip* attached_tabstrip, 860 const gfx::Point& screen_point) { 861 DCHECK(!attached_tabstrip_); // We should already have detached by the time 862 // we get here. 863 864 attached_tabstrip_ = attached_tabstrip; 865 866 // And we don't need the dragged view. 867 view_.reset(); 868 869 std::vector<BaseTab*> tabs = 870 GetTabsMatchingDraggedContents(attached_tabstrip_); 871 872 if (tabs.empty()) { 873 // There is no Tab in |attached_tabstrip| that corresponds to the dragged 874 // TabContents. We must now create one. 875 876 // Remove ourselves as the delegate now that the dragged TabContents is 877 // being inserted back into a Browser. 878 for (size_t i = 0; i < drag_data_.size(); ++i) { 879 drag_data_[i].contents->tab_contents()->set_delegate(NULL); 880 drag_data_[i].original_delegate = NULL; 881 } 882 883 // Return the TabContents' to normalcy. 884 source_dragged_contents()->tab_contents()->set_capturing_contents(false); 885 886 // Inserting counts as a move. We don't want the tabs to jitter when the 887 // user moves the tab immediately after attaching it. 888 last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip); 889 890 // Figure out where to insert the tab based on the bounds of the dragged 891 // representation and the ideal bounds of the other Tabs already in the 892 // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are 893 // changing due to animation). 894 gfx::Point tab_strip_point(screen_point); 895 views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_strip_point); 896 tab_strip_point.set_x( 897 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x())); 898 tab_strip_point.Offset(-mouse_offset_.x(), -mouse_offset_.y()); 899 gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point); 900 int index = GetInsertionIndexForDraggedBounds(bounds); 901 attached_tabstrip_->set_attaching_dragged_tab(true); 902 for (size_t i = 0; i < drag_data_.size(); ++i) { 903 int add_types = TabStripModel::ADD_NONE; 904 if (drag_data_[i].pinned) 905 add_types |= TabStripModel::ADD_PINNED; 906 GetModel(attached_tabstrip_)->InsertTabContentsAt( 907 index + i, drag_data_[i].contents, add_types); 908 } 909 attached_tabstrip_->set_attaching_dragged_tab(false); 910 911 tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); 912 } 913 DCHECK_EQ(tabs.size(), drag_data_.size()); 914 for (size_t i = 0; i < drag_data_.size(); ++i) 915 drag_data_[i].attached_tab = tabs[i]; 916 917 attached_tabstrip_->StartedDraggingTabs(tabs); 918 919 ResetSelection(GetModel(attached_tabstrip_)); 920 921 if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 922 // The size of the dragged tab may have changed. Adjust the x offset so that 923 // ratio of mouse_offset_ to original width is maintained. 924 std::vector<BaseTab*> tabs_to_source(tabs); 925 tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1, 926 tabs_to_source.end()); 927 int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) - 928 tabs[source_tab_index_]->width() + 929 static_cast<int>(offset_to_width_ratio_ * 930 tabs[source_tab_index_]->width()); 931 mouse_offset_.set_x(new_x); 932 } 933 934 // Move the corresponding window to the front. 935 attached_tabstrip_->GetWindow()->Activate(); 936 } 937 938 void DraggedTabController::Detach() { 939 // Prevent the TabContents' HWND from being hidden by any of the model 940 // operations performed during the drag. 941 source_dragged_contents()->tab_contents()->set_capturing_contents(true); 942 943 // Calculate the drag bounds. 944 std::vector<gfx::Rect> drag_bounds; 945 std::vector<BaseTab*> attached_tabs; 946 for (size_t i = 0; i < drag_data_.size(); ++i) 947 attached_tabs.push_back(drag_data_[i].attached_tab); 948 attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs, 949 &drag_bounds); 950 951 TabStripModel* attached_model = GetModel(attached_tabstrip_); 952 std::vector<TabRendererData> tab_data; 953 for (size_t i = 0; i < drag_data_.size(); ++i) { 954 tab_data.push_back(drag_data_[i].attached_tab->data()); 955 int index = attached_model->GetIndexOfTabContents(drag_data_[i].contents); 956 DCHECK_NE(-1, index); 957 958 // Hide the tab so that the user doesn't see it animate closed. 959 drag_data_[i].attached_tab->SetVisible(false); 960 961 attached_model->DetachTabContentsAt(index); 962 963 // Detaching resets the delegate, but we still want to be the delegate. 964 drag_data_[i].contents->tab_contents()->set_delegate(this); 965 966 // Detaching may end up deleting the tab, drop references to it. 967 drag_data_[i].attached_tab = NULL; 968 } 969 970 // If we've removed the last Tab from the TabStrip, hide the frame now. 971 if (attached_model->empty()) 972 HideFrame(); 973 974 // Create the dragged view. 975 CreateDraggedView(tab_data, drag_bounds); 976 977 attached_tabstrip_ = NULL; 978 } 979 980 int DraggedTabController::GetInsertionIndexForDraggedBounds( 981 const gfx::Rect& dragged_bounds) const { 982 int right_tab_x = 0; 983 int bottom_tab_y = 0; 984 int index = -1; 985 for (int i = 0; i < attached_tabstrip_->tab_count(); ++i) { 986 const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i); 987 if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 988 gfx::Rect left_half = ideal_bounds; 989 left_half.set_width(left_half.width() / 2); 990 gfx::Rect right_half = ideal_bounds; 991 right_half.set_width(ideal_bounds.width() - left_half.width()); 992 right_half.set_x(left_half.right()); 993 right_tab_x = right_half.right(); 994 if (dragged_bounds.x() >= right_half.x() && 995 dragged_bounds.x() < right_half.right()) { 996 index = i + 1; 997 break; 998 } else if (dragged_bounds.x() >= left_half.x() && 999 dragged_bounds.x() < left_half.right()) { 1000 index = i; 1001 break; 1002 } 1003 } else { 1004 // Vertical tab strip. 1005 int max_y = ideal_bounds.bottom(); 1006 int mid_y = ideal_bounds.y() + ideal_bounds.height() / 2; 1007 bottom_tab_y = max_y; 1008 if (dragged_bounds.y() < mid_y) { 1009 index = i; 1010 break; 1011 } else if (dragged_bounds.y() >= mid_y && dragged_bounds.y() < max_y) { 1012 index = i + 1; 1013 break; 1014 } 1015 } 1016 } 1017 if (index == -1) { 1018 if ((attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP && 1019 dragged_bounds.right() > right_tab_x) || 1020 (attached_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP && 1021 dragged_bounds.y() >= bottom_tab_y)) { 1022 index = GetModel(attached_tabstrip_)->count(); 1023 } else { 1024 index = 0; 1025 } 1026 } 1027 1028 if (!drag_data_[0].attached_tab) { 1029 // If 'attached_tab' is NULL, it means we're in the process of attaching and 1030 // don't need to constrain the index. 1031 return index; 1032 } 1033 1034 int max_index = GetModel(attached_tabstrip_)->count() - 1035 static_cast<int>(drag_data_.size()); 1036 return std::max(0, std::min(max_index, index)); 1037 } 1038 1039 gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds( 1040 const gfx::Point& tab_strip_point) { 1041 // attached_tab is NULL when inserting into a new tabstrip. 1042 if (source_tab_drag_data()->attached_tab) { 1043 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1044 source_tab_drag_data()->attached_tab->width(), 1045 source_tab_drag_data()->attached_tab->height()); 1046 } 1047 1048 if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 1049 double sel_width, unselected_width; 1050 static_cast<TabStrip*>(attached_tabstrip_)->GetCurrentTabWidths( 1051 &sel_width, &unselected_width); 1052 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1053 static_cast<int>(sel_width), 1054 Tab::GetStandardSize().height()); 1055 } 1056 1057 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(), 1058 attached_tabstrip_->width(), 1059 SideTab::GetPreferredHeight()); 1060 } 1061 1062 gfx::Point DraggedTabController::GetAttachedDragPoint( 1063 const gfx::Point& screen_point) { 1064 DCHECK(attached_tabstrip_); // The tab must be attached. 1065 1066 gfx::Point tab_loc(screen_point); 1067 views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_loc); 1068 int x = 1069 attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x(); 1070 int y = tab_loc.y() - mouse_offset_.y(); 1071 1072 // TODO: consider caching this. 1073 std::vector<BaseTab*> attached_tabs; 1074 for (size_t i = 0; i < drag_data_.size(); ++i) 1075 attached_tabs.push_back(drag_data_[i].attached_tab); 1076 1077 int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs); 1078 1079 if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { 1080 int max_x = attached_tabstrip_->width() - size; 1081 x = std::min(std::max(x, 0), max_x); 1082 y = 0; 1083 } else { 1084 x = SideTabStrip::kTabStripInset; 1085 int max_y = attached_tabstrip_->height() - size; 1086 y = std::min(std::max(y, SideTabStrip::kTabStripInset), max_y); 1087 } 1088 return gfx::Point(x, y); 1089 } 1090 1091 std::vector<BaseTab*> DraggedTabController::GetTabsMatchingDraggedContents( 1092 BaseTabStrip* tabstrip) { 1093 TabStripModel* model = GetModel(attached_tabstrip_); 1094 std::vector<BaseTab*> tabs; 1095 for (size_t i = 0; i < drag_data_.size(); ++i) { 1096 int model_index = model->GetIndexOfTabContents(drag_data_[i].contents); 1097 if (model_index == TabStripModel::kNoTab) 1098 return std::vector<BaseTab*>(); 1099 tabs.push_back(tabstrip->GetBaseTabAtModelIndex(model_index)); 1100 } 1101 return tabs; 1102 } 1103 1104 void DraggedTabController::EndDragImpl(EndDragType type) { 1105 active_ = false; 1106 1107 bring_to_front_timer_.Stop(); 1108 1109 // Hide the current dock controllers. 1110 for (size_t i = 0; i < dock_controllers_.size(); ++i) { 1111 // Be sure and clear the controller first, that way if Hide ends up 1112 // deleting the controller it won't call us back. 1113 dock_controllers_[i]->clear_controller(); 1114 dock_controllers_[i]->Hide(); 1115 } 1116 dock_controllers_.clear(); 1117 dock_windows_.clear(); 1118 1119 if (type != TAB_DESTROYED) { 1120 // We only finish up the drag if we were actually dragging. If start_drag_ 1121 // is false, the user just clicked and released and didn't move the mouse 1122 // enough to trigger a drag. 1123 if (started_drag_) { 1124 RestoreFocus(); 1125 if (type == CANCELED) 1126 RevertDrag(); 1127 else 1128 CompleteDrag(); 1129 } 1130 } else if (drag_data_.size() > 1) { 1131 RevertDrag(); 1132 } // else case the only tab we were dragging was deleted. Nothing to do. 1133 1134 ResetDelegates(); 1135 1136 // Clear out drag data so we don't attempt to do anything with it. 1137 drag_data_.clear(); 1138 1139 source_tabstrip_->DestroyDragController(); 1140 } 1141 1142 void DraggedTabController::RevertDrag() { 1143 std::vector<BaseTab*> tabs; 1144 for (size_t i = 0; i < drag_data_.size(); ++i) { 1145 if (drag_data_[i].contents) { 1146 // Contents is NULL if a tab was destroyed while the drag was under way. 1147 tabs.push_back(drag_data_[i].attached_tab); 1148 RevertDragAt(i); 1149 } 1150 } 1151 1152 bool restore_frame = attached_tabstrip_ != source_tabstrip_; 1153 if (attached_tabstrip_ && attached_tabstrip_ == source_tabstrip_) 1154 source_tabstrip_->StoppedDraggingTabs(tabs); 1155 1156 attached_tabstrip_ = source_tabstrip_; 1157 1158 ResetSelection(GetModel(attached_tabstrip_)); 1159 1160 // If we're not attached to any TabStrip, or attached to some other TabStrip, 1161 // we need to restore the bounds of the original TabStrip's frame, in case 1162 // it has been hidden. 1163 if (restore_frame) { 1164 if (!restore_bounds_.IsEmpty()) { 1165 #if defined(OS_WIN) 1166 HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView(); 1167 MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(), 1168 restore_bounds_.width(), restore_bounds_.height(), TRUE); 1169 #else 1170 NOTIMPLEMENTED(); 1171 #endif 1172 } 1173 } 1174 } 1175 1176 void DraggedTabController::ResetSelection(TabStripModel* model) { 1177 DCHECK(model); 1178 TabStripSelectionModel selection_model; 1179 bool has_one_valid_tab = false; 1180 for (size_t i = 0; i < drag_data_.size(); ++i) { 1181 // |contents| is NULL if a tab was deleted out from under us. 1182 if (drag_data_[i].contents) { 1183 int index = model->GetIndexOfTabContents(drag_data_[i].contents); 1184 DCHECK_NE(-1, index); 1185 selection_model.AddIndexToSelection(index); 1186 if (!has_one_valid_tab || i == source_tab_index_) { 1187 // Reset the active/lead to the first tab. If the source tab is still 1188 // valid we'll reset these again later on. 1189 selection_model.set_active(index); 1190 selection_model.set_anchor(index); 1191 has_one_valid_tab = true; 1192 } 1193 } 1194 } 1195 if (!has_one_valid_tab) 1196 return; 1197 1198 model->SetSelectionFromModel(selection_model); 1199 } 1200 1201 void DraggedTabController::RevertDragAt(size_t drag_index) { 1202 DCHECK(started_drag_); 1203 1204 TabDragData* data = &(drag_data_[drag_index]); 1205 if (attached_tabstrip_) { 1206 int index = 1207 GetModel(attached_tabstrip_)->GetIndexOfTabContents(data->contents); 1208 if (attached_tabstrip_ != source_tabstrip_) { 1209 // The Tab was inserted into another TabStrip. We need to put it back 1210 // into the original one. 1211 GetModel(attached_tabstrip_)->DetachTabContentsAt(index); 1212 // TODO(beng): (Cleanup) seems like we should use Attach() for this 1213 // somehow. 1214 GetModel(source_tabstrip_)->InsertTabContentsAt( 1215 data->source_model_index, data->contents, 1216 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1217 } else { 1218 // The Tab was moved within the TabStrip where the drag was initiated. 1219 // Move it back to the starting location. 1220 GetModel(source_tabstrip_)->MoveTabContentsAt( 1221 index, data->source_model_index, false); 1222 } 1223 } else { 1224 // The Tab was detached from the TabStrip where the drag began, and has not 1225 // been attached to any other TabStrip. We need to put it back into the 1226 // source TabStrip. 1227 GetModel(source_tabstrip_)->InsertTabContentsAt( 1228 data->source_model_index, data->contents, 1229 (data->pinned ? TabStripModel::ADD_PINNED : 0)); 1230 } 1231 } 1232 1233 void DraggedTabController::CompleteDrag() { 1234 DCHECK(started_drag_); 1235 1236 if (attached_tabstrip_) { 1237 attached_tabstrip_->StoppedDraggingTabs( 1238 GetTabsMatchingDraggedContents(attached_tabstrip_)); 1239 } else { 1240 if (dock_info_.type() != DockInfo::NONE) { 1241 Profile* profile = GetModel(source_tabstrip_)->profile(); 1242 switch (dock_info_.type()) { 1243 case DockInfo::LEFT_OF_WINDOW: 1244 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Left"), 1245 profile); 1246 break; 1247 1248 case DockInfo::RIGHT_OF_WINDOW: 1249 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Right"), 1250 profile); 1251 break; 1252 1253 case DockInfo::BOTTOM_OF_WINDOW: 1254 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Bottom"), 1255 profile); 1256 break; 1257 1258 case DockInfo::TOP_OF_WINDOW: 1259 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Top"), 1260 profile); 1261 break; 1262 1263 case DockInfo::MAXIMIZE: 1264 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Maximize"), 1265 profile); 1266 break; 1267 1268 case DockInfo::LEFT_HALF: 1269 UserMetrics::RecordAction(UserMetricsAction("DockingWindow_LeftHalf"), 1270 profile); 1271 break; 1272 1273 case DockInfo::RIGHT_HALF: 1274 UserMetrics::RecordAction( 1275 UserMetricsAction("DockingWindow_RightHalf"), 1276 profile); 1277 break; 1278 1279 case DockInfo::BOTTOM_HALF: 1280 UserMetrics::RecordAction( 1281 UserMetricsAction("DockingWindow_BottomHalf"), 1282 profile); 1283 break; 1284 1285 default: 1286 NOTREACHED(); 1287 break; 1288 } 1289 } 1290 // Compel the model to construct a new window for the detached TabContents. 1291 views::Window* window = source_tabstrip_->GetWindow(); 1292 gfx::Rect window_bounds(window->GetNormalBounds()); 1293 window_bounds.set_origin(GetWindowCreatePoint()); 1294 // When modifying the following if statement, please make sure not to 1295 // introduce issue listed in http://crbug.com/6223 comment #11. 1296 bool rtl_ui = base::i18n::IsRTL(); 1297 bool has_dock_position = (dock_info_.type() != DockInfo::NONE); 1298 if (rtl_ui && has_dock_position) { 1299 // Mirror X axis so the docked tab is aligned using the mouse click as 1300 // the top-right corner. 1301 window_bounds.set_x(window_bounds.x() - window_bounds.width()); 1302 } 1303 Browser* new_browser = 1304 GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents( 1305 drag_data_[0].contents, window_bounds, dock_info_, 1306 window->IsMaximized()); 1307 TabStripModel* new_model = new_browser->tabstrip_model(); 1308 new_model->SetTabPinned( 1309 new_model->GetIndexOfTabContents(drag_data_[0].contents), 1310 drag_data_[0].pinned); 1311 for (size_t i = 1; i < drag_data_.size(); ++i) { 1312 new_model->InsertTabContentsAt( 1313 static_cast<int>(i), 1314 drag_data_[i].contents, 1315 drag_data_[i].pinned ? TabStripModel::ADD_PINNED : 1316 TabStripModel::ADD_NONE); 1317 } 1318 ResetSelection(new_browser->tabstrip_model()); 1319 new_browser->window()->Show(); 1320 } 1321 1322 CleanUpHiddenFrame(); 1323 } 1324 1325 void DraggedTabController::ResetDelegates() { 1326 for (size_t i = 0; i < drag_data_.size(); ++i) { 1327 if (drag_data_[i].contents && 1328 drag_data_[i].contents->tab_contents()->delegate() == this) { 1329 drag_data_[i].contents->tab_contents()->set_delegate( 1330 drag_data_[i].original_delegate); 1331 } 1332 } 1333 } 1334 1335 void DraggedTabController::CreateDraggedView( 1336 const std::vector<TabRendererData>& data, 1337 const std::vector<gfx::Rect>& renderer_bounds) { 1338 DCHECK(!view_.get()); 1339 DCHECK_EQ(data.size(), drag_data_.size()); 1340 1341 // Set up the photo booth to start capturing the contents of the dragged 1342 // TabContents. 1343 NativeViewPhotobooth* photobooth = 1344 NativeViewPhotobooth::Create( 1345 source_dragged_contents()->tab_contents()->GetNativeView()); 1346 1347 gfx::Rect content_bounds; 1348 source_dragged_contents()->tab_contents()->GetContainerBounds( 1349 &content_bounds); 1350 1351 std::vector<views::View*> renderers; 1352 for (size_t i = 0; i < drag_data_.size(); ++i) { 1353 BaseTab* renderer = source_tabstrip_->CreateTabForDragging(); 1354 renderer->SetData(data[i]); 1355 renderers.push_back(renderer); 1356 } 1357 // DraggedTabView takes ownership of the renderers. 1358 view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_, 1359 content_bounds.size(), photobooth)); 1360 } 1361 1362 gfx::Point DraggedTabController::GetCursorScreenPoint() const { 1363 #if defined(OS_WIN) 1364 DWORD pos = GetMessagePos(); 1365 return gfx::Point(pos); 1366 #else 1367 gint x, y; 1368 gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL); 1369 return gfx::Point(x, y); 1370 #endif 1371 } 1372 1373 gfx::Rect DraggedTabController::GetViewScreenBounds(views::View* view) const { 1374 gfx::Point view_topleft; 1375 views::View::ConvertPointToScreen(view, &view_topleft); 1376 gfx::Rect view_screen_bounds = view->GetLocalBounds(); 1377 view_screen_bounds.Offset(view_topleft.x(), view_topleft.y()); 1378 return view_screen_bounds; 1379 } 1380 1381 void DraggedTabController::HideFrame() { 1382 #if defined(OS_WIN) 1383 // We don't actually hide the window, rather we just move it way off-screen. 1384 // If we actually hide it, we stop receiving drag events. 1385 HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView(); 1386 RECT wr; 1387 GetWindowRect(frame_hwnd, &wr); 1388 MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left, 1389 wr.bottom - wr.top, TRUE); 1390 1391 // We also save the bounds of the window prior to it being moved, so that if 1392 // the drag session is aborted we can restore them. 1393 restore_bounds_ = gfx::Rect(wr); 1394 #else 1395 NOTIMPLEMENTED(); 1396 #endif 1397 } 1398 1399 void DraggedTabController::CleanUpHiddenFrame() { 1400 // If the model we started dragging from is now empty, we must ask the 1401 // delegate to close the frame. 1402 if (GetModel(source_tabstrip_)->empty()) 1403 GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession(); 1404 } 1405 1406 void DraggedTabController::DockDisplayerDestroyed( 1407 DockDisplayer* controller) { 1408 DockWindows::iterator dock_i = 1409 dock_windows_.find(controller->popup_view()); 1410 if (dock_i != dock_windows_.end()) 1411 dock_windows_.erase(dock_i); 1412 else 1413 NOTREACHED(); 1414 1415 std::vector<DockDisplayer*>::iterator i = 1416 std::find(dock_controllers_.begin(), dock_controllers_.end(), 1417 controller); 1418 if (i != dock_controllers_.end()) 1419 dock_controllers_.erase(i); 1420 else 1421 NOTREACHED(); 1422 } 1423 1424 void DraggedTabController::BringWindowUnderMouseToFront() { 1425 // If we're going to dock to another window, bring it to the front. 1426 gfx::NativeWindow window = dock_info_.window(); 1427 if (!window) { 1428 gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView(); 1429 dock_windows_.insert(dragged_view); 1430 window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(), 1431 dock_windows_); 1432 dock_windows_.erase(dragged_view); 1433 } 1434 if (window) { 1435 #if defined(OS_WIN) 1436 // Move the window to the front. 1437 SetWindowPos(window, HWND_TOP, 0, 0, 0, 0, 1438 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 1439 1440 // The previous call made the window appear on top of the dragged window, 1441 // move the dragged window to the front. 1442 SetWindowPos(view_->GetWidget()->GetNativeView(), HWND_TOP, 0, 0, 0, 0, 1443 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 1444 #else 1445 NOTIMPLEMENTED(); 1446 #endif 1447 } 1448 } 1449 1450 TabStripModel* DraggedTabController::GetModel(BaseTabStrip* tabstrip) const { 1451 return static_cast<BrowserTabStripController*>(tabstrip->controller())-> 1452 model(); 1453 } 1454 1455 bool DraggedTabController::AreTabsConsecutive() { 1456 for (size_t i = 1; i < drag_data_.size(); ++i) { 1457 if (drag_data_[i - 1].source_model_index + 1 != 1458 drag_data_[i].source_model_index) { 1459 return false; 1460 } 1461 } 1462 return true; 1463 } 1464