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 |