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