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/gtk/tabs/dragged_tab_controller_gtk.h" 6 7 #include <algorithm> 8 9 #include "base/callback.h" 10 #include "base/i18n/rtl.h" 11 #include "chrome/browser/platform_util.h" 12 #include "chrome/browser/tabs/tab_strip_model.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 15 #include "chrome/browser/ui/gtk/gtk_util.h" 16 #include "chrome/browser/ui/gtk/tabs/dragged_tab_gtk.h" 17 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" 18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 19 #include "content/browser/tab_contents/tab_contents.h" 20 #include "content/common/notification_source.h" 21 22 namespace { 23 24 // Delay, in ms, during dragging before we bring a window to front. 25 const int kBringToFrontDelay = 750; 26 27 // Used to determine how far a tab must obscure another tab in order to swap 28 // their indexes. 29 const int kHorizontalMoveThreshold = 16; // pixels 30 31 // How far a drag must pull a tab out of the tabstrip in order to detach it. 32 const int kVerticalDetachMagnetism = 15; // pixels 33 34 } // namespace 35 36 DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab, 37 TabStripGtk* source_tabstrip) 38 : dragged_contents_(NULL), 39 original_delegate_(NULL), 40 source_tab_(source_tab), 41 source_tabstrip_(source_tabstrip), 42 source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)), 43 attached_tabstrip_(source_tabstrip), 44 in_destructor_(false), 45 last_move_screen_x_(0), 46 mini_(source_tabstrip->model()->IsMiniTab(source_model_index_)), 47 pinned_(source_tabstrip->model()->IsTabPinned(source_model_index_)) { 48 SetDraggedContents( 49 source_tabstrip_->model()->GetTabContentsAt(source_model_index_)); 50 } 51 52 DraggedTabControllerGtk::~DraggedTabControllerGtk() { 53 in_destructor_ = true; 54 CleanUpSourceTab(); 55 // Need to delete the dragged tab here manually _before_ we reset the dragged 56 // contents to NULL, otherwise if the view is animating to its destination 57 // bounds, it won't be able to clean up properly since its cleanup routine 58 // uses GetIndexForDraggedContents, which will be invalid. 59 dragged_tab_.reset(); 60 SetDraggedContents(NULL); 61 } 62 63 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) { 64 start_screen_point_ = GetCursorScreenPoint(); 65 mouse_offset_ = mouse_offset; 66 } 67 68 void DraggedTabControllerGtk::Drag() { 69 if (!source_tab_ || !dragged_contents_) 70 return; 71 72 bring_to_front_timer_.Stop(); 73 74 EnsureDraggedTab(); 75 76 // Before we get to dragging anywhere, ensure that we consider ourselves 77 // attached to the source tabstrip. 78 if (source_tab_->IsVisible()) { 79 Attach(source_tabstrip_, gfx::Point()); 80 } 81 82 if (!source_tab_->IsVisible()) { 83 // TODO(jhawkins): Save focus. 84 ContinueDragging(); 85 } 86 } 87 88 bool DraggedTabControllerGtk::EndDrag(bool canceled) { 89 return EndDragImpl(canceled ? CANCELED : NORMAL); 90 } 91 92 TabGtk* DraggedTabControllerGtk::GetDragSourceTabForContents( 93 TabContents* contents) const { 94 if (attached_tabstrip_ == source_tabstrip_) 95 return contents == dragged_contents_->tab_contents() ? source_tab_ : NULL; 96 return NULL; 97 } 98 99 bool DraggedTabControllerGtk::IsDragSourceTab(const TabGtk* tab) const { 100 return source_tab_ == tab; 101 } 102 103 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) const { 104 if (!IsDragSourceTab(tab)) 105 return false; 106 return (attached_tabstrip_ == NULL); 107 } 108 109 //////////////////////////////////////////////////////////////////////////////// 110 // DraggedTabControllerGtk, TabContentsDelegate implementation: 111 112 void DraggedTabControllerGtk::OpenURLFromTab(TabContents* source, 113 const GURL& url, 114 const GURL& referrer, 115 WindowOpenDisposition disposition, 116 PageTransition::Type transition) { 117 if (original_delegate_) { 118 if (disposition == CURRENT_TAB) 119 disposition = NEW_WINDOW; 120 121 original_delegate_->OpenURLFromTab(source, url, referrer, 122 disposition, transition); 123 } 124 } 125 126 void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source, 127 unsigned changed_flags) { 128 if (dragged_tab_.get()) 129 dragged_tab_->Update(); 130 } 131 132 void DraggedTabControllerGtk::AddNewContents(TabContents* source, 133 TabContents* new_contents, 134 WindowOpenDisposition disposition, 135 const gfx::Rect& initial_pos, 136 bool user_gesture) { 137 DCHECK(disposition != CURRENT_TAB); 138 139 // Theoretically could be called while dragging if the page tries to 140 // spawn a window. Route this message back to the browser in most cases. 141 if (original_delegate_) { 142 original_delegate_->AddNewContents(source, new_contents, disposition, 143 initial_pos, user_gesture); 144 } 145 } 146 147 void DraggedTabControllerGtk::ActivateContents(TabContents* contents) { 148 // Ignored. 149 } 150 151 void DraggedTabControllerGtk::DeactivateContents(TabContents* contents) { 152 // Ignored. 153 } 154 155 void DraggedTabControllerGtk::LoadingStateChanged(TabContents* source) { 156 // TODO(jhawkins): It would be nice to respond to this message by changing the 157 // screen shot in the dragged tab. 158 if (dragged_tab_.get()) 159 dragged_tab_->Update(); 160 } 161 162 void DraggedTabControllerGtk::CloseContents(TabContents* source) { 163 // Theoretically could be called by a window. Should be ignored 164 // because window.close() is ignored (usually, even though this 165 // method gets called.) 166 } 167 168 void DraggedTabControllerGtk::MoveContents(TabContents* source, 169 const gfx::Rect& pos) { 170 // Theoretically could be called by a web page trying to move its 171 // own window. Should be ignored since we're moving the window... 172 } 173 174 bool DraggedTabControllerGtk::IsPopup(const TabContents* source) const { 175 return false; 176 } 177 178 void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source, 179 const GURL& url) { 180 // Ignored. 181 } 182 183 //////////////////////////////////////////////////////////////////////////////// 184 // DraggedTabControllerGtk, NotificationObserver implementation: 185 186 void DraggedTabControllerGtk::Observe(NotificationType type, 187 const NotificationSource& source, 188 const NotificationDetails& details) { 189 DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); 190 DCHECK(Source<TabContentsWrapper>(source).ptr() == dragged_contents_); 191 EndDragImpl(TAB_DESTROYED); 192 } 193 194 void DraggedTabControllerGtk::InitWindowCreatePoint() { 195 window_create_point_.SetPoint(mouse_offset_.x(), mouse_offset_.y()); 196 } 197 198 gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const { 199 gfx::Point cursor_point = GetCursorScreenPoint(); 200 return gfx::Point(cursor_point.x() - window_create_point_.x(), 201 cursor_point.y() - window_create_point_.y()); 202 } 203 204 void DraggedTabControllerGtk::SetDraggedContents( 205 TabContentsWrapper* new_contents) { 206 if (dragged_contents_) { 207 registrar_.Remove(this, 208 NotificationType::TAB_CONTENTS_DESTROYED, 209 Source<TabContentsWrapper>(dragged_contents_)); 210 if (original_delegate_) 211 dragged_contents_->tab_contents()->set_delegate(original_delegate_); 212 } 213 original_delegate_ = NULL; 214 dragged_contents_ = new_contents; 215 if (dragged_contents_) { 216 registrar_.Add(this, 217 NotificationType::TAB_CONTENTS_DESTROYED, 218 Source<TabContentsWrapper>(dragged_contents_)); 219 220 // We need to be the delegate so we receive messages about stuff, 221 // otherwise our dragged_contents() may be replaced and subsequently 222 // collected/destroyed while the drag is in process, leading to 223 // nasty crashes. 224 original_delegate_ = dragged_contents_->tab_contents()->delegate(); 225 dragged_contents_->tab_contents()->set_delegate(this); 226 } 227 } 228 229 void DraggedTabControllerGtk::ContinueDragging() { 230 // TODO(jhawkins): We don't handle the situation where the last tab is dragged 231 // out of a window, so we'll just go with the way Windows handles dragging for 232 // now. 233 gfx::Point screen_point = GetCursorScreenPoint(); 234 235 // Determine whether or not we have dragged over a compatible TabStrip in 236 // another browser window. If we have, we should attach to it and start 237 // dragging within it. 238 #if defined(OS_CHROMEOS) 239 // We don't allow detaching on chrome os. 240 TabStripGtk* target_tabstrip = source_tabstrip_; 241 #else 242 TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point); 243 #endif 244 if (target_tabstrip != attached_tabstrip_) { 245 // Make sure we're fully detached from whatever TabStrip we're attached to 246 // (if any). 247 if (attached_tabstrip_) 248 Detach(); 249 250 if (target_tabstrip) 251 Attach(target_tabstrip, screen_point); 252 } 253 254 if (!target_tabstrip) { 255 bring_to_front_timer_.Start( 256 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, 257 &DraggedTabControllerGtk::BringWindowUnderMouseToFront); 258 } 259 260 MoveTab(screen_point); 261 } 262 263 void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) { 264 gfx::Point dragged_tab_point = GetDraggedTabPoint(screen_point); 265 266 if (attached_tabstrip_) { 267 TabStripModel* attached_model = attached_tabstrip_->model(); 268 int from_index = attached_model->GetIndexOfTabContents(dragged_contents_); 269 270 // Determine the horizontal move threshold. This is dependent on the width 271 // of tabs. The smaller the tabs compared to the standard size, the smaller 272 // the threshold. 273 double unselected, selected; 274 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); 275 double ratio = unselected / TabGtk::GetStandardSize().width(); 276 int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold); 277 278 // Update the model, moving the TabContents from one index to another. Do 279 // this only if we have moved a minimum distance since the last reorder (to 280 // prevent jitter). 281 if (abs(screen_point.x() - last_move_screen_x_) > threshold) { 282 gfx::Rect bounds = GetDraggedTabTabStripBounds(dragged_tab_point); 283 int to_index = GetInsertionIndexForDraggedBounds(bounds, true); 284 to_index = NormalizeIndexToAttachedTabStrip(to_index); 285 if (from_index != to_index) { 286 last_move_screen_x_ = screen_point.x(); 287 attached_model->MoveTabContentsAt(from_index, to_index, true); 288 } 289 } 290 } 291 292 // Move the dragged tab. There are no changes to the model if we're detached. 293 dragged_tab_->MoveTo(dragged_tab_point); 294 } 295 296 TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( 297 const gfx::Point& screen_point) { 298 GtkWidget* dragged_window = dragged_tab_->widget(); 299 dock_windows_.insert(dragged_window); 300 gfx::NativeWindow local_window = 301 DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_); 302 dock_windows_.erase(dragged_window); 303 if (!local_window) 304 return NULL; 305 306 BrowserWindowGtk* browser = 307 BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window); 308 if (!browser) 309 return NULL; 310 311 TabStripGtk* other_tabstrip = browser->tabstrip(); 312 if (!other_tabstrip->IsCompatibleWith(source_tabstrip_)) 313 return NULL; 314 315 return GetTabStripIfItContains(other_tabstrip, screen_point); 316 } 317 318 TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains( 319 TabStripGtk* tabstrip, const gfx::Point& screen_point) const { 320 // Make sure the specified screen point is actually within the bounds of the 321 // specified tabstrip... 322 gfx::Rect tabstrip_bounds = 323 gtk_util::GetWidgetScreenBounds(tabstrip->tabstrip_.get()); 324 if (screen_point.x() < tabstrip_bounds.right() && 325 screen_point.x() >= tabstrip_bounds.x()) { 326 // TODO(beng): make this be relative to the start position of the mouse for 327 // the source TabStrip. 328 int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism; 329 int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism; 330 if (screen_point.y() >= lower_threshold && 331 screen_point.y() <= upper_threshold) { 332 return tabstrip; 333 } 334 } 335 336 return NULL; 337 } 338 339 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip, 340 const gfx::Point& screen_point) { 341 attached_tabstrip_ = attached_tabstrip; 342 InitWindowCreatePoint(); 343 attached_tabstrip_->GenerateIdealBounds(); 344 345 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_); 346 347 // Update the tab first, so we can ask it for its bounds and determine 348 // where to insert the hidden tab. 349 350 // If this is the first time Attach is called for this drag, we're attaching 351 // to the source tabstrip, and we should assume the tab count already 352 // includes this tab since we haven't been detached yet. If we don't do this, 353 // the dragged representation will be a different size to others in the 354 // tabstrip. 355 int tab_count = attached_tabstrip_->GetTabCount(); 356 int mini_tab_count = attached_tabstrip_->GetMiniTabCount(); 357 if (!tab) 358 ++tab_count; 359 double unselected_width = 0, selected_width = 0; 360 attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count, 361 &unselected_width, &selected_width); 362 int dragged_tab_width = 363 mini_ ? TabGtk::GetMiniWidth() : static_cast<int>(selected_width); 364 dragged_tab_->Attach(dragged_tab_width); 365 366 if (!tab) { 367 // There is no tab in |attached_tabstrip| that corresponds to the dragged 368 // TabContents. We must now create one. 369 370 // Remove ourselves as the delegate now that the dragged TabContents is 371 // being inserted back into a Browser. 372 dragged_contents_->tab_contents()->set_delegate(NULL); 373 original_delegate_ = NULL; 374 375 // Return the TabContents' to normalcy. 376 dragged_contents_->tab_contents()->set_capturing_contents(false); 377 378 // We need to ask the tabstrip we're attached to ensure that the ideal 379 // bounds for all its tabs are correctly generated, because the calculation 380 // in GetInsertionIndexForDraggedBounds needs them to be to figure out the 381 // appropriate insertion index. 382 attached_tabstrip_->GenerateIdealBounds(); 383 384 // Inserting counts as a move. We don't want the tabs to jitter when the 385 // user moves the tab immediately after attaching it. 386 last_move_screen_x_ = screen_point.x(); 387 388 // Figure out where to insert the tab based on the bounds of the dragged 389 // representation and the ideal bounds of the other tabs already in the 390 // strip. ("ideal bounds" are stable even if the tabs' actual bounds are 391 // changing due to animation). 392 gfx::Rect bounds = GetDraggedTabTabStripBounds(screen_point); 393 int index = GetInsertionIndexForDraggedBounds(bounds, false); 394 attached_tabstrip_->model()->InsertTabContentsAt( 395 index, dragged_contents_, 396 TabStripModel::ADD_ACTIVE | 397 (pinned_ ? TabStripModel::ADD_PINNED : 0)); 398 399 tab = GetTabMatchingDraggedContents(attached_tabstrip_); 400 } 401 DCHECK(tab); // We should now have a tab. 402 tab->SetVisible(false); 403 tab->set_dragging(true); 404 405 // TODO(jhawkins): Move the corresponding window to the front. 406 } 407 408 void DraggedTabControllerGtk::Detach() { 409 // Update the Model. 410 TabStripModel* attached_model = attached_tabstrip_->model(); 411 int index = attached_model->GetIndexOfTabContents(dragged_contents_); 412 if (index >= 0 && index < attached_model->count()) { 413 // Sometimes, DetachTabContentsAt has consequences that result in 414 // attached_tabstrip_ being set to NULL, so we need to save it first. 415 TabStripGtk* attached_tabstrip = attached_tabstrip_; 416 attached_model->DetachTabContentsAt(index); 417 attached_tabstrip->SchedulePaint(); 418 } 419 420 // If we've removed the last tab from the tabstrip, hide the frame now. 421 if (attached_model->empty()) 422 HideWindow(); 423 424 // Update the dragged tab. This NULL check is necessary apparently in some 425 // conditions during automation where the view_ is destroyed inside a 426 // function call preceding this point but after it is created. 427 if (dragged_tab_.get()) { 428 dragged_tab_->Detach(); 429 } 430 431 // Detaching resets the delegate, but we still want to be the delegate. 432 dragged_contents_->tab_contents()->set_delegate(this); 433 434 attached_tabstrip_ = NULL; 435 } 436 437 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint( 438 TabStripGtk* tabstrip, const gfx::Point& screen_point) { 439 gfx::Point tabstrip_screen_point = 440 gtk_util::GetWidgetScreenPosition(tabstrip->tabstrip_.get()); 441 return screen_point.Subtract(tabstrip_screen_point); 442 } 443 444 gfx::Rect DraggedTabControllerGtk::GetDraggedTabTabStripBounds( 445 const gfx::Point& screen_point) { 446 gfx::Point client_point = 447 ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); 448 gfx::Size tab_size = dragged_tab_->attached_tab_size(); 449 return gfx::Rect(client_point.x(), client_point.y(), 450 tab_size.width(), tab_size.height()); 451 } 452 453 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds( 454 const gfx::Rect& dragged_bounds, 455 bool is_tab_attached) const { 456 int right_tab_x = 0; 457 int dragged_bounds_x = base::i18n::IsRTL() ? dragged_bounds.right() : 458 dragged_bounds.x(); 459 dragged_bounds_x = 460 gtk_util::MirroredXCoordinate(attached_tabstrip_->widget(), 461 dragged_bounds_x); 462 463 // Divides each tab into two halves to see if the dragged tab has crossed 464 // the halfway boundary necessary to move past the next tab. 465 int index = -1; 466 for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) { 467 gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i); 468 469 gfx::Rect left_half = ideal_bounds; 470 left_half.set_width(left_half.width() / 2); 471 472 gfx::Rect right_half = ideal_bounds; 473 right_half.set_width(ideal_bounds.width() - left_half.width()); 474 right_half.set_x(left_half.right()); 475 476 right_tab_x = right_half.right(); 477 478 if (dragged_bounds_x >= right_half.x() && 479 dragged_bounds_x < right_half.right()) { 480 index = i + 1; 481 break; 482 } else if (dragged_bounds_x >= left_half.x() && 483 dragged_bounds_x < left_half.right()) { 484 index = i; 485 break; 486 } 487 } 488 489 if (index == -1) { 490 bool at_the_end = base::i18n::IsRTL() ? 491 dragged_bounds.x() < right_tab_x : 492 dragged_bounds.right() > right_tab_x; 493 index = at_the_end ? attached_tabstrip_->model()->count() : 0; 494 } 495 496 index = attached_tabstrip_->model()->ConstrainInsertionIndex(index, mini_); 497 if (is_tab_attached && mini_ && 498 index == attached_tabstrip_->model()->IndexOfFirstNonMiniTab()) { 499 index--; 500 } 501 502 return index; 503 } 504 505 gfx::Point DraggedTabControllerGtk::GetDraggedTabPoint( 506 const gfx::Point& screen_point) { 507 int x = screen_point.x() - mouse_offset_.x(); 508 int y = screen_point.y() - mouse_offset_.y(); 509 510 // If we're not attached, we just use x and y from above. 511 if (attached_tabstrip_) { 512 gfx::Rect tabstrip_bounds = 513 gtk_util::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get()); 514 // Snap the dragged tab to the tabstrip if we are attached, detaching 515 // only when the mouse position (screen_point) exceeds the screen bounds 516 // of the tabstrip. 517 if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x()) 518 x = tabstrip_bounds.x(); 519 520 gfx::Size tab_size = dragged_tab_->attached_tab_size(); 521 int vertical_drag_magnetism = tab_size.height() * 2; 522 int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism; 523 if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point) 524 y = tabstrip_bounds.y(); 525 526 // Make sure the tab can't be dragged off the right side of the tabstrip 527 // unless the mouse pointer passes outside the bounds of the strip by 528 // clamping the position of the dragged window to the tabstrip width less 529 // the width of one tab until the mouse pointer (screen_point) exceeds the 530 // screen bounds of the tabstrip. 531 int max_x = tabstrip_bounds.right() - tab_size.width(); 532 int max_y = tabstrip_bounds.bottom() - tab_size.height(); 533 if (x > max_x && screen_point.x() <= tabstrip_bounds.right()) 534 x = max_x; 535 if (y > max_y && screen_point.y() <= 536 (tabstrip_bounds.bottom() + vertical_drag_magnetism)) { 537 y = max_y; 538 } 539 #if defined(OS_CHROMEOS) 540 // We don't allow detaching on chromeos. This restricts dragging to the 541 // source window. 542 x = std::min(std::max(x, tabstrip_bounds.x()), max_x); 543 y = tabstrip_bounds.y(); 544 #endif 545 } 546 return gfx::Point(x, y); 547 } 548 549 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const { 550 if (index >= attached_tabstrip_->model_->count()) 551 return attached_tabstrip_->model_->count() - 1; 552 if (index == TabStripModel::kNoTab) 553 return 0; 554 return index; 555 } 556 557 TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents( 558 TabStripGtk* tabstrip) const { 559 int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_); 560 return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); 561 } 562 563 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) { 564 bring_to_front_timer_.Stop(); 565 566 // WARNING: this may be invoked multiple times. In particular, if deletion 567 // occurs after a delay (as it does when the tab is released in the original 568 // tab strip) and the navigation controller/tab contents is deleted before 569 // the animation finishes, this is invoked twice. The second time through 570 // type == TAB_DESTROYED. 571 572 bool destroy_now = true; 573 if (type == TAB_DESTROYED) { 574 // If we get here it means the NavigationController is going down. Don't 575 // attempt to do any cleanup other than resetting the delegate (if we're 576 // still the delegate). 577 if (dragged_contents_ && 578 dragged_contents_->tab_contents()->delegate() == this) 579 dragged_contents_->tab_contents()->set_delegate(NULL); 580 dragged_contents_ = NULL; 581 } else { 582 // If we never received a drag-motion event, the drag will never have 583 // started in the sense that |dragged_tab_| will be NULL. We don't need to 584 // revert or complete the drag in that case. 585 if (dragged_tab_.get()) { 586 if (type == CANCELED) { 587 RevertDrag(); 588 } else { 589 destroy_now = CompleteDrag(); 590 } 591 } 592 593 if (dragged_contents_ && 594 dragged_contents_->tab_contents()->delegate() == this) 595 dragged_contents_->tab_contents()->set_delegate(original_delegate_); 596 } 597 598 // The delegate of the dragged contents should have been reset. Unset the 599 // original delegate so that we don't attempt to reset the delegate when 600 // deleted. 601 DCHECK(!dragged_contents_ || 602 dragged_contents_->tab_contents()->delegate() != this); 603 original_delegate_ = NULL; 604 605 // If we're not destroyed now, we'll be destroyed asynchronously later. 606 if (destroy_now) 607 source_tabstrip_->DestroyDragController(); 608 609 return destroy_now; 610 } 611 612 void DraggedTabControllerGtk::RevertDrag() { 613 // We save this here because code below will modify |attached_tabstrip_|. 614 bool restore_window = attached_tabstrip_ != source_tabstrip_; 615 if (attached_tabstrip_) { 616 int index = attached_tabstrip_->model()->GetIndexOfTabContents( 617 dragged_contents_); 618 if (attached_tabstrip_ != source_tabstrip_) { 619 // The tab was inserted into another tabstrip. We need to put it back 620 // into the original one. 621 attached_tabstrip_->model()->DetachTabContentsAt(index); 622 // TODO(beng): (Cleanup) seems like we should use Attach() for this 623 // somehow. 624 attached_tabstrip_ = source_tabstrip_; 625 source_tabstrip_->model()->InsertTabContentsAt( 626 source_model_index_, dragged_contents_, 627 TabStripModel::ADD_ACTIVE | 628 (pinned_ ? TabStripModel::ADD_PINNED : 0)); 629 } else { 630 // The tab was moved within the tabstrip where the drag was initiated. 631 // Move it back to the starting location. 632 source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_, 633 true); 634 } 635 } else { 636 // TODO(beng): (Cleanup) seems like we should use Attach() for this 637 // somehow. 638 attached_tabstrip_ = source_tabstrip_; 639 // The tab was detached from the tabstrip where the drag began, and has not 640 // been attached to any other tabstrip. We need to put it back into the 641 // source tabstrip. 642 source_tabstrip_->model()->InsertTabContentsAt( 643 source_model_index_, dragged_contents_, 644 TabStripModel::ADD_ACTIVE | 645 (pinned_ ? TabStripModel::ADD_PINNED : 0)); 646 } 647 648 // If we're not attached to any tab strip, or attached to some other tab 649 // strip, we need to restore the bounds of the original tab strip's frame, in 650 // case it has been hidden. 651 if (restore_window) 652 ShowWindow(); 653 654 source_tab_->SetVisible(true); 655 source_tab_->set_dragging(false); 656 } 657 658 bool DraggedTabControllerGtk::CompleteDrag() { 659 bool destroy_immediately = true; 660 if (attached_tabstrip_) { 661 // We don't need to do anything other than make the tab visible again, 662 // since the dragged tab is going away. 663 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_); 664 gfx::Rect rect = GetTabScreenBounds(tab); 665 dragged_tab_->AnimateToBounds(GetTabScreenBounds(tab), 666 NewCallback(this, &DraggedTabControllerGtk::OnAnimateToBoundsComplete)); 667 destroy_immediately = false; 668 } else { 669 // Compel the model to construct a new window for the detached TabContents. 670 BrowserWindowGtk* window = source_tabstrip_->window(); 671 gfx::Rect window_bounds = window->GetRestoredBounds(); 672 window_bounds.set_origin(GetWindowCreatePoint()); 673 Browser* new_browser = 674 source_tabstrip_->model()->delegate()->CreateNewStripWithContents( 675 dragged_contents_, window_bounds, dock_info_, window->IsMaximized()); 676 TabStripModel* new_model = new_browser->tabstrip_model(); 677 new_model->SetTabPinned(new_model->GetIndexOfTabContents(dragged_contents_), 678 pinned_); 679 new_browser->window()->Show(); 680 CleanUpHiddenFrame(); 681 } 682 683 return destroy_immediately; 684 } 685 686 void DraggedTabControllerGtk::EnsureDraggedTab() { 687 if (!dragged_tab_.get()) { 688 gfx::Rect rect; 689 dragged_contents_->tab_contents()->GetContainerBounds(&rect); 690 691 dragged_tab_.reset(new DraggedTabGtk(dragged_contents_->tab_contents(), 692 mouse_offset_, rect.size(), mini_)); 693 } 694 } 695 696 gfx::Point DraggedTabControllerGtk::GetCursorScreenPoint() const { 697 // Get default display and screen. 698 GdkDisplay* display = gdk_display_get_default(); 699 700 // Get cursor position. 701 int x, y; 702 gdk_display_get_pointer(display, NULL, &x, &y, NULL); 703 704 return gfx::Point(x, y); 705 } 706 707 // static 708 gfx::Rect DraggedTabControllerGtk::GetTabScreenBounds(TabGtk* tab) { 709 // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't 710 // update its allocation until after the widget is shown, so we have to use 711 // the tab bounds we keep track of. 712 // 713 // We use the requested bounds instead of the allocation because the 714 // allocation is relative to the first windowed widget ancestor of the tab. 715 // Because of this, we can't use the tabs allocation to get the screen bounds. 716 gfx::Rect bounds = tab->GetRequisition(); 717 GtkWidget* widget = tab->widget(); 718 GtkWidget* parent = gtk_widget_get_parent(widget); 719 gfx::Point point = gtk_util::GetWidgetScreenPosition(parent); 720 bounds.Offset(point); 721 722 return gfx::Rect(bounds.x(), bounds.y(), bounds.width(), bounds.height()); 723 } 724 725 void DraggedTabControllerGtk::HideWindow() { 726 GtkWidget* tabstrip = source_tabstrip_->widget(); 727 GtkWindow* window = platform_util::GetTopLevel(tabstrip); 728 gtk_widget_hide(GTK_WIDGET(window)); 729 } 730 731 void DraggedTabControllerGtk::ShowWindow() { 732 GtkWidget* tabstrip = source_tabstrip_->widget(); 733 GtkWindow* window = platform_util::GetTopLevel(tabstrip); 734 gtk_window_present(window); 735 } 736 737 void DraggedTabControllerGtk::CleanUpHiddenFrame() { 738 // If the model we started dragging from is now empty, we must ask the 739 // delegate to close the frame. 740 if (source_tabstrip_->model()->empty()) 741 source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession(); 742 } 743 744 void DraggedTabControllerGtk::CleanUpSourceTab() { 745 // If we were attached to the source tabstrip, source tab will be in use 746 // as the tab. If we were detached or attached to another tabstrip, we can 747 // safely remove this item and delete it now. 748 if (attached_tabstrip_ != source_tabstrip_) { 749 source_tabstrip_->DestroyDraggedSourceTab(source_tab_); 750 source_tab_ = NULL; 751 } 752 } 753 754 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() { 755 // Sometimes, for some reason, in automation we can be called back on a 756 // detach even though we aren't attached to a tabstrip. Guard against that. 757 if (attached_tabstrip_) { 758 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_); 759 if (tab) { 760 tab->SetVisible(true); 761 tab->set_dragging(false); 762 // Paint the tab now, otherwise there may be slight flicker between the 763 // time the dragged tab window is destroyed and we paint. 764 tab->SchedulePaint(); 765 } 766 } 767 768 CleanUpHiddenFrame(); 769 770 if (!in_destructor_) 771 source_tabstrip_->DestroyDragController(); 772 } 773 774 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() { 775 // If we're going to dock to another window, bring it to the front. 776 gfx::NativeWindow window = dock_info_.window(); 777 if (!window) { 778 gfx::NativeView dragged_tab = dragged_tab_->widget(); 779 dock_windows_.insert(dragged_tab); 780 window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(), 781 dock_windows_); 782 dock_windows_.erase(dragged_tab); 783 } 784 785 if (window) 786 gtk_window_present(GTK_WINDOW(window)); 787 } 788