• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/callback.h"
10 #include "base/i18n/rtl.h"
11 #include "chrome/browser/platform_util.h"
12 #include "chrome/browser/tabs/tab_strip_model.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
15 #include "chrome/browser/ui/gtk/gtk_util.h"
16 #include "chrome/browser/ui/gtk/tabs/dragged_tab_gtk.h"
17 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
19 #include "content/browser/tab_contents/tab_contents.h"
20 #include "content/common/notification_source.h"
21 
22 namespace {
23 
24 // Delay, in ms, during dragging before we bring a window to front.
25 const int kBringToFrontDelay = 750;
26 
27 // Used to determine how far a tab must obscure another tab in order to swap
28 // their indexes.
29 const int kHorizontalMoveThreshold = 16;  // pixels
30 
31 // How far a drag must pull a tab out of the tabstrip in order to detach it.
32 const int kVerticalDetachMagnetism = 15;  // pixels
33 
34 }  // namespace
35 
DraggedTabControllerGtk(TabGtk * source_tab,TabStripGtk * source_tabstrip)36 DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab,
37                                                  TabStripGtk* source_tabstrip)
38     : dragged_contents_(NULL),
39       original_delegate_(NULL),
40       source_tab_(source_tab),
41       source_tabstrip_(source_tabstrip),
42       source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)),
43       attached_tabstrip_(source_tabstrip),
44       in_destructor_(false),
45       last_move_screen_x_(0),
46       mini_(source_tabstrip->model()->IsMiniTab(source_model_index_)),
47       pinned_(source_tabstrip->model()->IsTabPinned(source_model_index_)) {
48   SetDraggedContents(
49       source_tabstrip_->model()->GetTabContentsAt(source_model_index_));
50 }
51 
~DraggedTabControllerGtk()52 DraggedTabControllerGtk::~DraggedTabControllerGtk() {
53   in_destructor_ = true;
54   CleanUpSourceTab();
55   // Need to delete the dragged tab here manually _before_ we reset the dragged
56   // contents to NULL, otherwise if the view is animating to its destination
57   // bounds, it won't be able to clean up properly since its cleanup routine
58   // uses GetIndexForDraggedContents, which will be invalid.
59   dragged_tab_.reset();
60   SetDraggedContents(NULL);
61 }
62 
CaptureDragInfo(const gfx::Point & mouse_offset)63 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) {
64   start_screen_point_ = GetCursorScreenPoint();
65   mouse_offset_ = mouse_offset;
66 }
67 
Drag()68 void DraggedTabControllerGtk::Drag() {
69   if (!source_tab_ || !dragged_contents_)
70     return;
71 
72   bring_to_front_timer_.Stop();
73 
74   EnsureDraggedTab();
75 
76   // Before we get to dragging anywhere, ensure that we consider ourselves
77   // attached to the source tabstrip.
78   if (source_tab_->IsVisible()) {
79     Attach(source_tabstrip_, gfx::Point());
80   }
81 
82   if (!source_tab_->IsVisible()) {
83     // TODO(jhawkins): Save focus.
84     ContinueDragging();
85   }
86 }
87 
EndDrag(bool canceled)88 bool DraggedTabControllerGtk::EndDrag(bool canceled) {
89   return EndDragImpl(canceled ? CANCELED : NORMAL);
90 }
91 
GetDragSourceTabForContents(TabContents * contents) const92 TabGtk* DraggedTabControllerGtk::GetDragSourceTabForContents(
93     TabContents* contents) const {
94   if (attached_tabstrip_ == source_tabstrip_)
95     return contents == dragged_contents_->tab_contents() ? source_tab_ : NULL;
96   return NULL;
97 }
98 
IsDragSourceTab(const TabGtk * tab) const99 bool DraggedTabControllerGtk::IsDragSourceTab(const TabGtk* tab) const {
100   return source_tab_ == tab;
101 }
102 
IsTabDetached(const TabGtk * tab) const103 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) const {
104   if (!IsDragSourceTab(tab))
105     return false;
106   return (attached_tabstrip_ == NULL);
107 }
108 
109 ////////////////////////////////////////////////////////////////////////////////
110 // DraggedTabControllerGtk, TabContentsDelegate implementation:
111 
OpenURLFromTab(TabContents * source,const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)112 void DraggedTabControllerGtk::OpenURLFromTab(TabContents* source,
113                                              const GURL& url,
114                                              const GURL& referrer,
115                                              WindowOpenDisposition disposition,
116                                              PageTransition::Type transition) {
117   if (original_delegate_) {
118     if (disposition == CURRENT_TAB)
119       disposition = NEW_WINDOW;
120 
121     original_delegate_->OpenURLFromTab(source, url, referrer,
122                                        disposition, transition);
123   }
124 }
125 
NavigationStateChanged(const TabContents * source,unsigned changed_flags)126 void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source,
127                                                      unsigned changed_flags) {
128   if (dragged_tab_.get())
129     dragged_tab_->Update();
130 }
131 
AddNewContents(TabContents * source,TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)132 void DraggedTabControllerGtk::AddNewContents(TabContents* source,
133                                              TabContents* new_contents,
134                                              WindowOpenDisposition disposition,
135                                              const gfx::Rect& initial_pos,
136                                              bool user_gesture) {
137   DCHECK(disposition != CURRENT_TAB);
138 
139   // Theoretically could be called while dragging if the page tries to
140   // spawn a window. Route this message back to the browser in most cases.
141   if (original_delegate_) {
142     original_delegate_->AddNewContents(source, new_contents, disposition,
143                                        initial_pos, user_gesture);
144   }
145 }
146 
ActivateContents(TabContents * contents)147 void DraggedTabControllerGtk::ActivateContents(TabContents* contents) {
148   // Ignored.
149 }
150 
DeactivateContents(TabContents * contents)151 void DraggedTabControllerGtk::DeactivateContents(TabContents* contents) {
152   // Ignored.
153 }
154 
LoadingStateChanged(TabContents * source)155 void DraggedTabControllerGtk::LoadingStateChanged(TabContents* source) {
156   // TODO(jhawkins): It would be nice to respond to this message by changing the
157   // screen shot in the dragged tab.
158   if (dragged_tab_.get())
159     dragged_tab_->Update();
160 }
161 
CloseContents(TabContents * source)162 void DraggedTabControllerGtk::CloseContents(TabContents* source) {
163   // Theoretically could be called by a window. Should be ignored
164   // because window.close() is ignored (usually, even though this
165   // method gets called.)
166 }
167 
MoveContents(TabContents * source,const gfx::Rect & pos)168 void DraggedTabControllerGtk::MoveContents(TabContents* source,
169                                         const gfx::Rect& pos) {
170   // Theoretically could be called by a web page trying to move its
171   // own window. Should be ignored since we're moving the window...
172 }
173 
IsPopup(const TabContents * source) const174 bool DraggedTabControllerGtk::IsPopup(const TabContents* source) const {
175   return false;
176 }
177 
UpdateTargetURL(TabContents * source,const GURL & url)178 void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source,
179                                               const GURL& url) {
180   // Ignored.
181 }
182 
183 ////////////////////////////////////////////////////////////////////////////////
184 // DraggedTabControllerGtk, NotificationObserver implementation:
185 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)186 void DraggedTabControllerGtk::Observe(NotificationType type,
187                                    const NotificationSource& source,
188                                    const NotificationDetails& details) {
189   DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
190   DCHECK(Source<TabContentsWrapper>(source).ptr() == dragged_contents_);
191   EndDragImpl(TAB_DESTROYED);
192 }
193 
InitWindowCreatePoint()194 void DraggedTabControllerGtk::InitWindowCreatePoint() {
195   window_create_point_.SetPoint(mouse_offset_.x(), mouse_offset_.y());
196 }
197 
GetWindowCreatePoint() const198 gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const {
199   gfx::Point cursor_point = GetCursorScreenPoint();
200   return gfx::Point(cursor_point.x() - window_create_point_.x(),
201                     cursor_point.y() - window_create_point_.y());
202 }
203 
SetDraggedContents(TabContentsWrapper * new_contents)204 void DraggedTabControllerGtk::SetDraggedContents(
205     TabContentsWrapper* new_contents) {
206   if (dragged_contents_) {
207     registrar_.Remove(this,
208                       NotificationType::TAB_CONTENTS_DESTROYED,
209                       Source<TabContentsWrapper>(dragged_contents_));
210     if (original_delegate_)
211       dragged_contents_->tab_contents()->set_delegate(original_delegate_);
212   }
213   original_delegate_ = NULL;
214   dragged_contents_ = new_contents;
215   if (dragged_contents_) {
216     registrar_.Add(this,
217                    NotificationType::TAB_CONTENTS_DESTROYED,
218                    Source<TabContentsWrapper>(dragged_contents_));
219 
220     // We need to be the delegate so we receive messages about stuff,
221     // otherwise our dragged_contents() may be replaced and subsequently
222     // collected/destroyed while the drag is in process, leading to
223     // nasty crashes.
224     original_delegate_ = dragged_contents_->tab_contents()->delegate();
225     dragged_contents_->tab_contents()->set_delegate(this);
226   }
227 }
228 
ContinueDragging()229 void DraggedTabControllerGtk::ContinueDragging() {
230   // TODO(jhawkins): We don't handle the situation where the last tab is dragged
231   // out of a window, so we'll just go with the way Windows handles dragging for
232   // now.
233   gfx::Point screen_point = GetCursorScreenPoint();
234 
235   // Determine whether or not we have dragged over a compatible TabStrip in
236   // another browser window. If we have, we should attach to it and start
237   // dragging within it.
238 #if defined(OS_CHROMEOS)
239   // We don't allow detaching on chrome os.
240   TabStripGtk* target_tabstrip = source_tabstrip_;
241 #else
242   TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point);
243 #endif
244   if (target_tabstrip != attached_tabstrip_) {
245     // Make sure we're fully detached from whatever TabStrip we're attached to
246     // (if any).
247     if (attached_tabstrip_)
248       Detach();
249 
250     if (target_tabstrip)
251       Attach(target_tabstrip, screen_point);
252   }
253 
254   if (!target_tabstrip) {
255     bring_to_front_timer_.Start(
256         base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this,
257         &DraggedTabControllerGtk::BringWindowUnderMouseToFront);
258   }
259 
260   MoveTab(screen_point);
261 }
262 
MoveTab(const gfx::Point & screen_point)263 void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) {
264   gfx::Point dragged_tab_point = GetDraggedTabPoint(screen_point);
265 
266   if (attached_tabstrip_) {
267     TabStripModel* attached_model = attached_tabstrip_->model();
268     int from_index = attached_model->GetIndexOfTabContents(dragged_contents_);
269 
270     // Determine the horizontal move threshold. This is dependent on the width
271     // of tabs. The smaller the tabs compared to the standard size, the smaller
272     // the threshold.
273     double unselected, selected;
274     attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
275     double ratio = unselected / TabGtk::GetStandardSize().width();
276     int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
277 
278     // Update the model, moving the TabContents from one index to another. Do
279     // this only if we have moved a minimum distance since the last reorder (to
280     // prevent jitter).
281     if (abs(screen_point.x() - last_move_screen_x_) > threshold) {
282       gfx::Rect bounds = GetDraggedTabTabStripBounds(dragged_tab_point);
283       int to_index = GetInsertionIndexForDraggedBounds(bounds, true);
284       to_index = NormalizeIndexToAttachedTabStrip(to_index);
285       if (from_index != to_index) {
286         last_move_screen_x_ = screen_point.x();
287         attached_model->MoveTabContentsAt(from_index, to_index, true);
288       }
289     }
290   }
291 
292   // Move the dragged tab. There are no changes to the model if we're detached.
293   dragged_tab_->MoveTo(dragged_tab_point);
294 }
295 
GetTabStripForPoint(const gfx::Point & screen_point)296 TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint(
297     const gfx::Point& screen_point) {
298   GtkWidget* dragged_window = dragged_tab_->widget();
299   dock_windows_.insert(dragged_window);
300   gfx::NativeWindow local_window =
301       DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_);
302   dock_windows_.erase(dragged_window);
303   if (!local_window)
304     return NULL;
305 
306   BrowserWindowGtk* browser =
307       BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window);
308   if (!browser)
309     return NULL;
310 
311   TabStripGtk* other_tabstrip = browser->tabstrip();
312   if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
313     return NULL;
314 
315   return GetTabStripIfItContains(other_tabstrip, screen_point);
316 }
317 
GetTabStripIfItContains(TabStripGtk * tabstrip,const gfx::Point & screen_point) const318 TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains(
319     TabStripGtk* tabstrip, const gfx::Point& screen_point) const {
320   // Make sure the specified screen point is actually within the bounds of the
321   // specified tabstrip...
322   gfx::Rect tabstrip_bounds =
323       gtk_util::GetWidgetScreenBounds(tabstrip->tabstrip_.get());
324   if (screen_point.x() < tabstrip_bounds.right() &&
325       screen_point.x() >= tabstrip_bounds.x()) {
326     // TODO(beng): make this be relative to the start position of the mouse for
327     // the source TabStrip.
328     int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
329     int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
330     if (screen_point.y() >= lower_threshold &&
331         screen_point.y() <= upper_threshold) {
332       return tabstrip;
333     }
334   }
335 
336   return NULL;
337 }
338 
Attach(TabStripGtk * attached_tabstrip,const gfx::Point & screen_point)339 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip,
340                                      const gfx::Point& screen_point) {
341   attached_tabstrip_ = attached_tabstrip;
342   InitWindowCreatePoint();
343   attached_tabstrip_->GenerateIdealBounds();
344 
345   TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
346 
347   // Update the tab first, so we can ask it for its bounds and determine
348   // where to insert the hidden tab.
349 
350   // If this is the first time Attach is called for this drag, we're attaching
351   // to the source tabstrip, and we should assume the tab count already
352   // includes this tab since we haven't been detached yet. If we don't do this,
353   // the dragged representation will be a different size to others in the
354   // tabstrip.
355   int tab_count = attached_tabstrip_->GetTabCount();
356   int mini_tab_count = attached_tabstrip_->GetMiniTabCount();
357   if (!tab)
358     ++tab_count;
359   double unselected_width = 0, selected_width = 0;
360   attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count,
361                                           &unselected_width, &selected_width);
362   int dragged_tab_width =
363       mini_ ? TabGtk::GetMiniWidth() : static_cast<int>(selected_width);
364   dragged_tab_->Attach(dragged_tab_width);
365 
366   if (!tab) {
367     // There is no tab in |attached_tabstrip| that corresponds to the dragged
368     // TabContents. We must now create one.
369 
370     // Remove ourselves as the delegate now that the dragged TabContents is
371     // being inserted back into a Browser.
372     dragged_contents_->tab_contents()->set_delegate(NULL);
373     original_delegate_ = NULL;
374 
375     // Return the TabContents' to normalcy.
376     dragged_contents_->tab_contents()->set_capturing_contents(false);
377 
378     // We need to ask the tabstrip we're attached to ensure that the ideal
379     // bounds for all its tabs are correctly generated, because the calculation
380     // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
381     // appropriate insertion index.
382     attached_tabstrip_->GenerateIdealBounds();
383 
384     // Inserting counts as a move. We don't want the tabs to jitter when the
385     // user moves the tab immediately after attaching it.
386     last_move_screen_x_ = screen_point.x();
387 
388     // Figure out where to insert the tab based on the bounds of the dragged
389     // representation and the ideal bounds of the other tabs already in the
390     // strip. ("ideal bounds" are stable even if the tabs' actual bounds are
391     // changing due to animation).
392     gfx::Rect bounds = GetDraggedTabTabStripBounds(screen_point);
393     int index = GetInsertionIndexForDraggedBounds(bounds, false);
394     attached_tabstrip_->model()->InsertTabContentsAt(
395         index, dragged_contents_,
396         TabStripModel::ADD_ACTIVE |
397             (pinned_ ? TabStripModel::ADD_PINNED : 0));
398 
399     tab = GetTabMatchingDraggedContents(attached_tabstrip_);
400   }
401   DCHECK(tab);  // We should now have a tab.
402   tab->SetVisible(false);
403   tab->set_dragging(true);
404 
405   // TODO(jhawkins): Move the corresponding window to the front.
406 }
407 
Detach()408 void DraggedTabControllerGtk::Detach() {
409   // Update the Model.
410   TabStripModel* attached_model = attached_tabstrip_->model();
411   int index = attached_model->GetIndexOfTabContents(dragged_contents_);
412   if (index >= 0 && index < attached_model->count()) {
413     // Sometimes, DetachTabContentsAt has consequences that result in
414     // attached_tabstrip_ being set to NULL, so we need to save it first.
415     TabStripGtk* attached_tabstrip = attached_tabstrip_;
416     attached_model->DetachTabContentsAt(index);
417     attached_tabstrip->SchedulePaint();
418   }
419 
420   // If we've removed the last tab from the tabstrip, hide the frame now.
421   if (attached_model->empty())
422     HideWindow();
423 
424   // Update the dragged tab. This NULL check is necessary apparently in some
425   // conditions during automation where the view_ is destroyed inside a
426   // function call preceding this point but after it is created.
427   if (dragged_tab_.get()) {
428     dragged_tab_->Detach();
429   }
430 
431   // Detaching resets the delegate, but we still want to be the delegate.
432   dragged_contents_->tab_contents()->set_delegate(this);
433 
434   attached_tabstrip_ = NULL;
435 }
436 
ConvertScreenPointToTabStripPoint(TabStripGtk * tabstrip,const gfx::Point & screen_point)437 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint(
438     TabStripGtk* tabstrip, const gfx::Point& screen_point) {
439   gfx::Point tabstrip_screen_point =
440       gtk_util::GetWidgetScreenPosition(tabstrip->tabstrip_.get());
441   return screen_point.Subtract(tabstrip_screen_point);
442 }
443 
GetDraggedTabTabStripBounds(const gfx::Point & screen_point)444 gfx::Rect DraggedTabControllerGtk::GetDraggedTabTabStripBounds(
445     const gfx::Point& screen_point) {
446   gfx::Point client_point =
447       ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
448   gfx::Size tab_size = dragged_tab_->attached_tab_size();
449   return gfx::Rect(client_point.x(), client_point.y(),
450                    tab_size.width(), tab_size.height());
451 }
452 
GetInsertionIndexForDraggedBounds(const gfx::Rect & dragged_bounds,bool is_tab_attached) const453 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds(
454     const gfx::Rect& dragged_bounds,
455     bool is_tab_attached) const {
456   int right_tab_x = 0;
457   int dragged_bounds_x = base::i18n::IsRTL() ? dragged_bounds.right() :
458                                                dragged_bounds.x();
459   dragged_bounds_x =
460       gtk_util::MirroredXCoordinate(attached_tabstrip_->widget(),
461                                     dragged_bounds_x);
462 
463   // Divides each tab into two halves to see if the dragged tab has crossed
464   // the halfway boundary necessary to move past the next tab.
465   int index = -1;
466   for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) {
467     gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i);
468 
469     gfx::Rect left_half = ideal_bounds;
470     left_half.set_width(left_half.width() / 2);
471 
472     gfx::Rect right_half = ideal_bounds;
473     right_half.set_width(ideal_bounds.width() - left_half.width());
474     right_half.set_x(left_half.right());
475 
476     right_tab_x = right_half.right();
477 
478     if (dragged_bounds_x >= right_half.x() &&
479         dragged_bounds_x < right_half.right()) {
480       index = i + 1;
481       break;
482     } else if (dragged_bounds_x >= left_half.x() &&
483                dragged_bounds_x < left_half.right()) {
484       index = i;
485       break;
486     }
487   }
488 
489   if (index == -1) {
490     bool at_the_end = base::i18n::IsRTL() ?
491         dragged_bounds.x() < right_tab_x :
492         dragged_bounds.right() > right_tab_x;
493     index = at_the_end ? attached_tabstrip_->model()->count() : 0;
494   }
495 
496   index = attached_tabstrip_->model()->ConstrainInsertionIndex(index, mini_);
497   if (is_tab_attached && mini_ &&
498       index == attached_tabstrip_->model()->IndexOfFirstNonMiniTab()) {
499     index--;
500   }
501 
502   return index;
503 }
504 
GetDraggedTabPoint(const gfx::Point & screen_point)505 gfx::Point DraggedTabControllerGtk::GetDraggedTabPoint(
506     const gfx::Point& screen_point) {
507   int x = screen_point.x() - mouse_offset_.x();
508   int y = screen_point.y() - mouse_offset_.y();
509 
510   // If we're not attached, we just use x and y from above.
511   if (attached_tabstrip_) {
512     gfx::Rect tabstrip_bounds =
513         gtk_util::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get());
514     // Snap the dragged tab to the tabstrip if we are attached, detaching
515     // only when the mouse position (screen_point) exceeds the screen bounds
516     // of the tabstrip.
517     if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x())
518       x = tabstrip_bounds.x();
519 
520     gfx::Size tab_size = dragged_tab_->attached_tab_size();
521     int vertical_drag_magnetism = tab_size.height() * 2;
522     int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism;
523     if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point)
524       y = tabstrip_bounds.y();
525 
526     // Make sure the tab can't be dragged off the right side of the tabstrip
527     // unless the mouse pointer passes outside the bounds of the strip by
528     // clamping the position of the dragged window to the tabstrip width less
529     // the width of one tab until the mouse pointer (screen_point) exceeds the
530     // screen bounds of the tabstrip.
531     int max_x = tabstrip_bounds.right() - tab_size.width();
532     int max_y = tabstrip_bounds.bottom() - tab_size.height();
533     if (x > max_x && screen_point.x() <= tabstrip_bounds.right())
534       x = max_x;
535     if (y > max_y && screen_point.y() <=
536         (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
537       y = max_y;
538     }
539 #if defined(OS_CHROMEOS)
540     // We don't allow detaching on chromeos. This restricts dragging to the
541     // source window.
542     x = std::min(std::max(x, tabstrip_bounds.x()), max_x);
543     y = tabstrip_bounds.y();
544 #endif
545   }
546   return gfx::Point(x, y);
547 }
548 
NormalizeIndexToAttachedTabStrip(int index) const549 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const {
550   if (index >= attached_tabstrip_->model_->count())
551     return attached_tabstrip_->model_->count() - 1;
552   if (index == TabStripModel::kNoTab)
553     return 0;
554   return index;
555 }
556 
GetTabMatchingDraggedContents(TabStripGtk * tabstrip) const557 TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents(
558     TabStripGtk* tabstrip) const {
559   int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_);
560   return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
561 }
562 
EndDragImpl(EndDragType type)563 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) {
564   bring_to_front_timer_.Stop();
565 
566   // WARNING: this may be invoked multiple times. In particular, if deletion
567   // occurs after a delay (as it does when the tab is released in the original
568   // tab strip) and the navigation controller/tab contents is deleted before
569   // the animation finishes, this is invoked twice. The second time through
570   // type == TAB_DESTROYED.
571 
572   bool destroy_now = true;
573   if (type == TAB_DESTROYED) {
574     // If we get here it means the NavigationController is going down. Don't
575     // attempt to do any cleanup other than resetting the delegate (if we're
576     // still the delegate).
577     if (dragged_contents_ &&
578         dragged_contents_->tab_contents()->delegate() == this)
579       dragged_contents_->tab_contents()->set_delegate(NULL);
580     dragged_contents_ = NULL;
581   } else {
582     // If we never received a drag-motion event, the drag will never have
583     // started in the sense that |dragged_tab_| will be NULL. We don't need to
584     // revert or complete the drag in that case.
585     if (dragged_tab_.get()) {
586       if (type == CANCELED) {
587         RevertDrag();
588       } else {
589         destroy_now = CompleteDrag();
590       }
591     }
592 
593     if (dragged_contents_ &&
594         dragged_contents_->tab_contents()->delegate() == this)
595       dragged_contents_->tab_contents()->set_delegate(original_delegate_);
596   }
597 
598   // The delegate of the dragged contents should have been reset. Unset the
599   // original delegate so that we don't attempt to reset the delegate when
600   // deleted.
601   DCHECK(!dragged_contents_ ||
602          dragged_contents_->tab_contents()->delegate() != this);
603   original_delegate_ = NULL;
604 
605   // If we're not destroyed now, we'll be destroyed asynchronously later.
606   if (destroy_now)
607     source_tabstrip_->DestroyDragController();
608 
609   return destroy_now;
610 }
611 
RevertDrag()612 void DraggedTabControllerGtk::RevertDrag() {
613   // We save this here because code below will modify |attached_tabstrip_|.
614   bool restore_window = attached_tabstrip_ != source_tabstrip_;
615   if (attached_tabstrip_) {
616     int index = attached_tabstrip_->model()->GetIndexOfTabContents(
617         dragged_contents_);
618     if (attached_tabstrip_ != source_tabstrip_) {
619       // The tab was inserted into another tabstrip. We need to put it back
620       // into the original one.
621       attached_tabstrip_->model()->DetachTabContentsAt(index);
622       // TODO(beng): (Cleanup) seems like we should use Attach() for this
623       //             somehow.
624       attached_tabstrip_ = source_tabstrip_;
625       source_tabstrip_->model()->InsertTabContentsAt(
626           source_model_index_, dragged_contents_,
627           TabStripModel::ADD_ACTIVE |
628               (pinned_ ? TabStripModel::ADD_PINNED : 0));
629     } else {
630       // The tab was moved within the tabstrip where the drag was initiated.
631       // Move it back to the starting location.
632       source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_,
633           true);
634     }
635   } else {
636     // TODO(beng): (Cleanup) seems like we should use Attach() for this
637     //             somehow.
638     attached_tabstrip_ = source_tabstrip_;
639     // The tab was detached from the tabstrip where the drag began, and has not
640     // been attached to any other tabstrip. We need to put it back into the
641     // source tabstrip.
642     source_tabstrip_->model()->InsertTabContentsAt(
643         source_model_index_, dragged_contents_,
644         TabStripModel::ADD_ACTIVE |
645             (pinned_ ? TabStripModel::ADD_PINNED : 0));
646   }
647 
648   // If we're not attached to any tab strip, or attached to some other tab
649   // strip, we need to restore the bounds of the original tab strip's frame, in
650   // case it has been hidden.
651   if (restore_window)
652     ShowWindow();
653 
654   source_tab_->SetVisible(true);
655   source_tab_->set_dragging(false);
656 }
657 
CompleteDrag()658 bool DraggedTabControllerGtk::CompleteDrag() {
659   bool destroy_immediately = true;
660   if (attached_tabstrip_) {
661     // We don't need to do anything other than make the tab visible again,
662     // since the dragged tab is going away.
663     TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
664     gfx::Rect rect = GetTabScreenBounds(tab);
665     dragged_tab_->AnimateToBounds(GetTabScreenBounds(tab),
666         NewCallback(this, &DraggedTabControllerGtk::OnAnimateToBoundsComplete));
667     destroy_immediately = false;
668   } else {
669     // Compel the model to construct a new window for the detached TabContents.
670     BrowserWindowGtk* window = source_tabstrip_->window();
671     gfx::Rect window_bounds = window->GetRestoredBounds();
672     window_bounds.set_origin(GetWindowCreatePoint());
673     Browser* new_browser =
674         source_tabstrip_->model()->delegate()->CreateNewStripWithContents(
675         dragged_contents_, window_bounds, dock_info_, window->IsMaximized());
676     TabStripModel* new_model = new_browser->tabstrip_model();
677     new_model->SetTabPinned(new_model->GetIndexOfTabContents(dragged_contents_),
678                             pinned_);
679     new_browser->window()->Show();
680     CleanUpHiddenFrame();
681   }
682 
683   return destroy_immediately;
684 }
685 
EnsureDraggedTab()686 void DraggedTabControllerGtk::EnsureDraggedTab() {
687   if (!dragged_tab_.get()) {
688     gfx::Rect rect;
689     dragged_contents_->tab_contents()->GetContainerBounds(&rect);
690 
691     dragged_tab_.reset(new DraggedTabGtk(dragged_contents_->tab_contents(),
692                                          mouse_offset_, rect.size(), mini_));
693   }
694 }
695 
GetCursorScreenPoint() const696 gfx::Point DraggedTabControllerGtk::GetCursorScreenPoint() const {
697   // Get default display and screen.
698   GdkDisplay* display = gdk_display_get_default();
699 
700   // Get cursor position.
701   int x, y;
702   gdk_display_get_pointer(display, NULL, &x, &y, NULL);
703 
704   return gfx::Point(x, y);
705 }
706 
707 // static
GetTabScreenBounds(TabGtk * tab)708 gfx::Rect DraggedTabControllerGtk::GetTabScreenBounds(TabGtk* tab) {
709   // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't
710   // update its allocation until after the widget is shown, so we have to use
711   // the tab bounds we keep track of.
712   //
713   // We use the requested bounds instead of the allocation because the
714   // allocation is relative to the first windowed widget ancestor of the tab.
715   // Because of this, we can't use the tabs allocation to get the screen bounds.
716   gfx::Rect bounds = tab->GetRequisition();
717   GtkWidget* widget = tab->widget();
718   GtkWidget* parent = gtk_widget_get_parent(widget);
719   gfx::Point point = gtk_util::GetWidgetScreenPosition(parent);
720   bounds.Offset(point);
721 
722   return gfx::Rect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
723 }
724 
HideWindow()725 void DraggedTabControllerGtk::HideWindow() {
726   GtkWidget* tabstrip = source_tabstrip_->widget();
727   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
728   gtk_widget_hide(GTK_WIDGET(window));
729 }
730 
ShowWindow()731 void DraggedTabControllerGtk::ShowWindow() {
732   GtkWidget* tabstrip = source_tabstrip_->widget();
733   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
734   gtk_window_present(window);
735 }
736 
CleanUpHiddenFrame()737 void DraggedTabControllerGtk::CleanUpHiddenFrame() {
738   // If the model we started dragging from is now empty, we must ask the
739   // delegate to close the frame.
740   if (source_tabstrip_->model()->empty())
741     source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
742 }
743 
CleanUpSourceTab()744 void DraggedTabControllerGtk::CleanUpSourceTab() {
745   // If we were attached to the source tabstrip, source tab will be in use
746   // as the tab. If we were detached or attached to another tabstrip, we can
747   // safely remove this item and delete it now.
748   if (attached_tabstrip_ != source_tabstrip_) {
749     source_tabstrip_->DestroyDraggedSourceTab(source_tab_);
750     source_tab_ = NULL;
751   }
752 }
753 
OnAnimateToBoundsComplete()754 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() {
755   // Sometimes, for some reason, in automation we can be called back on a
756   // detach even though we aren't attached to a tabstrip. Guard against that.
757   if (attached_tabstrip_) {
758     TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
759     if (tab) {
760       tab->SetVisible(true);
761       tab->set_dragging(false);
762       // Paint the tab now, otherwise there may be slight flicker between the
763       // time the dragged tab window is destroyed and we paint.
764       tab->SchedulePaint();
765     }
766   }
767 
768   CleanUpHiddenFrame();
769 
770   if (!in_destructor_)
771     source_tabstrip_->DestroyDragController();
772 }
773 
BringWindowUnderMouseToFront()774 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() {
775   // If we're going to dock to another window, bring it to the front.
776   gfx::NativeWindow window = dock_info_.window();
777   if (!window) {
778     gfx::NativeView dragged_tab = dragged_tab_->widget();
779     dock_windows_.insert(dragged_tab);
780     window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(),
781                                                     dock_windows_);
782     dock_windows_.erase(dragged_tab);
783   }
784 
785   if (window)
786     gtk_window_present(GTK_WINDOW(window));
787 }
788