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/gtk/tabs/dragged_tab_controller_gtk.h" 6 7 #include <algorithm> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/i18n/rtl.h" 12 #include "chrome/browser/chrome_notification_types.h" 13 #include "chrome/browser/platform_util.h" 14 #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h" 15 #include "chrome/browser/ui/browser.h" 16 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 17 #include "chrome/browser/ui/gtk/gtk_util.h" 18 #include "chrome/browser/ui/gtk/tabs/dragged_view_gtk.h" 19 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" 20 #include "chrome/browser/ui/tabs/tab_strip_model.h" 21 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" 22 #include "content/public/browser/notification_source.h" 23 #include "content/public/browser/web_contents.h" 24 #include "content/public/browser/web_contents_view.h" 25 #include "ui/base/gtk/gtk_screen_util.h" 26 #include "ui/gfx/screen.h" 27 28 using content::OpenURLParams; 29 using content::WebContents; 30 31 namespace { 32 33 // Delay, in ms, during dragging before we bring a window to front. 34 const int kBringToFrontDelay = 750; 35 36 // Used to determine how far a tab must obscure another tab in order to swap 37 // their indexes. 38 const int kHorizontalMoveThreshold = 16; // pixels 39 40 // How far a drag must pull a tab out of the tabstrip in order to detach it. 41 const int kVerticalDetachMagnetism = 15; // pixels 42 43 // Returns the bounds that will be used for insertion index calculation. 44 // |bounds| is the actual tab bounds, but subtracting the overlapping areas from 45 // both sides makes the calculations much simpler. 46 gfx::Rect GetEffectiveBounds(const gfx::Rect& bounds) { 47 gfx::Rect effective_bounds(bounds); 48 effective_bounds.set_width(effective_bounds.width() - 2 * 16); 49 effective_bounds.set_x(effective_bounds.x() + 16); 50 return effective_bounds; 51 } 52 53 } // namespace 54 55 DraggedTabControllerGtk::DraggedTabControllerGtk( 56 TabStripGtk* source_tabstrip, 57 TabGtk* source_tab, 58 const std::vector<TabGtk*>& tabs) 59 : source_tabstrip_(source_tabstrip), 60 attached_tabstrip_(source_tabstrip), 61 in_destructor_(false), 62 last_move_screen_x_(0), 63 initial_move_(true) { 64 DCHECK(!tabs.empty()); 65 DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); 66 67 std::vector<DraggedTabData> drag_data; 68 for (size_t i = 0; i < tabs.size(); i++) 69 drag_data.push_back(InitDraggedTabData(tabs[i])); 70 71 int source_tab_index = 72 std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin(); 73 drag_data_.reset(new DragData(drag_data, source_tab_index)); 74 } 75 76 DraggedTabControllerGtk::~DraggedTabControllerGtk() { 77 in_destructor_ = true; 78 // Need to delete the dragged tab here manually _before_ we reset the dragged 79 // contents to NULL, otherwise if the view is animating to its destination 80 // bounds, it won't be able to clean up properly since its cleanup routine 81 // uses GetIndexForDraggedContents, which will be invalid. 82 CleanUpDraggedTabs(); 83 dragged_view_.reset(); 84 drag_data_.reset(); 85 } 86 87 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) { 88 start_screen_point_ = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 89 mouse_offset_ = mouse_offset; 90 } 91 92 void DraggedTabControllerGtk::Drag() { 93 if (!drag_data_->GetSourceTabData()->tab_ || 94 !drag_data_->GetSourceWebContents()) { 95 return; 96 } 97 98 bring_to_front_timer_.Stop(); 99 100 EnsureDraggedView(); 101 102 // Before we get to dragging anywhere, ensure that we consider ourselves 103 // attached to the source tabstrip. 104 if (drag_data_->GetSourceTabData()->tab_->IsVisible()) { 105 Attach(source_tabstrip_, gfx::Point()); 106 } 107 108 if (!drag_data_->GetSourceTabData()->tab_->IsVisible()) { 109 // TODO(jhawkins): Save focus. 110 ContinueDragging(); 111 } 112 } 113 114 bool DraggedTabControllerGtk::EndDrag(bool canceled) { 115 return EndDragImpl(canceled ? CANCELED : NORMAL); 116 } 117 118 TabGtk* DraggedTabControllerGtk::GetDraggedTabForContents( 119 WebContents* contents) { 120 if (attached_tabstrip_ == source_tabstrip_) { 121 for (size_t i = 0; i < drag_data_->size(); i++) { 122 if (contents == drag_data_->get(i)->contents_) 123 return drag_data_->get(i)->tab_; 124 } 125 } 126 return NULL; 127 } 128 129 bool DraggedTabControllerGtk::IsDraggingTab(const TabGtk* tab) { 130 for (size_t i = 0; i < drag_data_->size(); i++) { 131 if (tab == drag_data_->get(i)->tab_) 132 return true; 133 } 134 return false; 135 } 136 137 bool DraggedTabControllerGtk::IsDraggingWebContents( 138 const WebContents* web_contents) { 139 for (size_t i = 0; i < drag_data_->size(); i++) { 140 if (web_contents == drag_data_->get(i)->contents_) 141 return true; 142 } 143 return false; 144 } 145 146 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) { 147 return IsDraggingTab(tab) && attached_tabstrip_ == NULL; 148 } 149 150 DraggedTabData DraggedTabControllerGtk::InitDraggedTabData(TabGtk* tab) { 151 int source_model_index = source_tabstrip_->GetIndexOfTab(tab); 152 WebContents* contents = 153 source_tabstrip_->model()->GetWebContentsAt(source_model_index); 154 bool pinned = source_tabstrip_->IsTabPinned(tab); 155 bool mini = source_tabstrip_->model()->IsMiniTab(source_model_index); 156 // We need to be the delegate so we receive messages about stuff, 157 // otherwise our dragged_contents() may be replaced and subsequently 158 // collected/destroyed while the drag is in process, leading to 159 // nasty crashes. 160 content::WebContentsDelegate* original_delegate = contents->GetDelegate(); 161 contents->SetDelegate(this); 162 163 DraggedTabData dragged_tab_data(tab, contents, original_delegate, 164 source_model_index, pinned, mini); 165 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 166 content::Source<WebContents>(contents)); 167 return dragged_tab_data; 168 } 169 170 //////////////////////////////////////////////////////////////////////////////// 171 // DraggedTabControllerGtk, content::WebContentsDelegate implementation: 172 173 WebContents* DraggedTabControllerGtk::OpenURLFromTab( 174 WebContents* source, 175 const OpenURLParams& params) { 176 if (drag_data_->GetSourceTabData()->original_delegate_) { 177 OpenURLParams forward_params = params; 178 if (params.disposition == CURRENT_TAB) 179 forward_params.disposition = NEW_WINDOW; 180 181 return drag_data_->GetSourceTabData()->original_delegate_-> 182 OpenURLFromTab(source, forward_params); 183 } 184 return NULL; 185 } 186 187 void DraggedTabControllerGtk::NavigationStateChanged(const WebContents* source, 188 unsigned changed_flags) { 189 if (dragged_view_.get()) 190 dragged_view_->Update(); 191 } 192 193 void DraggedTabControllerGtk::AddNewContents(WebContents* source, 194 WebContents* new_contents, 195 WindowOpenDisposition disposition, 196 const gfx::Rect& initial_pos, 197 bool user_gesture, 198 bool* was_blocked) { 199 DCHECK(disposition != CURRENT_TAB); 200 201 // Theoretically could be called while dragging if the page tries to 202 // spawn a window. Route this message back to the browser in most cases. 203 if (drag_data_->GetSourceTabData()->original_delegate_) { 204 drag_data_->GetSourceTabData()->original_delegate_->AddNewContents( 205 source, new_contents, disposition, initial_pos, user_gesture, 206 was_blocked); 207 } 208 } 209 210 void DraggedTabControllerGtk::LoadingStateChanged(WebContents* source) { 211 // TODO(jhawkins): It would be nice to respond to this message by changing the 212 // screen shot in the dragged tab. 213 if (dragged_view_.get()) 214 dragged_view_->Update(); 215 } 216 217 content::JavaScriptDialogManager* 218 DraggedTabControllerGtk::GetJavaScriptDialogManager() { 219 return GetJavaScriptDialogManagerInstance(); 220 } 221 222 //////////////////////////////////////////////////////////////////////////////// 223 // DraggedTabControllerGtk, content::NotificationObserver implementation: 224 225 void DraggedTabControllerGtk::Observe( 226 int type, 227 const content::NotificationSource& source, 228 const content::NotificationDetails& details) { 229 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); 230 WebContents* destroyed_web_contents = 231 content::Source<WebContents>(source).ptr(); 232 for (size_t i = 0; i < drag_data_->size(); ++i) { 233 if (drag_data_->get(i)->contents_ == destroyed_web_contents) { 234 // One of the tabs we're dragging has been destroyed. Cancel the drag. 235 if (destroyed_web_contents->GetDelegate() == this) 236 destroyed_web_contents->SetDelegate(NULL); 237 drag_data_->get(i)->contents_ = NULL; 238 drag_data_->get(i)->original_delegate_ = NULL; 239 EndDragImpl(TAB_DESTROYED); 240 return; 241 } 242 } 243 // If we get here it means we got notification for a tab we don't know about. 244 NOTREACHED(); 245 } 246 247 gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const { 248 gfx::Point creation_point = 249 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 250 gfx::Point distance_from_origin = 251 dragged_view_->GetDistanceFromTabStripOriginToMousePointer(); 252 // TODO(dpapad): offset also because of tabstrip origin being different than 253 // window origin. 254 creation_point.Offset(-distance_from_origin.x(), -distance_from_origin.y()); 255 return creation_point; 256 } 257 258 void DraggedTabControllerGtk::ContinueDragging() { 259 // TODO(jhawkins): We don't handle the situation where the last tab is dragged 260 // out of a window, so we'll just go with the way Windows handles dragging for 261 // now. 262 gfx::Point screen_point = 263 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); 264 265 // Determine whether or not we have dragged over a compatible TabStrip in 266 // another browser window. If we have, we should attach to it and start 267 // dragging within it. 268 TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point); 269 if (target_tabstrip != attached_tabstrip_) { 270 // Make sure we're fully detached from whatever TabStrip we're attached to 271 // (if any). 272 if (attached_tabstrip_) 273 Detach(); 274 275 if (target_tabstrip) 276 Attach(target_tabstrip, screen_point); 277 } 278 279 if (!target_tabstrip) { 280 bring_to_front_timer_.Start(FROM_HERE, 281 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, 282 &DraggedTabControllerGtk::BringWindowUnderMouseToFront); 283 } 284 285 if (attached_tabstrip_) 286 MoveAttached(screen_point); 287 else 288 MoveDetached(screen_point); 289 } 290 291 void DraggedTabControllerGtk::RestoreSelection(TabStripModel* model) { 292 for (size_t i = 0; i < drag_data_->size(); ++i) { 293 WebContents* contents = drag_data_->get(i)->contents_; 294 // If a tab is destroyed while dragging contents might be null. See 295 // http://crbug.com/115409. 296 if (contents) { 297 int index = model->GetIndexOfWebContents(contents); 298 CHECK(index != TabStripModel::kNoTab); 299 model->AddTabAtToSelection(index); 300 } 301 } 302 } 303 304 void DraggedTabControllerGtk::MoveAttached(const gfx::Point& screen_point) { 305 DCHECK(attached_tabstrip_); 306 307 gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point); 308 TabStripModel* attached_model = attached_tabstrip_->model(); 309 310 std::vector<TabGtk*> tabs(drag_data_->GetDraggedTabs()); 311 312 // Determine the horizontal move threshold. This is dependent on the width 313 // of tabs. The smaller the tabs compared to the standard size, the smaller 314 // the threshold. 315 double unselected, selected; 316 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); 317 double ratio = unselected / TabGtk::GetStandardSize().width(); 318 int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); 319 320 // Update the model, moving the WebContents from one index to another. 321 // Do this only if we have moved a minimum distance since the last reorder (to 322 // prevent jitter) or if this is the first move and the tabs are not 323 // consecutive. 324 if (abs(screen_point.x() - last_move_screen_x_) > threshold || 325 (initial_move_ && !AreTabsConsecutive())) { 326 if (initial_move_ && !AreTabsConsecutive()) { 327 // Making dragged tabs adjacent, this is done only once, if necessary. 328 attached_tabstrip_->model()->MoveSelectedTabsTo( 329 drag_data_->GetSourceTabData()->source_model_index_ - 330 drag_data_->source_tab_index()); 331 } 332 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); 333 int to_index = GetInsertionIndexForDraggedBounds( 334 GetEffectiveBounds(bounds)); 335 to_index = NormalizeIndexToAttachedTabStrip(to_index); 336 last_move_screen_x_ = screen_point.x(); 337 attached_model->MoveSelectedTabsTo(to_index); 338 } 339 340 dragged_view_->MoveAttachedTo(dragged_view_point); 341 initial_move_ = false; 342 } 343 344 void DraggedTabControllerGtk::MoveDetached(const gfx::Point& screen_point) { 345 DCHECK(!attached_tabstrip_); 346 // Just moving the dragged view. There are no changes to the model if we're 347 // detached. 348 dragged_view_->MoveDetachedTo(screen_point); 349 } 350 351 TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( 352 const gfx::Point& screen_point) { 353 GtkWidget* dragged_window = dragged_view_->widget(); 354 dock_windows_.insert(dragged_window); 355 gfx::NativeWindow local_window = 356 DockInfo::GetLocalProcessWindowAtPoint( 357 chrome::HOST_DESKTOP_TYPE_NATIVE, screen_point, dock_windows_); 358 dock_windows_.erase(dragged_window); 359 if (!local_window) 360 return NULL; 361 362 BrowserWindowGtk* browser = 363 BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window); 364 if (!browser) 365 return NULL; 366 367 TabStripGtk* other_tabstrip = browser->tabstrip(); 368 if (!other_tabstrip->IsCompatibleWith(source_tabstrip_)) 369 return NULL; 370 371 return GetTabStripIfItContains(other_tabstrip, screen_point); 372 } 373 374 TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains( 375 TabStripGtk* tabstrip, const gfx::Point& screen_point) const { 376 // Make sure the specified screen point is actually within the bounds of the 377 // specified tabstrip... 378 gfx::Rect tabstrip_bounds = 379 ui::GetWidgetScreenBounds(tabstrip->tabstrip_.get()); 380 if (screen_point.x() < tabstrip_bounds.right() && 381 screen_point.x() >= tabstrip_bounds.x()) { 382 // TODO(beng): make this be relative to the start position of the mouse for 383 // the source TabStrip. 384 int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism; 385 int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism; 386 if (screen_point.y() >= lower_threshold && 387 screen_point.y() <= upper_threshold) { 388 return tabstrip; 389 } 390 } 391 392 return NULL; 393 } 394 395 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip, 396 const gfx::Point& screen_point) { 397 attached_tabstrip_ = attached_tabstrip; 398 attached_tabstrip_->GenerateIdealBounds(); 399 400 std::vector<TabGtk*> attached_dragged_tabs = 401 GetTabsMatchingDraggedContents(attached_tabstrip_); 402 403 // Update the tab first, so we can ask it for its bounds and determine 404 // where to insert the hidden tab. 405 406 // If this is the first time Attach is called for this drag, we're attaching 407 // to the source tabstrip, and we should assume the tab count already 408 // includes this tab since we haven't been detached yet. If we don't do this, 409 // the dragged representation will be a different size to others in the 410 // tabstrip. 411 int tab_count = attached_tabstrip_->GetTabCount(); 412 int mini_tab_count = attached_tabstrip_->GetMiniTabCount(); 413 if (attached_dragged_tabs.size() == 0) { 414 tab_count += drag_data_->size(); 415 mini_tab_count += drag_data_->mini_tab_count(); 416 } 417 418 double unselected_width = 0, selected_width = 0; 419 attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count, 420 &unselected_width, &selected_width); 421 422 GtkWidget* parent_window = gtk_widget_get_parent( 423 gtk_widget_get_parent(attached_tabstrip_->tabstrip_.get())); 424 gfx::Rect window_bounds = ui::GetWidgetScreenBounds(parent_window); 425 dragged_view_->Attach(static_cast<int>(floor(selected_width + 0.5)), 426 TabGtk::GetMiniWidth(), window_bounds.width()); 427 428 if (attached_dragged_tabs.size() == 0) { 429 // There is no tab in |attached_tabstrip| that corresponds to the dragged 430 // WebContents. We must now create one. 431 432 // Remove ourselves as the delegate now that the dragged WebContents 433 // is being inserted back into a Browser. 434 for (size_t i = 0; i < drag_data_->size(); ++i) { 435 drag_data_->get(i)->contents_->SetDelegate(NULL); 436 drag_data_->get(i)->original_delegate_ = NULL; 437 } 438 439 // We need to ask the tabstrip we're attached to ensure that the ideal 440 // bounds for all its tabs are correctly generated, because the calculation 441 // in GetInsertionIndexForDraggedBounds needs them to be to figure out the 442 // appropriate insertion index. 443 attached_tabstrip_->GenerateIdealBounds(); 444 445 // Inserting counts as a move. We don't want the tabs to jitter when the 446 // user moves the tab immediately after attaching it. 447 last_move_screen_x_ = screen_point.x(); 448 449 // Figure out where to insert the tab based on the bounds of the dragged 450 // representation and the ideal bounds of the other tabs already in the 451 // strip. ("ideal bounds" are stable even if the tabs' actual bounds are 452 // changing due to animation). 453 gfx::Rect bounds = 454 GetDraggedViewTabStripBounds(GetDraggedViewPoint(screen_point)); 455 int index = GetInsertionIndexForDraggedBounds(GetEffectiveBounds(bounds)); 456 for (size_t i = 0; i < drag_data_->size(); ++i) { 457 attached_tabstrip_->model()->InsertWebContentsAt( 458 index + i, drag_data_->get(i)->contents_, 459 drag_data_->GetAddTypesForDraggedTabAt(i)); 460 } 461 RestoreSelection(attached_tabstrip_->model()); 462 attached_dragged_tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); 463 } 464 // We should now have a tab. 465 DCHECK(attached_dragged_tabs.size() == drag_data_->size()); 466 SetDraggedTabsVisible(false, false); 467 // TODO(jhawkins): Move the corresponding window to the front. 468 } 469 470 void DraggedTabControllerGtk::Detach() { 471 // Update the Model. 472 TabStripModel* attached_model = attached_tabstrip_->model(); 473 for (size_t i = 0; i < drag_data_->size(); ++i) { 474 int index = 475 attached_model->GetIndexOfWebContents(drag_data_->get(i)->contents_); 476 if (index >= 0 && index < attached_model->count()) { 477 // Sometimes, DetachWebContentsAt has consequences that result in 478 // attached_tabstrip_ being set to NULL, so we need to save it first. 479 attached_model->DetachWebContentsAt(index); 480 } 481 } 482 483 // If we've removed the last tab from the tabstrip, hide the frame now. 484 if (attached_model->empty()) 485 HideWindow(); 486 487 // Update the dragged tab. This NULL check is necessary apparently in some 488 // conditions during automation where the view_ is destroyed inside a 489 // function call preceding this point but after it is created. 490 if (dragged_view_.get()) { 491 dragged_view_->Detach(); 492 } 493 494 // Detaching resets the delegate, but we still want to be the delegate. 495 for (size_t i = 0; i < drag_data_->size(); ++i) 496 drag_data_->get(i)->contents_->SetDelegate(this); 497 498 attached_tabstrip_ = NULL; 499 } 500 501 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint( 502 TabStripGtk* tabstrip, const gfx::Point& screen_point) { 503 return screen_point - ui::GetWidgetScreenOffset(tabstrip->tabstrip_.get()); 504 } 505 506 gfx::Rect DraggedTabControllerGtk::GetDraggedViewTabStripBounds( 507 const gfx::Point& screen_point) { 508 gfx::Point client_point = 509 ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); 510 gfx::Size tab_size = dragged_view_->attached_tab_size(); 511 return gfx::Rect(client_point.x(), client_point.y(), 512 dragged_view_->GetTotalWidthInTabStrip(), tab_size.height()); 513 } 514 515 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds( 516 const gfx::Rect& dragged_bounds) { 517 int dragged_bounds_start = gtk_util::MirroredLeftPointForRect( 518 attached_tabstrip_->widget(), 519 dragged_bounds); 520 int dragged_bounds_end = gtk_util::MirroredRightPointForRect( 521 attached_tabstrip_->widget(), 522 dragged_bounds); 523 int right_tab_x = 0; 524 int index = -1; 525 526 for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) { 527 // Divides each tab into two halves to see if the dragged tab has crossed 528 // the halfway boundary necessary to move past the next tab. 529 gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i); 530 gfx::Rect left_half, right_half; 531 ideal_bounds.SplitVertically(&left_half, &right_half); 532 right_tab_x = right_half.x(); 533 534 if (dragged_bounds_start >= right_half.x() && 535 dragged_bounds_start < right_half.right()) { 536 index = i + 1; 537 break; 538 } else if (dragged_bounds_start >= left_half.x() && 539 dragged_bounds_start < left_half.right()) { 540 index = i; 541 break; 542 } 543 } 544 545 if (index == -1) { 546 if (dragged_bounds_end > right_tab_x) 547 index = attached_tabstrip_->GetTabCount(); 548 else 549 index = 0; 550 } 551 552 TabGtk* tab = GetTabMatchingDraggedContents( 553 attached_tabstrip_, drag_data_->GetSourceWebContents()); 554 if (tab == NULL) { 555 // If dragged tabs are not attached yet, we don't need to constrain the 556 // index. 557 return index; 558 } 559 560 int max_index = 561 attached_tabstrip_-> GetTabCount() - static_cast<int>(drag_data_->size()); 562 return std::max(0, std::min(max_index, index)); 563 } 564 565 gfx::Point DraggedTabControllerGtk::GetDraggedViewPoint( 566 const gfx::Point& screen_point) { 567 int x = screen_point.x() - 568 dragged_view_->GetWidthInTabStripUpToMousePointer(); 569 int y = screen_point.y() - mouse_offset_.y(); 570 571 // If we're not attached, we just use x and y from above. 572 if (attached_tabstrip_) { 573 gfx::Rect tabstrip_bounds = 574 ui::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get()); 575 // Snap the dragged tab to the tabstrip if we are attached, detaching 576 // only when the mouse position (screen_point) exceeds the screen bounds 577 // of the tabstrip. 578 if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x()) 579 x = tabstrip_bounds.x(); 580 581 gfx::Size tab_size = dragged_view_->attached_tab_size(); 582 int vertical_drag_magnetism = tab_size.height() * 2; 583 int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism; 584 if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point) 585 y = tabstrip_bounds.y(); 586 587 // Make sure the tab can't be dragged off the right side of the tabstrip 588 // unless the mouse pointer passes outside the bounds of the strip by 589 // clamping the position of the dragged window to the tabstrip width less 590 // the width of one tab until the mouse pointer (screen_point) exceeds the 591 // screen bounds of the tabstrip. 592 int max_x = 593 tabstrip_bounds.right() - dragged_view_->GetTotalWidthInTabStrip(); 594 int max_y = tabstrip_bounds.bottom() - tab_size.height(); 595 if (x > max_x && screen_point.x() <= tabstrip_bounds.right()) 596 x = max_x; 597 if (y > max_y && screen_point.y() <= 598 (tabstrip_bounds.bottom() + vertical_drag_magnetism)) { 599 y = max_y; 600 } 601 } 602 return gfx::Point(x, y); 603 } 604 605 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const { 606 if (index >= attached_tabstrip_->model_->count()) 607 return attached_tabstrip_->model_->count() - 1; 608 if (index == TabStripModel::kNoTab) 609 return 0; 610 return index; 611 } 612 613 TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents( 614 TabStripGtk* tabstrip, WebContents* web_contents) { 615 int index = tabstrip->model()->GetIndexOfWebContents(web_contents); 616 return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); 617 } 618 619 std::vector<TabGtk*> DraggedTabControllerGtk::GetTabsMatchingDraggedContents( 620 TabStripGtk* tabstrip) { 621 std::vector<TabGtk*> dragged_tabs; 622 for (size_t i = 0; i < drag_data_->size(); ++i) { 623 if (!drag_data_->get(i)->contents_) 624 continue; 625 TabGtk* tab = GetTabMatchingDraggedContents(tabstrip, 626 drag_data_->get(i)->contents_); 627 if (tab) 628 dragged_tabs.push_back(tab); 629 } 630 return dragged_tabs; 631 } 632 633 void DraggedTabControllerGtk::SetDraggedTabsVisible(bool visible, 634 bool repaint) { 635 std::vector<TabGtk*> dragged_tabs(GetTabsMatchingDraggedContents( 636 attached_tabstrip_)); 637 for (size_t i = 0; i < dragged_tabs.size(); ++i) { 638 dragged_tabs[i]->SetVisible(visible); 639 dragged_tabs[i]->set_dragging(!visible); 640 if (repaint) 641 dragged_tabs[i]->SchedulePaint(); 642 } 643 } 644 645 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) { 646 bring_to_front_timer_.Stop(); 647 648 // WARNING: this may be invoked multiple times. In particular, if deletion 649 // occurs after a delay (as it does when the tab is released in the original 650 // tab strip) and the navigation controller/tab contents is deleted before 651 // the animation finishes, this is invoked twice. The second time through 652 // type == TAB_DESTROYED. 653 654 bool destroy_now = true; 655 if (type != TAB_DESTROYED) { 656 // If we never received a drag-motion event, the drag will never have 657 // started in the sense that |dragged_view_| will be NULL. We don't need to 658 // revert or complete the drag in that case. 659 if (dragged_view_.get()) { 660 if (type == CANCELED) { 661 RevertDrag(); 662 } else { 663 destroy_now = CompleteDrag(); 664 } 665 } 666 } else if (drag_data_->size() > 0) { 667 RevertDrag(); 668 } 669 670 ResetDelegates(); 671 672 // If we're not destroyed now, we'll be destroyed asynchronously later. 673 if (destroy_now) 674 source_tabstrip_->DestroyDragController(); 675 676 return destroy_now; 677 } 678 679 void DraggedTabControllerGtk::RevertDrag() { 680 // We save this here because code below will modify |attached_tabstrip_|. 681 bool restore_window = attached_tabstrip_ != source_tabstrip_; 682 if (attached_tabstrip_) { 683 if (attached_tabstrip_ != source_tabstrip_) { 684 // The tabs were inserted into another tabstrip. We need to put them back 685 // into the original one. 686 for (size_t i = 0; i < drag_data_->size(); ++i) { 687 if (!drag_data_->get(i)->contents_) 688 continue; 689 int index = attached_tabstrip_->model()->GetIndexOfWebContents( 690 drag_data_->get(i)->contents_); 691 attached_tabstrip_->model()->DetachWebContentsAt(index); 692 } 693 // TODO(beng): (Cleanup) seems like we should use Attach() for this 694 // somehow. 695 attached_tabstrip_ = source_tabstrip_; 696 for (size_t i = 0; i < drag_data_->size(); ++i) { 697 if (!drag_data_->get(i)->contents_) 698 continue; 699 attached_tabstrip_->model()->InsertWebContentsAt( 700 drag_data_->get(i)->source_model_index_, 701 drag_data_->get(i)->contents_, 702 drag_data_->GetAddTypesForDraggedTabAt(i)); 703 } 704 } else { 705 // The tabs were moved within the tabstrip where the drag was initiated. 706 // Move them back to their starting locations. 707 for (size_t i = 0; i < drag_data_->size(); ++i) { 708 if (!drag_data_->get(i)->contents_) 709 continue; 710 int index = attached_tabstrip_->model()->GetIndexOfWebContents( 711 drag_data_->get(i)->contents_); 712 source_tabstrip_->model()->MoveWebContentsAt( 713 index, drag_data_->get(i)->source_model_index_, true); 714 } 715 } 716 } else { 717 // TODO(beng): (Cleanup) seems like we should use Attach() for this 718 // somehow. 719 attached_tabstrip_ = source_tabstrip_; 720 // The tab was detached from the tabstrip where the drag began, and has not 721 // been attached to any other tabstrip. We need to put it back into the 722 // source tabstrip. 723 for (size_t i = 0; i < drag_data_->size(); ++i) { 724 if (!drag_data_->get(i)->contents_) 725 continue; 726 source_tabstrip_->model()->InsertWebContentsAt( 727 drag_data_->get(i)->source_model_index_, 728 drag_data_->get(i)->contents_, 729 drag_data_->GetAddTypesForDraggedTabAt(i)); 730 } 731 } 732 RestoreSelection(source_tabstrip_->model()); 733 734 // If we're not attached to any tab strip, or attached to some other tab 735 // strip, we need to restore the bounds of the original tab strip's frame, in 736 // case it has been hidden. 737 if (restore_window) 738 ShowWindow(); 739 740 SetDraggedTabsVisible(true, false); 741 } 742 743 bool DraggedTabControllerGtk::CompleteDrag() { 744 bool destroy_immediately = true; 745 if (attached_tabstrip_) { 746 // We don't need to do anything other than make the tabs visible again, 747 // since the dragged view is going away. 748 dragged_view_->AnimateToBounds(GetAnimateBounds(), 749 base::Bind(&DraggedTabControllerGtk::OnAnimateToBoundsComplete, 750 base::Unretained(this))); 751 destroy_immediately = false; 752 } else { 753 // Compel the model to construct a new window for the detached 754 // WebContentses. 755 BrowserWindowGtk* window = source_tabstrip_->window(); 756 gfx::Rect window_bounds = window->GetRestoredBounds(); 757 window_bounds.set_origin(GetWindowCreatePoint()); 758 759 std::vector<TabStripModelDelegate::NewStripContents> contentses; 760 for (size_t i = 0; i < drag_data_->size(); ++i) { 761 TabStripModelDelegate::NewStripContents item; 762 item.web_contents = drag_data_->get(i)->contents_; 763 item.add_types = drag_data_->GetAddTypesForDraggedTabAt(i); 764 contentses.push_back(item); 765 }; 766 767 Browser* new_browser = 768 source_tabstrip_->model()->delegate()->CreateNewStripWithContents( 769 contentses, window_bounds, dock_info_, window->IsMaximized()); 770 RestoreSelection(new_browser->tab_strip_model()); 771 new_browser->window()->Show(); 772 CleanUpHiddenFrame(); 773 } 774 775 return destroy_immediately; 776 } 777 778 void DraggedTabControllerGtk::ResetDelegates() { 779 for (size_t i = 0; i < drag_data_->size(); ++i) { 780 if (drag_data_->get(i)->contents_ && 781 drag_data_->get(i)->contents_->GetDelegate() == this) { 782 drag_data_->get(i)->ResetDelegate(); 783 } 784 } 785 } 786 787 void DraggedTabControllerGtk::EnsureDraggedView() { 788 if (!dragged_view_.get()) { 789 gfx::Rect rect; 790 drag_data_->GetSourceWebContents()->GetView()->GetContainerBounds(&rect); 791 dragged_view_.reset(new DraggedViewGtk(drag_data_.get(), mouse_offset_, 792 rect.size())); 793 } 794 } 795 796 gfx::Rect DraggedTabControllerGtk::GetAnimateBounds() { 797 // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't 798 // update its allocation until after the widget is shown, so we have to use 799 // the tab bounds we keep track of. 800 // 801 // We use the requested bounds instead of the allocation because the 802 // allocation is relative to the first windowed widget ancestor of the tab. 803 // Because of this, we can't use the tabs allocation to get the screen bounds. 804 std::vector<TabGtk*> tabs = GetTabsMatchingDraggedContents( 805 attached_tabstrip_); 806 TabGtk* tab = !base::i18n::IsRTL() ? tabs.front() : tabs.back(); 807 gfx::Rect bounds = tab->GetRequisition(); 808 GtkWidget* widget = tab->widget(); 809 GtkWidget* parent = gtk_widget_get_parent(widget); 810 bounds.Offset(ui::GetWidgetScreenOffset(parent)); 811 812 return gfx::Rect(bounds.x(), bounds.y(), 813 dragged_view_->GetTotalWidthInTabStrip(), bounds.height()); 814 } 815 816 void DraggedTabControllerGtk::HideWindow() { 817 GtkWidget* tabstrip = source_tabstrip_->widget(); 818 GtkWindow* window = platform_util::GetTopLevel(tabstrip); 819 gtk_widget_hide(GTK_WIDGET(window)); 820 } 821 822 void DraggedTabControllerGtk::ShowWindow() { 823 GtkWidget* tabstrip = source_tabstrip_->widget(); 824 GtkWindow* window = platform_util::GetTopLevel(tabstrip); 825 gtk_window_present(window); 826 } 827 828 void DraggedTabControllerGtk::CleanUpHiddenFrame() { 829 // If the model we started dragging from is now empty, we must ask the 830 // delegate to close the frame. 831 if (source_tabstrip_->model()->empty()) 832 source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession(); 833 } 834 835 void DraggedTabControllerGtk::CleanUpDraggedTabs() { 836 // If we were attached to the source tabstrip, dragged tabs will be in use. If 837 // we were detached or attached to another tabstrip, we can safely remove 838 // them and delete them now. 839 if (attached_tabstrip_ != source_tabstrip_) { 840 for (size_t i = 0; i < drag_data_->size(); ++i) { 841 if (drag_data_->get(i)->contents_) { 842 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 843 content::Source<WebContents>(drag_data_->get(i)->contents_)); 844 } 845 source_tabstrip_->DestroyDraggedTab(drag_data_->get(i)->tab_); 846 drag_data_->get(i)->tab_ = NULL; 847 } 848 } 849 } 850 851 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() { 852 // Sometimes, for some reason, in automation we can be called back on a 853 // detach even though we aren't attached to a tabstrip. Guard against that. 854 if (attached_tabstrip_) 855 SetDraggedTabsVisible(true, true); 856 857 CleanUpHiddenFrame(); 858 859 if (!in_destructor_) 860 source_tabstrip_->DestroyDragController(); 861 } 862 863 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() { 864 // If we're going to dock to another window, bring it to the front. 865 gfx::NativeWindow window = dock_info_.window(); 866 if (!window) { 867 gfx::NativeView dragged_tab = dragged_view_->widget(); 868 dock_windows_.insert(dragged_tab); 869 window = DockInfo::GetLocalProcessWindowAtPoint( 870 chrome::HOST_DESKTOP_TYPE_NATIVE, 871 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(), 872 dock_windows_); 873 dock_windows_.erase(dragged_tab); 874 } 875 876 if (window) 877 gtk_window_present(GTK_WINDOW(window)); 878 } 879 880 bool DraggedTabControllerGtk::AreTabsConsecutive() { 881 for (size_t i = 1; i < drag_data_->size(); ++i) { 882 if (drag_data_->get(i - 1)->source_model_index_ + 1 != 883 drag_data_->get(i)->source_model_index_) { 884 return false; 885 } 886 } 887 return true; 888 } 889