- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / tabs / dragged_tab_controller_gtk.cc
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/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"
28
29 using content::OpenURLParams;
30 using content::WebContents;
31
32 namespace {
33
34 // Delay, in ms, during dragging before we bring a window to front.
35 const int kBringToFrontDelay = 750;
36
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
40
41 // How far a drag must pull a tab out of the tabstrip in order to detach it.
42 const int kVerticalDetachMagnetism = 15;  // pixels
43
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;
52 }
53
54 }  // namespace
55
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());
67
68   std::vector<DraggedTabData> drag_data;
69   for (size_t i = 0; i < tabs.size(); i++)
70     drag_data.push_back(InitDraggedTabData(tabs[i]));
71
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));
75 }
76
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();
86 }
87
88 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) {
89   start_screen_point_ = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
90   mouse_offset_ = mouse_offset;
91 }
92
93 void DraggedTabControllerGtk::Drag() {
94   if (!drag_data_->GetSourceTabData()->tab_ ||
95       !drag_data_->GetSourceWebContents()) {
96     return;
97   }
98
99   bring_to_front_timer_.Stop();
100
101   EnsureDraggedView();
102
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());
107   }
108
109   if (!drag_data_->GetSourceTabData()->tab_->IsVisible()) {
110     // TODO(jhawkins): Save focus.
111     ContinueDragging();
112   }
113 }
114
115 bool DraggedTabControllerGtk::EndDrag(bool canceled) {
116   return EndDragImpl(canceled ? CANCELED : NORMAL);
117 }
118
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_;
125     }
126   }
127   return NULL;
128 }
129
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;
134   }
135   return false;
136 }
137
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;
143   }
144   return false;
145 }
146
147 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) {
148   return IsDraggingTab(tab) && attached_tabstrip_ == NULL;
149 }
150
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);
163
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;
169 }
170
171 ////////////////////////////////////////////////////////////////////////////////
172 // DraggedTabControllerGtk, content::WebContentsDelegate implementation:
173
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;
181
182     return drag_data_->GetSourceTabData()->original_delegate_->
183         OpenURLFromTab(source, forward_params);
184   }
185   return NULL;
186 }
187
188 void DraggedTabControllerGtk::NavigationStateChanged(const WebContents* source,
189                                                      unsigned changed_flags) {
190   if (dragged_view_.get())
191     dragged_view_->Update();
192 }
193
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);
201
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);
208   }
209 }
210
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();
216 }
217
218 content::JavaScriptDialogManager*
219 DraggedTabControllerGtk::GetJavaScriptDialogManager() {
220   return GetJavaScriptDialogManagerInstance();
221 }
222
223 ////////////////////////////////////////////////////////////////////////////////
224 // DraggedTabControllerGtk, content::NotificationObserver implementation:
225
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;
242     }
243   }
244   // If we get here it means we got notification for a tab we don't know about.
245   NOTREACHED();
246 }
247
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);
257 }
258
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;
268 }
269
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();
276
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();
286
287     if (target_tabstrip)
288       Attach(target_tabstrip, screen_point);
289   }
290
291   if (!target_tabstrip) {
292     bring_to_front_timer_.Start(FROM_HERE,
293         base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this,
294         &DraggedTabControllerGtk::BringWindowUnderMouseToFront);
295   }
296
297   if (attached_tabstrip_)
298     MoveAttached(screen_point);
299   else
300     MoveDetached(screen_point);
301 }
302
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);
312     }
313   }
314 }
315
316 void DraggedTabControllerGtk::MoveAttached(const gfx::Point& screen_point) {
317   DCHECK(attached_tabstrip_);
318
319   gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point);
320   TabStripModel* attached_model = attached_tabstrip_->model();
321
322   std::vector<TabGtk*> tabs(drag_data_->GetDraggedTabs());
323
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);
331
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());
343     }
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);
350   }
351
352   dragged_view_->MoveAttachedTo(dragged_view_point);
353   initial_move_ = false;
354 }
355
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);
361 }
362
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;
373
374   BrowserWindowGtk* browser =
375       BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window);
376   if (!browser)
377     return NULL;
378
379   TabStripGtk* other_tabstrip = browser->tabstrip();
380   if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
381     return NULL;
382
383   return GetTabStripIfItContains(other_tabstrip, screen_point);
384 }
385
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;
401     }
402   }
403
404   return NULL;
405 }
406
407 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip,
408                                      const gfx::Point& screen_point) {
409   attached_tabstrip_ = attached_tabstrip;
410   attached_tabstrip_->GenerateIdealBounds();
411
412   std::vector<TabGtk*> attached_dragged_tabs =
413       GetTabsMatchingDraggedContents(attached_tabstrip_);
414
415   // Update the tab first, so we can ask it for its bounds and determine
416   // where to insert the hidden tab.
417
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();
428   }
429
430   double unselected_width = 0, selected_width = 0;
431   attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count,
432                                           &unselected_width, &selected_width);
433
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());
439
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.
443
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;
449     }
450
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();
456
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();
460
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));
472     }
473     RestoreSelection(attached_tabstrip_->model());
474     attached_dragged_tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
475   }
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.
480 }
481
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);
492     }
493   }
494
495   // If we've removed the last tab from the tabstrip, hide the frame now.
496   if (attached_model->empty())
497     HideWindow();
498
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();
504   }
505
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);
509
510   attached_tabstrip_ = NULL;
511 }
512
513 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint(
514     TabStripGtk* tabstrip, const gfx::Point& screen_point) {
515   return screen_point - ui::GetWidgetScreenOffset(tabstrip->tabstrip_.get());
516 }
517
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());
525 }
526
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;
537
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();
545
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;
554     }
555   }
556
557   if (index == -1) {
558     if (dragged_bounds_end > right_tab_x)
559       index = attached_tabstrip_->GetTabCount();
560     else
561       index = 0;
562   }
563
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;
570   }
571
572   int max_index =
573       attached_tabstrip_-> GetTabCount() - static_cast<int>(drag_data_->size());
574   return std::max(0, std::min(max_index, index));
575 }
576
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();
582
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();
592
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();
598
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;
612     }
613   }
614   return gfx::Point(x, y);
615 }
616
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;
623 }
624
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);
629 }
630
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);
641   }
642   return dragged_tabs;
643 }
644
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();
654   }
655 }
656
657 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) {
658   bring_to_front_timer_.Stop();
659
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.
665
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();
676       }
677     }
678   } else if (drag_data_->size() > 0) {
679     RevertDrag();
680   }
681
682   ResetDelegates();
683
684   // If we're not destroyed now, we'll be destroyed asynchronously later.
685   if (destroy_now)
686     source_tabstrip_->DestroyDragController();
687
688   return destroy_now;
689 }
690
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);
704       }
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));
715       }
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);
726       }
727     }
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));
742     }
743   }
744   RestoreSelection(source_tabstrip_->model());
745
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();
751
752   SetDraggedTabsVisible(true, false);
753 }
754
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());
770
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);
777     };
778
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();
785   }
786
787   return destroy_immediately;
788 }
789
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();
795     }
796   }
797 }
798
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()));
805   }
806 }
807
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.
812   //
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));
823
824   return gfx::Rect(bounds.x(), bounds.y(),
825                    dragged_view_->GetTotalWidthInTabStrip(), bounds.height());
826 }
827
828 void DraggedTabControllerGtk::HideWindow() {
829   GtkWidget* tabstrip = source_tabstrip_->widget();
830   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
831   gtk_widget_hide(GTK_WIDGET(window));
832 }
833
834 void DraggedTabControllerGtk::ShowWindow() {
835   GtkWidget* tabstrip = source_tabstrip_->widget();
836   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
837   gtk_window_present(window);
838 }
839
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();
845 }
846
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_));
856       }
857       source_tabstrip_->DestroyDraggedTab(drag_data_->get(i)->tab_);
858       drag_data_->get(i)->tab_ = NULL;
859     }
860   }
861 }
862
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);
868
869   CleanUpHiddenFrame();
870
871   if (!in_destructor_)
872     source_tabstrip_->DestroyDragController();
873 }
874
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);
886   }
887
888   if (window)
889     gtk_window_present(GTK_WINDOW(window));
890 }
891
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;
897     }
898   }
899   return true;
900 }