Home | History | Annotate | Download | only in tabs
      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 
     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 
     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 
     63 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) {
     64   start_screen_point_ = GetCursorScreenPoint();
     65   mouse_offset_ = mouse_offset;
     66 }
     67 
     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 
     88 bool DraggedTabControllerGtk::EndDrag(bool canceled) {
     89   return EndDragImpl(canceled ? CANCELED : NORMAL);
     90 }
     91 
     92 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 
     99 bool DraggedTabControllerGtk::IsDragSourceTab(const TabGtk* tab) const {
    100   return source_tab_ == tab;
    101 }
    102 
    103 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 
    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 
    126 void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source,
    127                                                      unsigned changed_flags) {
    128   if (dragged_tab_.get())
    129     dragged_tab_->Update();
    130 }
    131 
    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 
    147 void DraggedTabControllerGtk::ActivateContents(TabContents* contents) {
    148   // Ignored.
    149 }
    150 
    151 void DraggedTabControllerGtk::DeactivateContents(TabContents* contents) {
    152   // Ignored.
    153 }
    154 
    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 
    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 
    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 
    174 bool DraggedTabControllerGtk::IsPopup(const TabContents* source) const {
    175   return false;
    176 }
    177 
    178 void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source,
    179                                               const GURL& url) {
    180   // Ignored.
    181 }
    182 
    183 ////////////////////////////////////////////////////////////////////////////////
    184 // DraggedTabControllerGtk, NotificationObserver implementation:
    185 
    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 
    194 void DraggedTabControllerGtk::InitWindowCreatePoint() {
    195   window_create_point_.SetPoint(mouse_offset_.x(), mouse_offset_.y());
    196 }
    197 
    198 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 
    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 
    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 
    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 
    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 
    318 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 
    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 
    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 
    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 
    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 
    453 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 
    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 
    549 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 
    557 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 
    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 
    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 
    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 
    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 
    696 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
    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 
    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 
    731 void DraggedTabControllerGtk::ShowWindow() {
    732   GtkWidget* tabstrip = source_tabstrip_->widget();
    733   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
    734   gtk_window_present(window);
    735 }
    736 
    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 
    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 
    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 
    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