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.
5 #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.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/media_utils.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/web_contents_view.h"
26 #include "ui/base/gtk/gtk_screen_util.h"
27 #include "ui/gfx/screen.h"
29 using content::OpenURLParams
;
30 using content::WebContents
;
34 // Delay, in ms, during dragging before we bring a window to front.
35 const int kBringToFrontDelay
= 750;
37 // Used to determine how far a tab must obscure another tab in order to swap
39 const int kHorizontalMoveThreshold
= 16; // pixels
41 // How far a drag must pull a tab out of the tabstrip in order to detach it.
42 const int kVerticalDetachMagnetism
= 15; // pixels
44 // Returns the bounds that will be used for insertion index calculation.
45 // |bounds| is the actual tab bounds, but subtracting the overlapping areas from
46 // both sides makes the calculations much simpler.
47 gfx::Rect
GetEffectiveBounds(const gfx::Rect
& bounds
) {
48 gfx::Rect
effective_bounds(bounds
);
49 effective_bounds
.set_width(effective_bounds
.width() - 2 * 16);
50 effective_bounds
.set_x(effective_bounds
.x() + 16);
51 return effective_bounds
;
56 DraggedTabControllerGtk::DraggedTabControllerGtk(
57 TabStripGtk
* source_tabstrip
,
59 const std::vector
<TabGtk
*>& tabs
)
60 : source_tabstrip_(source_tabstrip
),
61 attached_tabstrip_(source_tabstrip
),
62 in_destructor_(false),
63 last_move_screen_x_(0),
65 DCHECK(!tabs
.empty());
66 DCHECK(std::find(tabs
.begin(), tabs
.end(), source_tab
) != tabs
.end());
68 std::vector
<DraggedTabData
> drag_data
;
69 for (size_t i
= 0; i
< tabs
.size(); i
++)
70 drag_data
.push_back(InitDraggedTabData(tabs
[i
]));
72 int source_tab_index
=
73 std::find(tabs
.begin(), tabs
.end(), source_tab
) - tabs
.begin();
74 drag_data_
.reset(new DragData(drag_data
, source_tab_index
));
77 DraggedTabControllerGtk::~DraggedTabControllerGtk() {
78 in_destructor_
= true;
79 // Need to delete the dragged tab here manually _before_ we reset the dragged
80 // contents to NULL, otherwise if the view is animating to its destination
81 // bounds, it won't be able to clean up properly since its cleanup routine
82 // uses GetIndexForDraggedContents, which will be invalid.
84 dragged_view_
.reset();
88 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point
& mouse_offset
) {
89 start_screen_point_
= gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
90 mouse_offset_
= mouse_offset
;
93 void DraggedTabControllerGtk::Drag() {
94 if (!drag_data_
->GetSourceTabData()->tab_
||
95 !drag_data_
->GetSourceWebContents()) {
99 bring_to_front_timer_
.Stop();
103 // Before we get to dragging anywhere, ensure that we consider ourselves
104 // attached to the source tabstrip.
105 if (drag_data_
->GetSourceTabData()->tab_
->IsVisible()) {
106 Attach(source_tabstrip_
, gfx::Point());
109 if (!drag_data_
->GetSourceTabData()->tab_
->IsVisible()) {
110 // TODO(jhawkins): Save focus.
115 bool DraggedTabControllerGtk::EndDrag(bool canceled
) {
116 return EndDragImpl(canceled
? CANCELED
: NORMAL
);
119 TabGtk
* DraggedTabControllerGtk::GetDraggedTabForContents(
120 WebContents
* contents
) {
121 if (attached_tabstrip_
== source_tabstrip_
) {
122 for (size_t i
= 0; i
< drag_data_
->size(); i
++) {
123 if (contents
== drag_data_
->get(i
)->contents_
)
124 return drag_data_
->get(i
)->tab_
;
130 bool DraggedTabControllerGtk::IsDraggingTab(const TabGtk
* tab
) {
131 for (size_t i
= 0; i
< drag_data_
->size(); i
++) {
132 if (tab
== drag_data_
->get(i
)->tab_
)
138 bool DraggedTabControllerGtk::IsDraggingWebContents(
139 const WebContents
* web_contents
) {
140 for (size_t i
= 0; i
< drag_data_
->size(); i
++) {
141 if (web_contents
== drag_data_
->get(i
)->contents_
)
147 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk
* tab
) {
148 return IsDraggingTab(tab
) && attached_tabstrip_
== NULL
;
151 DraggedTabData
DraggedTabControllerGtk::InitDraggedTabData(TabGtk
* tab
) {
152 int source_model_index
= source_tabstrip_
->GetIndexOfTab(tab
);
153 WebContents
* contents
=
154 source_tabstrip_
->model()->GetWebContentsAt(source_model_index
);
155 bool pinned
= source_tabstrip_
->IsTabPinned(tab
);
156 bool mini
= source_tabstrip_
->model()->IsMiniTab(source_model_index
);
157 // We need to be the delegate so we receive messages about stuff,
158 // otherwise our dragged_contents() may be replaced and subsequently
159 // collected/destroyed while the drag is in process, leading to
161 content::WebContentsDelegate
* original_delegate
= contents
->GetDelegate();
162 contents
->SetDelegate(this);
164 DraggedTabData
dragged_tab_data(tab
, contents
, original_delegate
,
165 source_model_index
, pinned
, mini
);
166 registrar_
.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
167 content::Source
<WebContents
>(contents
));
168 return dragged_tab_data
;
171 ////////////////////////////////////////////////////////////////////////////////
172 // DraggedTabControllerGtk, content::WebContentsDelegate implementation:
174 WebContents
* DraggedTabControllerGtk::OpenURLFromTab(
176 const OpenURLParams
& params
) {
177 if (drag_data_
->GetSourceTabData()->original_delegate_
) {
178 OpenURLParams forward_params
= params
;
179 if (params
.disposition
== CURRENT_TAB
)
180 forward_params
.disposition
= NEW_WINDOW
;
182 return drag_data_
->GetSourceTabData()->original_delegate_
->
183 OpenURLFromTab(source
, forward_params
);
188 void DraggedTabControllerGtk::NavigationStateChanged(const WebContents
* source
,
189 unsigned changed_flags
) {
190 if (dragged_view_
.get())
191 dragged_view_
->Update();
194 void DraggedTabControllerGtk::AddNewContents(WebContents
* source
,
195 WebContents
* new_contents
,
196 WindowOpenDisposition disposition
,
197 const gfx::Rect
& initial_pos
,
200 DCHECK(disposition
!= CURRENT_TAB
);
202 // Theoretically could be called while dragging if the page tries to
203 // spawn a window. Route this message back to the browser in most cases.
204 if (drag_data_
->GetSourceTabData()->original_delegate_
) {
205 drag_data_
->GetSourceTabData()->original_delegate_
->AddNewContents(
206 source
, new_contents
, disposition
, initial_pos
, user_gesture
,
211 void DraggedTabControllerGtk::LoadingStateChanged(WebContents
* source
) {
212 // TODO(jhawkins): It would be nice to respond to this message by changing the
213 // screen shot in the dragged tab.
214 if (dragged_view_
.get())
215 dragged_view_
->Update();
218 content::JavaScriptDialogManager
*
219 DraggedTabControllerGtk::GetJavaScriptDialogManager() {
220 return GetJavaScriptDialogManagerInstance();
223 ////////////////////////////////////////////////////////////////////////////////
224 // DraggedTabControllerGtk, content::NotificationObserver implementation:
226 void DraggedTabControllerGtk::Observe(
228 const content::NotificationSource
& source
,
229 const content::NotificationDetails
& details
) {
230 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED
, type
);
231 WebContents
* destroyed_web_contents
=
232 content::Source
<WebContents
>(source
).ptr();
233 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
234 if (drag_data_
->get(i
)->contents_
== destroyed_web_contents
) {
235 // One of the tabs we're dragging has been destroyed. Cancel the drag.
236 if (destroyed_web_contents
->GetDelegate() == this)
237 destroyed_web_contents
->SetDelegate(NULL
);
238 drag_data_
->get(i
)->contents_
= NULL
;
239 drag_data_
->get(i
)->original_delegate_
= NULL
;
240 EndDragImpl(TAB_DESTROYED
);
244 // If we get here it means we got notification for a tab we don't know about.
248 void DraggedTabControllerGtk::RequestMediaAccessPermission(
249 content::WebContents
* web_contents
,
250 const content::MediaStreamRequest
& request
,
251 const content::MediaResponseCallback
& callback
) {
252 ::RequestMediaAccessPermission(
254 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
259 gfx::Point
DraggedTabControllerGtk::GetWindowCreatePoint() const {
260 gfx::Point creation_point
=
261 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
262 gfx::Point distance_from_origin
=
263 dragged_view_
->GetDistanceFromTabStripOriginToMousePointer();
264 // TODO(dpapad): offset also because of tabstrip origin being different than
266 creation_point
.Offset(-distance_from_origin
.x(), -distance_from_origin
.y());
267 return creation_point
;
270 void DraggedTabControllerGtk::ContinueDragging() {
271 // TODO(jhawkins): We don't handle the situation where the last tab is dragged
272 // out of a window, so we'll just go with the way Windows handles dragging for
274 gfx::Point screen_point
=
275 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
277 // Determine whether or not we have dragged over a compatible TabStrip in
278 // another browser window. If we have, we should attach to it and start
279 // dragging within it.
280 TabStripGtk
* target_tabstrip
= GetTabStripForPoint(screen_point
);
281 if (target_tabstrip
!= attached_tabstrip_
) {
282 // Make sure we're fully detached from whatever TabStrip we're attached to
284 if (attached_tabstrip_
)
288 Attach(target_tabstrip
, screen_point
);
291 if (!target_tabstrip
) {
292 bring_to_front_timer_
.Start(FROM_HERE
,
293 base::TimeDelta::FromMilliseconds(kBringToFrontDelay
), this,
294 &DraggedTabControllerGtk::BringWindowUnderMouseToFront
);
297 if (attached_tabstrip_
)
298 MoveAttached(screen_point
);
300 MoveDetached(screen_point
);
303 void DraggedTabControllerGtk::RestoreSelection(TabStripModel
* model
) {
304 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
305 WebContents
* contents
= drag_data_
->get(i
)->contents_
;
306 // If a tab is destroyed while dragging contents might be null. See
307 // http://crbug.com/115409.
309 int index
= model
->GetIndexOfWebContents(contents
);
310 CHECK(index
!= TabStripModel::kNoTab
);
311 model
->AddTabAtToSelection(index
);
316 void DraggedTabControllerGtk::MoveAttached(const gfx::Point
& screen_point
) {
317 DCHECK(attached_tabstrip_
);
319 gfx::Point dragged_view_point
= GetDraggedViewPoint(screen_point
);
320 TabStripModel
* attached_model
= attached_tabstrip_
->model();
322 std::vector
<TabGtk
*> tabs(drag_data_
->GetDraggedTabs());
324 // Determine the horizontal move threshold. This is dependent on the width
325 // of tabs. The smaller the tabs compared to the standard size, the smaller
327 double unselected
, selected
;
328 attached_tabstrip_
->GetCurrentTabWidths(&unselected
, &selected
);
329 double ratio
= unselected
/ TabGtk::GetStandardSize().width();
330 int threshold
= static_cast<int>(ratio
* kHorizontalMoveThreshold
);
332 // Update the model, moving the WebContents from one index to another.
333 // Do this only if we have moved a minimum distance since the last reorder (to
334 // prevent jitter) or if this is the first move and the tabs are not
336 if (abs(screen_point
.x() - last_move_screen_x_
) > threshold
||
337 (initial_move_
&& !AreTabsConsecutive())) {
338 if (initial_move_
&& !AreTabsConsecutive()) {
339 // Making dragged tabs adjacent, this is done only once, if necessary.
340 attached_tabstrip_
->model()->MoveSelectedTabsTo(
341 drag_data_
->GetSourceTabData()->source_model_index_
-
342 drag_data_
->source_tab_index());
344 gfx::Rect bounds
= GetDraggedViewTabStripBounds(dragged_view_point
);
345 int to_index
= GetInsertionIndexForDraggedBounds(
346 GetEffectiveBounds(bounds
));
347 to_index
= NormalizeIndexToAttachedTabStrip(to_index
);
348 last_move_screen_x_
= screen_point
.x();
349 attached_model
->MoveSelectedTabsTo(to_index
);
352 dragged_view_
->MoveAttachedTo(dragged_view_point
);
353 initial_move_
= false;
356 void DraggedTabControllerGtk::MoveDetached(const gfx::Point
& screen_point
) {
357 DCHECK(!attached_tabstrip_
);
358 // Just moving the dragged view. There are no changes to the model if we're
360 dragged_view_
->MoveDetachedTo(screen_point
);
363 TabStripGtk
* DraggedTabControllerGtk::GetTabStripForPoint(
364 const gfx::Point
& screen_point
) {
365 GtkWidget
* dragged_window
= dragged_view_
->widget();
366 dock_windows_
.insert(dragged_window
);
367 gfx::NativeWindow local_window
=
368 DockInfo::GetLocalProcessWindowAtPoint(
369 chrome::HOST_DESKTOP_TYPE_NATIVE
, screen_point
, dock_windows_
);
370 dock_windows_
.erase(dragged_window
);
374 BrowserWindowGtk
* browser
=
375 BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window
);
379 TabStripGtk
* other_tabstrip
= browser
->tabstrip();
380 if (!other_tabstrip
->IsCompatibleWith(source_tabstrip_
))
383 return GetTabStripIfItContains(other_tabstrip
, screen_point
);
386 TabStripGtk
* DraggedTabControllerGtk::GetTabStripIfItContains(
387 TabStripGtk
* tabstrip
, const gfx::Point
& screen_point
) const {
388 // Make sure the specified screen point is actually within the bounds of the
389 // specified tabstrip...
390 gfx::Rect tabstrip_bounds
=
391 ui::GetWidgetScreenBounds(tabstrip
->tabstrip_
.get());
392 if (screen_point
.x() < tabstrip_bounds
.right() &&
393 screen_point
.x() >= tabstrip_bounds
.x()) {
394 // TODO(beng): make this be relative to the start position of the mouse for
395 // the source TabStrip.
396 int upper_threshold
= tabstrip_bounds
.bottom() + kVerticalDetachMagnetism
;
397 int lower_threshold
= tabstrip_bounds
.y() - kVerticalDetachMagnetism
;
398 if (screen_point
.y() >= lower_threshold
&&
399 screen_point
.y() <= upper_threshold
) {
407 void DraggedTabControllerGtk::Attach(TabStripGtk
* attached_tabstrip
,
408 const gfx::Point
& screen_point
) {
409 attached_tabstrip_
= attached_tabstrip
;
410 attached_tabstrip_
->GenerateIdealBounds();
412 std::vector
<TabGtk
*> attached_dragged_tabs
=
413 GetTabsMatchingDraggedContents(attached_tabstrip_
);
415 // Update the tab first, so we can ask it for its bounds and determine
416 // where to insert the hidden tab.
418 // If this is the first time Attach is called for this drag, we're attaching
419 // to the source tabstrip, and we should assume the tab count already
420 // includes this tab since we haven't been detached yet. If we don't do this,
421 // the dragged representation will be a different size to others in the
423 int tab_count
= attached_tabstrip_
->GetTabCount();
424 int mini_tab_count
= attached_tabstrip_
->GetMiniTabCount();
425 if (attached_dragged_tabs
.size() == 0) {
426 tab_count
+= drag_data_
->size();
427 mini_tab_count
+= drag_data_
->mini_tab_count();
430 double unselected_width
= 0, selected_width
= 0;
431 attached_tabstrip_
->GetDesiredTabWidths(tab_count
, mini_tab_count
,
432 &unselected_width
, &selected_width
);
434 GtkWidget
* parent_window
= gtk_widget_get_parent(
435 gtk_widget_get_parent(attached_tabstrip_
->tabstrip_
.get()));
436 gfx::Rect window_bounds
= ui::GetWidgetScreenBounds(parent_window
);
437 dragged_view_
->Attach(static_cast<int>(floor(selected_width
+ 0.5)),
438 TabGtk::GetMiniWidth(), window_bounds
.width());
440 if (attached_dragged_tabs
.size() == 0) {
441 // There is no tab in |attached_tabstrip| that corresponds to the dragged
442 // WebContents. We must now create one.
444 // Remove ourselves as the delegate now that the dragged WebContents
445 // is being inserted back into a Browser.
446 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
447 drag_data_
->get(i
)->contents_
->SetDelegate(NULL
);
448 drag_data_
->get(i
)->original_delegate_
= NULL
;
451 // We need to ask the tabstrip we're attached to ensure that the ideal
452 // bounds for all its tabs are correctly generated, because the calculation
453 // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
454 // appropriate insertion index.
455 attached_tabstrip_
->GenerateIdealBounds();
457 // Inserting counts as a move. We don't want the tabs to jitter when the
458 // user moves the tab immediately after attaching it.
459 last_move_screen_x_
= screen_point
.x();
461 // Figure out where to insert the tab based on the bounds of the dragged
462 // representation and the ideal bounds of the other tabs already in the
463 // strip. ("ideal bounds" are stable even if the tabs' actual bounds are
464 // changing due to animation).
466 GetDraggedViewTabStripBounds(GetDraggedViewPoint(screen_point
));
467 int index
= GetInsertionIndexForDraggedBounds(GetEffectiveBounds(bounds
));
468 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
469 attached_tabstrip_
->model()->InsertWebContentsAt(
470 index
+ i
, drag_data_
->get(i
)->contents_
,
471 drag_data_
->GetAddTypesForDraggedTabAt(i
));
473 RestoreSelection(attached_tabstrip_
->model());
474 attached_dragged_tabs
= GetTabsMatchingDraggedContents(attached_tabstrip_
);
476 // We should now have a tab.
477 DCHECK(attached_dragged_tabs
.size() == drag_data_
->size());
478 SetDraggedTabsVisible(false, false);
479 // TODO(jhawkins): Move the corresponding window to the front.
482 void DraggedTabControllerGtk::Detach() {
484 TabStripModel
* attached_model
= attached_tabstrip_
->model();
485 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
487 attached_model
->GetIndexOfWebContents(drag_data_
->get(i
)->contents_
);
488 if (index
>= 0 && index
< attached_model
->count()) {
489 // Sometimes, DetachWebContentsAt has consequences that result in
490 // attached_tabstrip_ being set to NULL, so we need to save it first.
491 attached_model
->DetachWebContentsAt(index
);
495 // If we've removed the last tab from the tabstrip, hide the frame now.
496 if (attached_model
->empty())
499 // Update the dragged tab. This NULL check is necessary apparently in some
500 // conditions during automation where the view_ is destroyed inside a
501 // function call preceding this point but after it is created.
502 if (dragged_view_
.get()) {
503 dragged_view_
->Detach();
506 // Detaching resets the delegate, but we still want to be the delegate.
507 for (size_t i
= 0; i
< drag_data_
->size(); ++i
)
508 drag_data_
->get(i
)->contents_
->SetDelegate(this);
510 attached_tabstrip_
= NULL
;
513 gfx::Point
DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint(
514 TabStripGtk
* tabstrip
, const gfx::Point
& screen_point
) {
515 return screen_point
- ui::GetWidgetScreenOffset(tabstrip
->tabstrip_
.get());
518 gfx::Rect
DraggedTabControllerGtk::GetDraggedViewTabStripBounds(
519 const gfx::Point
& screen_point
) {
520 gfx::Point client_point
=
521 ConvertScreenPointToTabStripPoint(attached_tabstrip_
, screen_point
);
522 gfx::Size tab_size
= dragged_view_
->attached_tab_size();
523 return gfx::Rect(client_point
.x(), client_point
.y(),
524 dragged_view_
->GetTotalWidthInTabStrip(), tab_size
.height());
527 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds(
528 const gfx::Rect
& dragged_bounds
) {
529 int dragged_bounds_start
= gtk_util::MirroredLeftPointForRect(
530 attached_tabstrip_
->widget(),
532 int dragged_bounds_end
= gtk_util::MirroredRightPointForRect(
533 attached_tabstrip_
->widget(),
538 for (int i
= 0; i
< attached_tabstrip_
->GetTabCount(); i
++) {
539 // Divides each tab into two halves to see if the dragged tab has crossed
540 // the halfway boundary necessary to move past the next tab.
541 gfx::Rect ideal_bounds
= attached_tabstrip_
->GetIdealBounds(i
);
542 gfx::Rect left_half
, right_half
;
543 ideal_bounds
.SplitVertically(&left_half
, &right_half
);
544 right_tab_x
= right_half
.x();
546 if (dragged_bounds_start
>= right_half
.x() &&
547 dragged_bounds_start
< right_half
.right()) {
550 } else if (dragged_bounds_start
>= left_half
.x() &&
551 dragged_bounds_start
< left_half
.right()) {
558 if (dragged_bounds_end
> right_tab_x
)
559 index
= attached_tabstrip_
->GetTabCount();
564 TabGtk
* tab
= GetTabMatchingDraggedContents(
565 attached_tabstrip_
, drag_data_
->GetSourceWebContents());
567 // If dragged tabs are not attached yet, we don't need to constrain the
573 attached_tabstrip_
-> GetTabCount() - static_cast<int>(drag_data_
->size());
574 return std::max(0, std::min(max_index
, index
));
577 gfx::Point
DraggedTabControllerGtk::GetDraggedViewPoint(
578 const gfx::Point
& screen_point
) {
579 int x
= screen_point
.x() -
580 dragged_view_
->GetWidthInTabStripUpToMousePointer();
581 int y
= screen_point
.y() - mouse_offset_
.y();
583 // If we're not attached, we just use x and y from above.
584 if (attached_tabstrip_
) {
585 gfx::Rect tabstrip_bounds
=
586 ui::GetWidgetScreenBounds(attached_tabstrip_
->tabstrip_
.get());
587 // Snap the dragged tab to the tabstrip if we are attached, detaching
588 // only when the mouse position (screen_point) exceeds the screen bounds
590 if (x
< tabstrip_bounds
.x() && screen_point
.x() >= tabstrip_bounds
.x())
591 x
= tabstrip_bounds
.x();
593 gfx::Size tab_size
= dragged_view_
->attached_tab_size();
594 int vertical_drag_magnetism
= tab_size
.height() * 2;
595 int vertical_detach_point
= tabstrip_bounds
.y() - vertical_drag_magnetism
;
596 if (y
< tabstrip_bounds
.y() && screen_point
.y() >= vertical_detach_point
)
597 y
= tabstrip_bounds
.y();
599 // Make sure the tab can't be dragged off the right side of the tabstrip
600 // unless the mouse pointer passes outside the bounds of the strip by
601 // clamping the position of the dragged window to the tabstrip width less
602 // the width of one tab until the mouse pointer (screen_point) exceeds the
603 // screen bounds of the tabstrip.
605 tabstrip_bounds
.right() - dragged_view_
->GetTotalWidthInTabStrip();
606 int max_y
= tabstrip_bounds
.bottom() - tab_size
.height();
607 if (x
> max_x
&& screen_point
.x() <= tabstrip_bounds
.right())
609 if (y
> max_y
&& screen_point
.y() <=
610 (tabstrip_bounds
.bottom() + vertical_drag_magnetism
)) {
614 return gfx::Point(x
, y
);
617 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index
) const {
618 if (index
>= attached_tabstrip_
->model_
->count())
619 return attached_tabstrip_
->model_
->count() - 1;
620 if (index
== TabStripModel::kNoTab
)
625 TabGtk
* DraggedTabControllerGtk::GetTabMatchingDraggedContents(
626 TabStripGtk
* tabstrip
, WebContents
* web_contents
) {
627 int index
= tabstrip
->model()->GetIndexOfWebContents(web_contents
);
628 return index
== TabStripModel::kNoTab
? NULL
: tabstrip
->GetTabAt(index
);
631 std::vector
<TabGtk
*> DraggedTabControllerGtk::GetTabsMatchingDraggedContents(
632 TabStripGtk
* tabstrip
) {
633 std::vector
<TabGtk
*> dragged_tabs
;
634 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
635 if (!drag_data_
->get(i
)->contents_
)
637 TabGtk
* tab
= GetTabMatchingDraggedContents(tabstrip
,
638 drag_data_
->get(i
)->contents_
);
640 dragged_tabs
.push_back(tab
);
645 void DraggedTabControllerGtk::SetDraggedTabsVisible(bool visible
,
647 std::vector
<TabGtk
*> dragged_tabs(GetTabsMatchingDraggedContents(
648 attached_tabstrip_
));
649 for (size_t i
= 0; i
< dragged_tabs
.size(); ++i
) {
650 dragged_tabs
[i
]->SetVisible(visible
);
651 dragged_tabs
[i
]->set_dragging(!visible
);
653 dragged_tabs
[i
]->SchedulePaint();
657 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type
) {
658 bring_to_front_timer_
.Stop();
660 // WARNING: this may be invoked multiple times. In particular, if deletion
661 // occurs after a delay (as it does when the tab is released in the original
662 // tab strip) and the navigation controller/tab contents is deleted before
663 // the animation finishes, this is invoked twice. The second time through
664 // type == TAB_DESTROYED.
666 bool destroy_now
= true;
667 if (type
!= TAB_DESTROYED
) {
668 // If we never received a drag-motion event, the drag will never have
669 // started in the sense that |dragged_view_| will be NULL. We don't need to
670 // revert or complete the drag in that case.
671 if (dragged_view_
.get()) {
672 if (type
== CANCELED
) {
675 destroy_now
= CompleteDrag();
678 } else if (drag_data_
->size() > 0) {
684 // If we're not destroyed now, we'll be destroyed asynchronously later.
686 source_tabstrip_
->DestroyDragController();
691 void DraggedTabControllerGtk::RevertDrag() {
692 // We save this here because code below will modify |attached_tabstrip_|.
693 bool restore_window
= attached_tabstrip_
!= source_tabstrip_
;
694 if (attached_tabstrip_
) {
695 if (attached_tabstrip_
!= source_tabstrip_
) {
696 // The tabs were inserted into another tabstrip. We need to put them back
697 // into the original one.
698 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
699 if (!drag_data_
->get(i
)->contents_
)
701 int index
= attached_tabstrip_
->model()->GetIndexOfWebContents(
702 drag_data_
->get(i
)->contents_
);
703 attached_tabstrip_
->model()->DetachWebContentsAt(index
);
705 // TODO(beng): (Cleanup) seems like we should use Attach() for this
707 attached_tabstrip_
= source_tabstrip_
;
708 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
709 if (!drag_data_
->get(i
)->contents_
)
711 attached_tabstrip_
->model()->InsertWebContentsAt(
712 drag_data_
->get(i
)->source_model_index_
,
713 drag_data_
->get(i
)->contents_
,
714 drag_data_
->GetAddTypesForDraggedTabAt(i
));
717 // The tabs were moved within the tabstrip where the drag was initiated.
718 // Move them back to their starting locations.
719 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
720 if (!drag_data_
->get(i
)->contents_
)
722 int index
= attached_tabstrip_
->model()->GetIndexOfWebContents(
723 drag_data_
->get(i
)->contents_
);
724 source_tabstrip_
->model()->MoveWebContentsAt(
725 index
, drag_data_
->get(i
)->source_model_index_
, true);
729 // TODO(beng): (Cleanup) seems like we should use Attach() for this
731 attached_tabstrip_
= source_tabstrip_
;
732 // The tab was detached from the tabstrip where the drag began, and has not
733 // been attached to any other tabstrip. We need to put it back into the
735 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
736 if (!drag_data_
->get(i
)->contents_
)
738 source_tabstrip_
->model()->InsertWebContentsAt(
739 drag_data_
->get(i
)->source_model_index_
,
740 drag_data_
->get(i
)->contents_
,
741 drag_data_
->GetAddTypesForDraggedTabAt(i
));
744 RestoreSelection(source_tabstrip_
->model());
746 // If we're not attached to any tab strip, or attached to some other tab
747 // strip, we need to restore the bounds of the original tab strip's frame, in
748 // case it has been hidden.
752 SetDraggedTabsVisible(true, false);
755 bool DraggedTabControllerGtk::CompleteDrag() {
756 bool destroy_immediately
= true;
757 if (attached_tabstrip_
) {
758 // We don't need to do anything other than make the tabs visible again,
759 // since the dragged view is going away.
760 dragged_view_
->AnimateToBounds(GetAnimateBounds(),
761 base::Bind(&DraggedTabControllerGtk::OnAnimateToBoundsComplete
,
762 base::Unretained(this)));
763 destroy_immediately
= false;
765 // Compel the model to construct a new window for the detached
767 BrowserWindowGtk
* window
= source_tabstrip_
->window();
768 gfx::Rect window_bounds
= window
->GetRestoredBounds();
769 window_bounds
.set_origin(GetWindowCreatePoint());
771 std::vector
<TabStripModelDelegate::NewStripContents
> contentses
;
772 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
773 TabStripModelDelegate::NewStripContents item
;
774 item
.web_contents
= drag_data_
->get(i
)->contents_
;
775 item
.add_types
= drag_data_
->GetAddTypesForDraggedTabAt(i
);
776 contentses
.push_back(item
);
779 Browser
* new_browser
=
780 source_tabstrip_
->model()->delegate()->CreateNewStripWithContents(
781 contentses
, window_bounds
, dock_info_
, window
->IsMaximized());
782 RestoreSelection(new_browser
->tab_strip_model());
783 new_browser
->window()->Show();
784 CleanUpHiddenFrame();
787 return destroy_immediately
;
790 void DraggedTabControllerGtk::ResetDelegates() {
791 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
792 if (drag_data_
->get(i
)->contents_
&&
793 drag_data_
->get(i
)->contents_
->GetDelegate() == this) {
794 drag_data_
->get(i
)->ResetDelegate();
799 void DraggedTabControllerGtk::EnsureDraggedView() {
800 if (!dragged_view_
.get()) {
802 drag_data_
->GetSourceWebContents()->GetView()->GetContainerBounds(&rect
);
803 dragged_view_
.reset(new DraggedViewGtk(drag_data_
.get(), mouse_offset_
,
808 gfx::Rect
DraggedTabControllerGtk::GetAnimateBounds() {
809 // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't
810 // update its allocation until after the widget is shown, so we have to use
811 // the tab bounds we keep track of.
813 // We use the requested bounds instead of the allocation because the
814 // allocation is relative to the first windowed widget ancestor of the tab.
815 // Because of this, we can't use the tabs allocation to get the screen bounds.
816 std::vector
<TabGtk
*> tabs
= GetTabsMatchingDraggedContents(
818 TabGtk
* tab
= !base::i18n::IsRTL() ? tabs
.front() : tabs
.back();
819 gfx::Rect bounds
= tab
->GetRequisition();
820 GtkWidget
* widget
= tab
->widget();
821 GtkWidget
* parent
= gtk_widget_get_parent(widget
);
822 bounds
.Offset(ui::GetWidgetScreenOffset(parent
));
824 return gfx::Rect(bounds
.x(), bounds
.y(),
825 dragged_view_
->GetTotalWidthInTabStrip(), bounds
.height());
828 void DraggedTabControllerGtk::HideWindow() {
829 GtkWidget
* tabstrip
= source_tabstrip_
->widget();
830 GtkWindow
* window
= platform_util::GetTopLevel(tabstrip
);
831 gtk_widget_hide(GTK_WIDGET(window
));
834 void DraggedTabControllerGtk::ShowWindow() {
835 GtkWidget
* tabstrip
= source_tabstrip_
->widget();
836 GtkWindow
* window
= platform_util::GetTopLevel(tabstrip
);
837 gtk_window_present(window
);
840 void DraggedTabControllerGtk::CleanUpHiddenFrame() {
841 // If the model we started dragging from is now empty, we must ask the
842 // delegate to close the frame.
843 if (source_tabstrip_
->model()->empty())
844 source_tabstrip_
->model()->delegate()->CloseFrameAfterDragSession();
847 void DraggedTabControllerGtk::CleanUpDraggedTabs() {
848 // If we were attached to the source tabstrip, dragged tabs will be in use. If
849 // we were detached or attached to another tabstrip, we can safely remove
850 // them and delete them now.
851 if (attached_tabstrip_
!= source_tabstrip_
) {
852 for (size_t i
= 0; i
< drag_data_
->size(); ++i
) {
853 if (drag_data_
->get(i
)->contents_
) {
854 registrar_
.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
855 content::Source
<WebContents
>(drag_data_
->get(i
)->contents_
));
857 source_tabstrip_
->DestroyDraggedTab(drag_data_
->get(i
)->tab_
);
858 drag_data_
->get(i
)->tab_
= NULL
;
863 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() {
864 // Sometimes, for some reason, in automation we can be called back on a
865 // detach even though we aren't attached to a tabstrip. Guard against that.
866 if (attached_tabstrip_
)
867 SetDraggedTabsVisible(true, true);
869 CleanUpHiddenFrame();
872 source_tabstrip_
->DestroyDragController();
875 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() {
876 // If we're going to dock to another window, bring it to the front.
877 gfx::NativeWindow window
= dock_info_
.window();
879 gfx::NativeView dragged_tab
= dragged_view_
->widget();
880 dock_windows_
.insert(dragged_tab
);
881 window
= DockInfo::GetLocalProcessWindowAtPoint(
882 chrome::HOST_DESKTOP_TYPE_NATIVE
,
883 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
885 dock_windows_
.erase(dragged_tab
);
889 gtk_window_present(GTK_WINDOW(window
));
892 bool DraggedTabControllerGtk::AreTabsConsecutive() {
893 for (size_t i
= 1; i
< drag_data_
->size(); ++i
) {
894 if (drag_data_
->get(i
- 1)->source_model_index_
+ 1 !=
895 drag_data_
->get(i
)->source_model_index_
) {