Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / tabs / dragged_tab_controller_gtk.cc
blob586dc63b50fa12b6f5bf6837cb11e0e79d2b681a
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"
7 #include <algorithm>
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/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;
32 namespace {
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
38 // their indexes.
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;
54 } // namespace
56 DraggedTabControllerGtk::DraggedTabControllerGtk(
57 TabStripGtk* source_tabstrip,
58 TabGtk* source_tab,
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),
64 initial_move_(true) {
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.
83 CleanUpDraggedTabs();
84 dragged_view_.reset();
85 drag_data_.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()) {
96 return;
99 bring_to_front_timer_.Stop();
101 EnsureDraggedView();
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.
111 ContinueDragging();
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_;
127 return NULL;
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_)
133 return true;
135 return false;
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_)
142 return true;
144 return false;
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
160 // nasty crashes.
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(
175 WebContents* source,
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);
185 return NULL;
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,
198 bool user_gesture,
199 bool* was_blocked) {
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,
207 was_blocked);
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(
227 int type,
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);
241 return;
244 // If we get here it means we got notification for a tab we don't know about.
245 NOTREACHED();
248 void DraggedTabControllerGtk::RequestMediaAccessPermission(
249 content::WebContents* web_contents,
250 const content::MediaStreamRequest& request,
251 const content::MediaResponseCallback& callback) {
252 ::RequestMediaAccessPermission(
253 web_contents,
254 Profile::FromBrowserContext(web_contents->GetBrowserContext()),
255 request,
256 callback);
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
265 // window origin.
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
273 // now.
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
283 // (if any).
284 if (attached_tabstrip_)
285 Detach();
287 if (target_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);
299 else
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.
308 if (contents) {
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
326 // the threshold.
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
335 // consecutive.
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
359 // detached.
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);
371 if (!local_window)
372 return NULL;
374 BrowserWindowGtk* browser =
375 BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window);
376 if (!browser)
377 return NULL;
379 TabStripGtk* other_tabstrip = browser->tabstrip();
380 if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
381 return NULL;
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) {
400 return tabstrip;
404 return NULL;
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
422 // tabstrip.
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).
465 gfx::Rect bounds =
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() {
483 // Update the Model.
484 TabStripModel* attached_model = attached_tabstrip_->model();
485 for (size_t i = 0; i < drag_data_->size(); ++i) {
486 int index =
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())
497 HideWindow();
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(),
531 dragged_bounds);
532 int dragged_bounds_end = gtk_util::MirroredRightPointForRect(
533 attached_tabstrip_->widget(),
534 dragged_bounds);
535 int right_tab_x = 0;
536 int index = -1;
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()) {
548 index = i + 1;
549 break;
550 } else if (dragged_bounds_start >= left_half.x() &&
551 dragged_bounds_start < left_half.right()) {
552 index = i;
553 break;
557 if (index == -1) {
558 if (dragged_bounds_end > right_tab_x)
559 index = attached_tabstrip_->GetTabCount();
560 else
561 index = 0;
564 TabGtk* tab = GetTabMatchingDraggedContents(
565 attached_tabstrip_, drag_data_->GetSourceWebContents());
566 if (tab == NULL) {
567 // If dragged tabs are not attached yet, we don't need to constrain the
568 // index.
569 return index;
572 int max_index =
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
589 // of the tabstrip.
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.
604 int max_x =
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())
608 x = max_x;
609 if (y > max_y && screen_point.y() <=
610 (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
611 y = max_y;
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)
621 return 0;
622 return index;
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_)
636 continue;
637 TabGtk* tab = GetTabMatchingDraggedContents(tabstrip,
638 drag_data_->get(i)->contents_);
639 if (tab)
640 dragged_tabs.push_back(tab);
642 return dragged_tabs;
645 void DraggedTabControllerGtk::SetDraggedTabsVisible(bool visible,
646 bool repaint) {
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);
652 if (repaint)
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) {
673 RevertDrag();
674 } else {
675 destroy_now = CompleteDrag();
678 } else if (drag_data_->size() > 0) {
679 RevertDrag();
682 ResetDelegates();
684 // If we're not destroyed now, we'll be destroyed asynchronously later.
685 if (destroy_now)
686 source_tabstrip_->DestroyDragController();
688 return destroy_now;
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_)
700 continue;
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
706 // somehow.
707 attached_tabstrip_ = source_tabstrip_;
708 for (size_t i = 0; i < drag_data_->size(); ++i) {
709 if (!drag_data_->get(i)->contents_)
710 continue;
711 attached_tabstrip_->model()->InsertWebContentsAt(
712 drag_data_->get(i)->source_model_index_,
713 drag_data_->get(i)->contents_,
714 drag_data_->GetAddTypesForDraggedTabAt(i));
716 } else {
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_)
721 continue;
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);
728 } else {
729 // TODO(beng): (Cleanup) seems like we should use Attach() for this
730 // somehow.
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
734 // source tabstrip.
735 for (size_t i = 0; i < drag_data_->size(); ++i) {
736 if (!drag_data_->get(i)->contents_)
737 continue;
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.
749 if (restore_window)
750 ShowWindow();
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;
764 } else {
765 // Compel the model to construct a new window for the detached
766 // WebContentses.
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()) {
801 gfx::Rect rect;
802 drag_data_->GetSourceWebContents()->GetView()->GetContainerBounds(&rect);
803 dragged_view_.reset(new DraggedViewGtk(drag_data_.get(), mouse_offset_,
804 rect.size()));
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(
817 attached_tabstrip_);
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();
871 if (!in_destructor_)
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();
878 if (!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(),
884 dock_windows_);
885 dock_windows_.erase(dragged_tab);
888 if (window)
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_) {
896 return false;
899 return true;