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