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