Home | History | Annotate | Download | only in tabs
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/bind_helpers.h"
     11 #include "base/i18n/rtl.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/platform_util.h"
     14 #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     17 #include "chrome/browser/ui/gtk/gtk_util.h"
     18 #include "chrome/browser/ui/gtk/tabs/dragged_view_gtk.h"
     19 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
     20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     21 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
     22 #include "content/public/browser/notification_source.h"
     23 #include "content/public/browser/web_contents.h"
     24 #include "content/public/browser/web_contents_view.h"
     25 #include "ui/base/gtk/gtk_screen_util.h"
     26 #include "ui/gfx/screen.h"
     27 
     28 using content::OpenURLParams;
     29 using content::WebContents;
     30 
     31 namespace {
     32 
     33 // Delay, in ms, during dragging before we bring a window to front.
     34 const int kBringToFrontDelay = 750;
     35 
     36 // Used to determine how far a tab must obscure another tab in order to swap
     37 // their indexes.
     38 const int kHorizontalMoveThreshold = 16;  // pixels
     39 
     40 // How far a drag must pull a tab out of the tabstrip in order to detach it.
     41 const int kVerticalDetachMagnetism = 15;  // pixels
     42 
     43 // Returns the bounds that will be used for insertion index calculation.
     44 // |bounds| is the actual tab bounds, but subtracting the overlapping areas from
     45 // both sides makes the calculations much simpler.
     46 gfx::Rect GetEffectiveBounds(const gfx::Rect& bounds) {
     47   gfx::Rect effective_bounds(bounds);
     48   effective_bounds.set_width(effective_bounds.width() - 2 * 16);
     49   effective_bounds.set_x(effective_bounds.x() + 16);
     50   return effective_bounds;
     51 }
     52 
     53 }  // namespace
     54 
     55 DraggedTabControllerGtk::DraggedTabControllerGtk(
     56     TabStripGtk* source_tabstrip,
     57     TabGtk* source_tab,
     58     const std::vector<TabGtk*>& tabs)
     59     : source_tabstrip_(source_tabstrip),
     60       attached_tabstrip_(source_tabstrip),
     61       in_destructor_(false),
     62       last_move_screen_x_(0),
     63       initial_move_(true) {
     64   DCHECK(!tabs.empty());
     65   DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
     66 
     67   std::vector<DraggedTabData> drag_data;
     68   for (size_t i = 0; i < tabs.size(); i++)
     69     drag_data.push_back(InitDraggedTabData(tabs[i]));
     70 
     71   int source_tab_index =
     72       std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
     73   drag_data_.reset(new DragData(drag_data, source_tab_index));
     74 }
     75 
     76 DraggedTabControllerGtk::~DraggedTabControllerGtk() {
     77   in_destructor_ = true;
     78   // Need to delete the dragged tab here manually _before_ we reset the dragged
     79   // contents to NULL, otherwise if the view is animating to its destination
     80   // bounds, it won't be able to clean up properly since its cleanup routine
     81   // uses GetIndexForDraggedContents, which will be invalid.
     82   CleanUpDraggedTabs();
     83   dragged_view_.reset();
     84   drag_data_.reset();
     85 }
     86 
     87 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) {
     88   start_screen_point_ = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
     89   mouse_offset_ = mouse_offset;
     90 }
     91 
     92 void DraggedTabControllerGtk::Drag() {
     93   if (!drag_data_->GetSourceTabData()->tab_ ||
     94       !drag_data_->GetSourceWebContents()) {
     95     return;
     96   }
     97 
     98   bring_to_front_timer_.Stop();
     99 
    100   EnsureDraggedView();
    101 
    102   // Before we get to dragging anywhere, ensure that we consider ourselves
    103   // attached to the source tabstrip.
    104   if (drag_data_->GetSourceTabData()->tab_->IsVisible()) {
    105     Attach(source_tabstrip_, gfx::Point());
    106   }
    107 
    108   if (!drag_data_->GetSourceTabData()->tab_->IsVisible()) {
    109     // TODO(jhawkins): Save focus.
    110     ContinueDragging();
    111   }
    112 }
    113 
    114 bool DraggedTabControllerGtk::EndDrag(bool canceled) {
    115   return EndDragImpl(canceled ? CANCELED : NORMAL);
    116 }
    117 
    118 TabGtk* DraggedTabControllerGtk::GetDraggedTabForContents(
    119     WebContents* contents) {
    120   if (attached_tabstrip_ == source_tabstrip_) {
    121     for (size_t i = 0; i < drag_data_->size(); i++) {
    122       if (contents == drag_data_->get(i)->contents_)
    123         return drag_data_->get(i)->tab_;
    124     }
    125   }
    126   return NULL;
    127 }
    128 
    129 bool DraggedTabControllerGtk::IsDraggingTab(const TabGtk* tab) {
    130   for (size_t i = 0; i < drag_data_->size(); i++) {
    131     if (tab == drag_data_->get(i)->tab_)
    132       return true;
    133   }
    134   return false;
    135 }
    136 
    137 bool DraggedTabControllerGtk::IsDraggingWebContents(
    138     const WebContents* web_contents) {
    139   for (size_t i = 0; i < drag_data_->size(); i++) {
    140     if (web_contents == drag_data_->get(i)->contents_)
    141       return true;
    142   }
    143   return false;
    144 }
    145 
    146 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) {
    147   return IsDraggingTab(tab) && attached_tabstrip_ == NULL;
    148 }
    149 
    150 DraggedTabData DraggedTabControllerGtk::InitDraggedTabData(TabGtk* tab) {
    151   int source_model_index = source_tabstrip_->GetIndexOfTab(tab);
    152   WebContents* contents =
    153       source_tabstrip_->model()->GetWebContentsAt(source_model_index);
    154   bool pinned = source_tabstrip_->IsTabPinned(tab);
    155   bool mini = source_tabstrip_->model()->IsMiniTab(source_model_index);
    156   // We need to be the delegate so we receive messages about stuff,
    157   // otherwise our dragged_contents() may be replaced and subsequently
    158   // collected/destroyed while the drag is in process, leading to
    159   // nasty crashes.
    160   content::WebContentsDelegate* original_delegate = contents->GetDelegate();
    161   contents->SetDelegate(this);
    162 
    163   DraggedTabData dragged_tab_data(tab, contents, original_delegate,
    164                                   source_model_index, pinned, mini);
    165   registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    166                  content::Source<WebContents>(contents));
    167   return dragged_tab_data;
    168 }
    169 
    170 ////////////////////////////////////////////////////////////////////////////////
    171 // DraggedTabControllerGtk, content::WebContentsDelegate implementation:
    172 
    173 WebContents* DraggedTabControllerGtk::OpenURLFromTab(
    174     WebContents* source,
    175     const OpenURLParams& params) {
    176   if (drag_data_->GetSourceTabData()->original_delegate_) {
    177     OpenURLParams forward_params = params;
    178     if (params.disposition == CURRENT_TAB)
    179       forward_params.disposition = NEW_WINDOW;
    180 
    181     return drag_data_->GetSourceTabData()->original_delegate_->
    182         OpenURLFromTab(source, forward_params);
    183   }
    184   return NULL;
    185 }
    186 
    187 void DraggedTabControllerGtk::NavigationStateChanged(const WebContents* source,
    188                                                      unsigned changed_flags) {
    189   if (dragged_view_.get())
    190     dragged_view_->Update();
    191 }
    192 
    193 void DraggedTabControllerGtk::AddNewContents(WebContents* source,
    194                                              WebContents* new_contents,
    195                                              WindowOpenDisposition disposition,
    196                                              const gfx::Rect& initial_pos,
    197                                              bool user_gesture,
    198                                              bool* was_blocked) {
    199   DCHECK(disposition != CURRENT_TAB);
    200 
    201   // Theoretically could be called while dragging if the page tries to
    202   // spawn a window. Route this message back to the browser in most cases.
    203   if (drag_data_->GetSourceTabData()->original_delegate_) {
    204     drag_data_->GetSourceTabData()->original_delegate_->AddNewContents(
    205         source, new_contents, disposition, initial_pos, user_gesture,
    206         was_blocked);
    207   }
    208 }
    209 
    210 void DraggedTabControllerGtk::LoadingStateChanged(WebContents* source) {
    211   // TODO(jhawkins): It would be nice to respond to this message by changing the
    212   // screen shot in the dragged tab.
    213   if (dragged_view_.get())
    214     dragged_view_->Update();
    215 }
    216 
    217 content::JavaScriptDialogManager*
    218 DraggedTabControllerGtk::GetJavaScriptDialogManager() {
    219   return GetJavaScriptDialogManagerInstance();
    220 }
    221 
    222 ////////////////////////////////////////////////////////////////////////////////
    223 // DraggedTabControllerGtk, content::NotificationObserver implementation:
    224 
    225 void DraggedTabControllerGtk::Observe(
    226     int type,
    227     const content::NotificationSource& source,
    228     const content::NotificationDetails& details) {
    229   DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
    230   WebContents* destroyed_web_contents =
    231       content::Source<WebContents>(source).ptr();
    232   for (size_t i = 0; i < drag_data_->size(); ++i) {
    233     if (drag_data_->get(i)->contents_ == destroyed_web_contents) {
    234       // One of the tabs we're dragging has been destroyed. Cancel the drag.
    235       if (destroyed_web_contents->GetDelegate() == this)
    236         destroyed_web_contents->SetDelegate(NULL);
    237       drag_data_->get(i)->contents_ = NULL;
    238       drag_data_->get(i)->original_delegate_ = NULL;
    239       EndDragImpl(TAB_DESTROYED);
    240       return;
    241     }
    242   }
    243   // If we get here it means we got notification for a tab we don't know about.
    244   NOTREACHED();
    245 }
    246 
    247 gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const {
    248   gfx::Point creation_point =
    249       gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
    250   gfx::Point distance_from_origin =
    251       dragged_view_->GetDistanceFromTabStripOriginToMousePointer();
    252   // TODO(dpapad): offset also because of tabstrip origin being different than
    253   // window origin.
    254   creation_point.Offset(-distance_from_origin.x(), -distance_from_origin.y());
    255   return creation_point;
    256 }
    257 
    258 void DraggedTabControllerGtk::ContinueDragging() {
    259   // TODO(jhawkins): We don't handle the situation where the last tab is dragged
    260   // out of a window, so we'll just go with the way Windows handles dragging for
    261   // now.
    262   gfx::Point screen_point =
    263       gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
    264 
    265   // Determine whether or not we have dragged over a compatible TabStrip in
    266   // another browser window. If we have, we should attach to it and start
    267   // dragging within it.
    268   TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point);
    269   if (target_tabstrip != attached_tabstrip_) {
    270     // Make sure we're fully detached from whatever TabStrip we're attached to
    271     // (if any).
    272     if (attached_tabstrip_)
    273       Detach();
    274 
    275     if (target_tabstrip)
    276       Attach(target_tabstrip, screen_point);
    277   }
    278 
    279   if (!target_tabstrip) {
    280     bring_to_front_timer_.Start(FROM_HERE,
    281         base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this,
    282         &DraggedTabControllerGtk::BringWindowUnderMouseToFront);
    283   }
    284 
    285   if (attached_tabstrip_)
    286     MoveAttached(screen_point);
    287   else
    288     MoveDetached(screen_point);
    289 }
    290 
    291 void DraggedTabControllerGtk::RestoreSelection(TabStripModel* model) {
    292   for (size_t i = 0; i < drag_data_->size(); ++i) {
    293     WebContents* contents = drag_data_->get(i)->contents_;
    294     // If a tab is destroyed while dragging contents might be null. See
    295     // http://crbug.com/115409.
    296     if (contents) {
    297       int index = model->GetIndexOfWebContents(contents);
    298       CHECK(index != TabStripModel::kNoTab);
    299       model->AddTabAtToSelection(index);
    300     }
    301   }
    302 }
    303 
    304 void DraggedTabControllerGtk::MoveAttached(const gfx::Point& screen_point) {
    305   DCHECK(attached_tabstrip_);
    306 
    307   gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point);
    308   TabStripModel* attached_model = attached_tabstrip_->model();
    309 
    310   std::vector<TabGtk*> tabs(drag_data_->GetDraggedTabs());
    311 
    312   // Determine the horizontal move threshold. This is dependent on the width
    313   // of tabs. The smaller the tabs compared to the standard size, the smaller
    314   // the threshold.
    315   double unselected, selected;
    316   attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
    317   double ratio = unselected / TabGtk::GetStandardSize().width();
    318   int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
    319 
    320   // Update the model, moving the WebContents from one index to another.
    321   // Do this only if we have moved a minimum distance since the last reorder (to
    322   // prevent jitter) or if this is the first move and the tabs are not
    323   // consecutive.
    324   if (abs(screen_point.x() - last_move_screen_x_) > threshold ||
    325       (initial_move_ && !AreTabsConsecutive())) {
    326     if (initial_move_ && !AreTabsConsecutive()) {
    327       // Making dragged tabs adjacent, this is done only once, if necessary.
    328       attached_tabstrip_->model()->MoveSelectedTabsTo(
    329           drag_data_->GetSourceTabData()->source_model_index_ -
    330               drag_data_->source_tab_index());
    331     }
    332     gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
    333     int to_index = GetInsertionIndexForDraggedBounds(
    334         GetEffectiveBounds(bounds));
    335     to_index = NormalizeIndexToAttachedTabStrip(to_index);
    336     last_move_screen_x_ = screen_point.x();
    337     attached_model->MoveSelectedTabsTo(to_index);
    338   }
    339 
    340   dragged_view_->MoveAttachedTo(dragged_view_point);
    341   initial_move_ = false;
    342 }
    343 
    344 void DraggedTabControllerGtk::MoveDetached(const gfx::Point& screen_point) {
    345   DCHECK(!attached_tabstrip_);
    346   // Just moving the dragged view. There are no changes to the model if we're
    347   // detached.
    348   dragged_view_->MoveDetachedTo(screen_point);
    349 }
    350 
    351 TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint(
    352     const gfx::Point& screen_point) {
    353   GtkWidget* dragged_window = dragged_view_->widget();
    354   dock_windows_.insert(dragged_window);
    355   gfx::NativeWindow local_window =
    356       DockInfo::GetLocalProcessWindowAtPoint(
    357           chrome::HOST_DESKTOP_TYPE_NATIVE, screen_point, dock_windows_);
    358   dock_windows_.erase(dragged_window);
    359   if (!local_window)
    360     return NULL;
    361 
    362   BrowserWindowGtk* browser =
    363       BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window);
    364   if (!browser)
    365     return NULL;
    366 
    367   TabStripGtk* other_tabstrip = browser->tabstrip();
    368   if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
    369     return NULL;
    370 
    371   return GetTabStripIfItContains(other_tabstrip, screen_point);
    372 }
    373 
    374 TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains(
    375     TabStripGtk* tabstrip, const gfx::Point& screen_point) const {
    376   // Make sure the specified screen point is actually within the bounds of the
    377   // specified tabstrip...
    378   gfx::Rect tabstrip_bounds =
    379       ui::GetWidgetScreenBounds(tabstrip->tabstrip_.get());
    380   if (screen_point.x() < tabstrip_bounds.right() &&
    381       screen_point.x() >= tabstrip_bounds.x()) {
    382     // TODO(beng): make this be relative to the start position of the mouse for
    383     // the source TabStrip.
    384     int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
    385     int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
    386     if (screen_point.y() >= lower_threshold &&
    387         screen_point.y() <= upper_threshold) {
    388       return tabstrip;
    389     }
    390   }
    391 
    392   return NULL;
    393 }
    394 
    395 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip,
    396                                      const gfx::Point& screen_point) {
    397   attached_tabstrip_ = attached_tabstrip;
    398   attached_tabstrip_->GenerateIdealBounds();
    399 
    400   std::vector<TabGtk*> attached_dragged_tabs =
    401       GetTabsMatchingDraggedContents(attached_tabstrip_);
    402 
    403   // Update the tab first, so we can ask it for its bounds and determine
    404   // where to insert the hidden tab.
    405 
    406   // If this is the first time Attach is called for this drag, we're attaching
    407   // to the source tabstrip, and we should assume the tab count already
    408   // includes this tab since we haven't been detached yet. If we don't do this,
    409   // the dragged representation will be a different size to others in the
    410   // tabstrip.
    411   int tab_count = attached_tabstrip_->GetTabCount();
    412   int mini_tab_count = attached_tabstrip_->GetMiniTabCount();
    413   if (attached_dragged_tabs.size() == 0) {
    414     tab_count += drag_data_->size();
    415     mini_tab_count += drag_data_->mini_tab_count();
    416   }
    417 
    418   double unselected_width = 0, selected_width = 0;
    419   attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count,
    420                                           &unselected_width, &selected_width);
    421 
    422   GtkWidget* parent_window = gtk_widget_get_parent(
    423       gtk_widget_get_parent(attached_tabstrip_->tabstrip_.get()));
    424   gfx::Rect window_bounds = ui::GetWidgetScreenBounds(parent_window);
    425   dragged_view_->Attach(static_cast<int>(floor(selected_width + 0.5)),
    426                         TabGtk::GetMiniWidth(), window_bounds.width());
    427 
    428   if (attached_dragged_tabs.size() == 0) {
    429     // There is no tab in |attached_tabstrip| that corresponds to the dragged
    430     // WebContents. We must now create one.
    431 
    432     // Remove ourselves as the delegate now that the dragged WebContents
    433     // is being inserted back into a Browser.
    434     for (size_t i = 0; i < drag_data_->size(); ++i) {
    435       drag_data_->get(i)->contents_->SetDelegate(NULL);
    436       drag_data_->get(i)->original_delegate_ = NULL;
    437     }
    438 
    439     // We need to ask the tabstrip we're attached to ensure that the ideal
    440     // bounds for all its tabs are correctly generated, because the calculation
    441     // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
    442     // appropriate insertion index.
    443     attached_tabstrip_->GenerateIdealBounds();
    444 
    445     // Inserting counts as a move. We don't want the tabs to jitter when the
    446     // user moves the tab immediately after attaching it.
    447     last_move_screen_x_ = screen_point.x();
    448 
    449     // Figure out where to insert the tab based on the bounds of the dragged
    450     // representation and the ideal bounds of the other tabs already in the
    451     // strip. ("ideal bounds" are stable even if the tabs' actual bounds are
    452     // changing due to animation).
    453     gfx::Rect bounds =
    454         GetDraggedViewTabStripBounds(GetDraggedViewPoint(screen_point));
    455     int index = GetInsertionIndexForDraggedBounds(GetEffectiveBounds(bounds));
    456     for (size_t i = 0; i < drag_data_->size(); ++i) {
    457       attached_tabstrip_->model()->InsertWebContentsAt(
    458           index + i, drag_data_->get(i)->contents_,
    459           drag_data_->GetAddTypesForDraggedTabAt(i));
    460     }
    461     RestoreSelection(attached_tabstrip_->model());
    462     attached_dragged_tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
    463   }
    464   // We should now have a tab.
    465   DCHECK(attached_dragged_tabs.size() == drag_data_->size());
    466   SetDraggedTabsVisible(false, false);
    467   // TODO(jhawkins): Move the corresponding window to the front.
    468 }
    469 
    470 void DraggedTabControllerGtk::Detach() {
    471   // Update the Model.
    472   TabStripModel* attached_model = attached_tabstrip_->model();
    473   for (size_t i = 0; i < drag_data_->size(); ++i) {
    474     int index =
    475         attached_model->GetIndexOfWebContents(drag_data_->get(i)->contents_);
    476     if (index >= 0 && index < attached_model->count()) {
    477       // Sometimes, DetachWebContentsAt has consequences that result in
    478       // attached_tabstrip_ being set to NULL, so we need to save it first.
    479       attached_model->DetachWebContentsAt(index);
    480     }
    481   }
    482 
    483   // If we've removed the last tab from the tabstrip, hide the frame now.
    484   if (attached_model->empty())
    485     HideWindow();
    486 
    487   // Update the dragged tab. This NULL check is necessary apparently in some
    488   // conditions during automation where the view_ is destroyed inside a
    489   // function call preceding this point but after it is created.
    490   if (dragged_view_.get()) {
    491     dragged_view_->Detach();
    492   }
    493 
    494   // Detaching resets the delegate, but we still want to be the delegate.
    495   for (size_t i = 0; i < drag_data_->size(); ++i)
    496     drag_data_->get(i)->contents_->SetDelegate(this);
    497 
    498   attached_tabstrip_ = NULL;
    499 }
    500 
    501 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint(
    502     TabStripGtk* tabstrip, const gfx::Point& screen_point) {
    503   return screen_point - ui::GetWidgetScreenOffset(tabstrip->tabstrip_.get());
    504 }
    505 
    506 gfx::Rect DraggedTabControllerGtk::GetDraggedViewTabStripBounds(
    507     const gfx::Point& screen_point) {
    508   gfx::Point client_point =
    509       ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
    510   gfx::Size tab_size = dragged_view_->attached_tab_size();
    511   return gfx::Rect(client_point.x(), client_point.y(),
    512                    dragged_view_->GetTotalWidthInTabStrip(), tab_size.height());
    513 }
    514 
    515 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds(
    516     const gfx::Rect& dragged_bounds) {
    517   int dragged_bounds_start = gtk_util::MirroredLeftPointForRect(
    518       attached_tabstrip_->widget(),
    519       dragged_bounds);
    520   int dragged_bounds_end = gtk_util::MirroredRightPointForRect(
    521       attached_tabstrip_->widget(),
    522       dragged_bounds);
    523   int right_tab_x = 0;
    524   int index = -1;
    525 
    526   for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) {
    527     // Divides each tab into two halves to see if the dragged tab has crossed
    528     // the halfway boundary necessary to move past the next tab.
    529     gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i);
    530     gfx::Rect left_half, right_half;
    531     ideal_bounds.SplitVertically(&left_half, &right_half);
    532     right_tab_x = right_half.x();
    533 
    534     if (dragged_bounds_start >= right_half.x() &&
    535         dragged_bounds_start < right_half.right()) {
    536       index = i + 1;
    537       break;
    538     } else if (dragged_bounds_start >= left_half.x() &&
    539                dragged_bounds_start < left_half.right()) {
    540       index = i;
    541       break;
    542     }
    543   }
    544 
    545   if (index == -1) {
    546     if (dragged_bounds_end > right_tab_x)
    547       index = attached_tabstrip_->GetTabCount();
    548     else
    549       index = 0;
    550   }
    551 
    552   TabGtk* tab = GetTabMatchingDraggedContents(
    553       attached_tabstrip_, drag_data_->GetSourceWebContents());
    554   if (tab == NULL) {
    555     // If dragged tabs are not attached yet, we don't need to constrain the
    556     // index.
    557     return index;
    558   }
    559 
    560   int max_index =
    561       attached_tabstrip_-> GetTabCount() - static_cast<int>(drag_data_->size());
    562   return std::max(0, std::min(max_index, index));
    563 }
    564 
    565 gfx::Point DraggedTabControllerGtk::GetDraggedViewPoint(
    566     const gfx::Point& screen_point) {
    567   int x = screen_point.x() -
    568       dragged_view_->GetWidthInTabStripUpToMousePointer();
    569   int y = screen_point.y() - mouse_offset_.y();
    570 
    571   // If we're not attached, we just use x and y from above.
    572   if (attached_tabstrip_) {
    573     gfx::Rect tabstrip_bounds =
    574         ui::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get());
    575     // Snap the dragged tab to the tabstrip if we are attached, detaching
    576     // only when the mouse position (screen_point) exceeds the screen bounds
    577     // of the tabstrip.
    578     if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x())
    579       x = tabstrip_bounds.x();
    580 
    581     gfx::Size tab_size = dragged_view_->attached_tab_size();
    582     int vertical_drag_magnetism = tab_size.height() * 2;
    583     int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism;
    584     if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point)
    585       y = tabstrip_bounds.y();
    586 
    587     // Make sure the tab can't be dragged off the right side of the tabstrip
    588     // unless the mouse pointer passes outside the bounds of the strip by
    589     // clamping the position of the dragged window to the tabstrip width less
    590     // the width of one tab until the mouse pointer (screen_point) exceeds the
    591     // screen bounds of the tabstrip.
    592     int max_x =
    593         tabstrip_bounds.right() - dragged_view_->GetTotalWidthInTabStrip();
    594     int max_y = tabstrip_bounds.bottom() - tab_size.height();
    595     if (x > max_x && screen_point.x() <= tabstrip_bounds.right())
    596       x = max_x;
    597     if (y > max_y && screen_point.y() <=
    598         (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
    599       y = max_y;
    600     }
    601   }
    602   return gfx::Point(x, y);
    603 }
    604 
    605 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const {
    606   if (index >= attached_tabstrip_->model_->count())
    607     return attached_tabstrip_->model_->count() - 1;
    608   if (index == TabStripModel::kNoTab)
    609     return 0;
    610   return index;
    611 }
    612 
    613 TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents(
    614     TabStripGtk* tabstrip, WebContents* web_contents) {
    615   int index = tabstrip->model()->GetIndexOfWebContents(web_contents);
    616   return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
    617 }
    618 
    619 std::vector<TabGtk*> DraggedTabControllerGtk::GetTabsMatchingDraggedContents(
    620     TabStripGtk* tabstrip) {
    621   std::vector<TabGtk*> dragged_tabs;
    622   for (size_t i = 0; i < drag_data_->size(); ++i) {
    623     if (!drag_data_->get(i)->contents_)
    624       continue;
    625     TabGtk* tab = GetTabMatchingDraggedContents(tabstrip,
    626                                                 drag_data_->get(i)->contents_);
    627     if (tab)
    628       dragged_tabs.push_back(tab);
    629   }
    630   return dragged_tabs;
    631 }
    632 
    633 void DraggedTabControllerGtk::SetDraggedTabsVisible(bool visible,
    634                                                     bool repaint) {
    635   std::vector<TabGtk*> dragged_tabs(GetTabsMatchingDraggedContents(
    636       attached_tabstrip_));
    637   for (size_t i = 0; i < dragged_tabs.size(); ++i) {
    638     dragged_tabs[i]->SetVisible(visible);
    639     dragged_tabs[i]->set_dragging(!visible);
    640     if (repaint)
    641       dragged_tabs[i]->SchedulePaint();
    642   }
    643 }
    644 
    645 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) {
    646   bring_to_front_timer_.Stop();
    647 
    648   // WARNING: this may be invoked multiple times. In particular, if deletion
    649   // occurs after a delay (as it does when the tab is released in the original
    650   // tab strip) and the navigation controller/tab contents is deleted before
    651   // the animation finishes, this is invoked twice. The second time through
    652   // type == TAB_DESTROYED.
    653 
    654   bool destroy_now = true;
    655   if (type != TAB_DESTROYED) {
    656     // If we never received a drag-motion event, the drag will never have
    657     // started in the sense that |dragged_view_| will be NULL. We don't need to
    658     // revert or complete the drag in that case.
    659     if (dragged_view_.get()) {
    660       if (type == CANCELED) {
    661         RevertDrag();
    662       } else {
    663         destroy_now = CompleteDrag();
    664       }
    665     }
    666   } else if (drag_data_->size() > 0) {
    667     RevertDrag();
    668   }
    669 
    670   ResetDelegates();
    671 
    672   // If we're not destroyed now, we'll be destroyed asynchronously later.
    673   if (destroy_now)
    674     source_tabstrip_->DestroyDragController();
    675 
    676   return destroy_now;
    677 }
    678 
    679 void DraggedTabControllerGtk::RevertDrag() {
    680   // We save this here because code below will modify |attached_tabstrip_|.
    681   bool restore_window = attached_tabstrip_ != source_tabstrip_;
    682   if (attached_tabstrip_) {
    683     if (attached_tabstrip_ != source_tabstrip_) {
    684       // The tabs were inserted into another tabstrip. We need to put them back
    685       // into the original one.
    686       for (size_t i = 0; i < drag_data_->size(); ++i) {
    687         if (!drag_data_->get(i)->contents_)
    688           continue;
    689         int index = attached_tabstrip_->model()->GetIndexOfWebContents(
    690             drag_data_->get(i)->contents_);
    691         attached_tabstrip_->model()->DetachWebContentsAt(index);
    692       }
    693       // TODO(beng): (Cleanup) seems like we should use Attach() for this
    694       //             somehow.
    695       attached_tabstrip_ = source_tabstrip_;
    696       for (size_t i = 0; i < drag_data_->size(); ++i) {
    697         if (!drag_data_->get(i)->contents_)
    698           continue;
    699         attached_tabstrip_->model()->InsertWebContentsAt(
    700             drag_data_->get(i)->source_model_index_,
    701             drag_data_->get(i)->contents_,
    702             drag_data_->GetAddTypesForDraggedTabAt(i));
    703       }
    704     } else {
    705       // The tabs were moved within the tabstrip where the drag was initiated.
    706       // Move them back to their starting locations.
    707       for (size_t i = 0; i < drag_data_->size(); ++i) {
    708         if (!drag_data_->get(i)->contents_)
    709           continue;
    710         int index = attached_tabstrip_->model()->GetIndexOfWebContents(
    711             drag_data_->get(i)->contents_);
    712         source_tabstrip_->model()->MoveWebContentsAt(
    713             index, drag_data_->get(i)->source_model_index_, true);
    714       }
    715     }
    716   } else {
    717     // TODO(beng): (Cleanup) seems like we should use Attach() for this
    718     //             somehow.
    719     attached_tabstrip_ = source_tabstrip_;
    720     // The tab was detached from the tabstrip where the drag began, and has not
    721     // been attached to any other tabstrip. We need to put it back into the
    722     // source tabstrip.
    723     for (size_t i = 0; i < drag_data_->size(); ++i) {
    724       if (!drag_data_->get(i)->contents_)
    725         continue;
    726       source_tabstrip_->model()->InsertWebContentsAt(
    727           drag_data_->get(i)->source_model_index_,
    728           drag_data_->get(i)->contents_,
    729           drag_data_->GetAddTypesForDraggedTabAt(i));
    730     }
    731   }
    732   RestoreSelection(source_tabstrip_->model());
    733 
    734   // If we're not attached to any tab strip, or attached to some other tab
    735   // strip, we need to restore the bounds of the original tab strip's frame, in
    736   // case it has been hidden.
    737   if (restore_window)
    738     ShowWindow();
    739 
    740   SetDraggedTabsVisible(true, false);
    741 }
    742 
    743 bool DraggedTabControllerGtk::CompleteDrag() {
    744   bool destroy_immediately = true;
    745   if (attached_tabstrip_) {
    746     // We don't need to do anything other than make the tabs visible again,
    747     // since the dragged view is going away.
    748     dragged_view_->AnimateToBounds(GetAnimateBounds(),
    749         base::Bind(&DraggedTabControllerGtk::OnAnimateToBoundsComplete,
    750                    base::Unretained(this)));
    751     destroy_immediately = false;
    752   } else {
    753     // Compel the model to construct a new window for the detached
    754     // WebContentses.
    755     BrowserWindowGtk* window = source_tabstrip_->window();
    756     gfx::Rect window_bounds = window->GetRestoredBounds();
    757     window_bounds.set_origin(GetWindowCreatePoint());
    758 
    759     std::vector<TabStripModelDelegate::NewStripContents> contentses;
    760     for (size_t i = 0; i < drag_data_->size(); ++i) {
    761       TabStripModelDelegate::NewStripContents item;
    762       item.web_contents = drag_data_->get(i)->contents_;
    763       item.add_types = drag_data_->GetAddTypesForDraggedTabAt(i);
    764       contentses.push_back(item);
    765     };
    766 
    767     Browser* new_browser =
    768         source_tabstrip_->model()->delegate()->CreateNewStripWithContents(
    769             contentses, window_bounds, dock_info_, window->IsMaximized());
    770     RestoreSelection(new_browser->tab_strip_model());
    771     new_browser->window()->Show();
    772     CleanUpHiddenFrame();
    773   }
    774 
    775   return destroy_immediately;
    776 }
    777 
    778 void DraggedTabControllerGtk::ResetDelegates() {
    779   for (size_t i = 0; i < drag_data_->size(); ++i) {
    780     if (drag_data_->get(i)->contents_ &&
    781         drag_data_->get(i)->contents_->GetDelegate() == this) {
    782       drag_data_->get(i)->ResetDelegate();
    783     }
    784   }
    785 }
    786 
    787 void DraggedTabControllerGtk::EnsureDraggedView() {
    788   if (!dragged_view_.get()) {
    789     gfx::Rect rect;
    790     drag_data_->GetSourceWebContents()->GetView()->GetContainerBounds(&rect);
    791     dragged_view_.reset(new DraggedViewGtk(drag_data_.get(), mouse_offset_,
    792                                            rect.size()));
    793   }
    794 }
    795 
    796 gfx::Rect DraggedTabControllerGtk::GetAnimateBounds() {
    797   // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't
    798   // update its allocation until after the widget is shown, so we have to use
    799   // the tab bounds we keep track of.
    800   //
    801   // We use the requested bounds instead of the allocation because the
    802   // allocation is relative to the first windowed widget ancestor of the tab.
    803   // Because of this, we can't use the tabs allocation to get the screen bounds.
    804   std::vector<TabGtk*> tabs = GetTabsMatchingDraggedContents(
    805       attached_tabstrip_);
    806   TabGtk* tab = !base::i18n::IsRTL() ? tabs.front() : tabs.back();
    807   gfx::Rect bounds = tab->GetRequisition();
    808   GtkWidget* widget = tab->widget();
    809   GtkWidget* parent = gtk_widget_get_parent(widget);
    810   bounds.Offset(ui::GetWidgetScreenOffset(parent));
    811 
    812   return gfx::Rect(bounds.x(), bounds.y(),
    813                    dragged_view_->GetTotalWidthInTabStrip(), bounds.height());
    814 }
    815 
    816 void DraggedTabControllerGtk::HideWindow() {
    817   GtkWidget* tabstrip = source_tabstrip_->widget();
    818   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
    819   gtk_widget_hide(GTK_WIDGET(window));
    820 }
    821 
    822 void DraggedTabControllerGtk::ShowWindow() {
    823   GtkWidget* tabstrip = source_tabstrip_->widget();
    824   GtkWindow* window = platform_util::GetTopLevel(tabstrip);
    825   gtk_window_present(window);
    826 }
    827 
    828 void DraggedTabControllerGtk::CleanUpHiddenFrame() {
    829   // If the model we started dragging from is now empty, we must ask the
    830   // delegate to close the frame.
    831   if (source_tabstrip_->model()->empty())
    832     source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
    833 }
    834 
    835 void DraggedTabControllerGtk::CleanUpDraggedTabs() {
    836   // If we were attached to the source tabstrip, dragged tabs will be in use. If
    837   // we were detached or attached to another tabstrip, we can safely remove
    838   // them and delete them now.
    839   if (attached_tabstrip_ != source_tabstrip_) {
    840     for (size_t i = 0; i < drag_data_->size(); ++i) {
    841       if (drag_data_->get(i)->contents_) {
    842         registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    843             content::Source<WebContents>(drag_data_->get(i)->contents_));
    844       }
    845       source_tabstrip_->DestroyDraggedTab(drag_data_->get(i)->tab_);
    846       drag_data_->get(i)->tab_ = NULL;
    847     }
    848   }
    849 }
    850 
    851 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() {
    852   // Sometimes, for some reason, in automation we can be called back on a
    853   // detach even though we aren't attached to a tabstrip. Guard against that.
    854   if (attached_tabstrip_)
    855     SetDraggedTabsVisible(true, true);
    856 
    857   CleanUpHiddenFrame();
    858 
    859   if (!in_destructor_)
    860     source_tabstrip_->DestroyDragController();
    861 }
    862 
    863 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() {
    864   // If we're going to dock to another window, bring it to the front.
    865   gfx::NativeWindow window = dock_info_.window();
    866   if (!window) {
    867     gfx::NativeView dragged_tab = dragged_view_->widget();
    868     dock_windows_.insert(dragged_tab);
    869     window = DockInfo::GetLocalProcessWindowAtPoint(
    870         chrome::HOST_DESKTOP_TYPE_NATIVE,
    871         gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(),
    872         dock_windows_);
    873     dock_windows_.erase(dragged_tab);
    874   }
    875 
    876   if (window)
    877     gtk_window_present(GTK_WINDOW(window));
    878 }
    879 
    880 bool DraggedTabControllerGtk::AreTabsConsecutive() {
    881   for (size_t i = 1; i < drag_data_->size(); ++i) {
    882     if (drag_data_->get(i - 1)->source_model_index_ + 1 !=
    883         drag_data_->get(i)->source_model_index_) {
    884       return false;
    885     }
    886   }
    887   return true;
    888 }
    889