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