// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/gtk/tabs/dragged_tab_controller_gtk.h" #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/i18n/rtl.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/gtk/browser_window_gtk.h" #include "chrome/browser/ui/gtk/gtk_util.h" #include "chrome/browser/ui/gtk/tabs/dragged_view_gtk.h" #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" #include "chrome/browser/ui/media_utils.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "ui/base/gtk/gtk_screen_util.h" #include "ui/gfx/screen.h" using content::OpenURLParams; using content::WebContents; namespace { // Delay, in ms, during dragging before we bring a window to front. const int kBringToFrontDelay = 750; // Used to determine how far a tab must obscure another tab in order to swap // their indexes. const int kHorizontalMoveThreshold = 16; // pixels // How far a drag must pull a tab out of the tabstrip in order to detach it. const int kVerticalDetachMagnetism = 15; // pixels // Returns the bounds that will be used for insertion index calculation. // |bounds| is the actual tab bounds, but subtracting the overlapping areas from // both sides makes the calculations much simpler. gfx::Rect GetEffectiveBounds(const gfx::Rect& bounds) { gfx::Rect effective_bounds(bounds); effective_bounds.set_width(effective_bounds.width() - 2 * 16); effective_bounds.set_x(effective_bounds.x() + 16); return effective_bounds; } } // namespace DraggedTabControllerGtk::DraggedTabControllerGtk( TabStripGtk* source_tabstrip, TabGtk* source_tab, const std::vector& tabs) : source_tabstrip_(source_tabstrip), attached_tabstrip_(source_tabstrip), in_destructor_(false), last_move_screen_x_(0), initial_move_(true) { DCHECK(!tabs.empty()); DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); std::vector drag_data; for (size_t i = 0; i < tabs.size(); i++) drag_data.push_back(InitDraggedTabData(tabs[i])); int source_tab_index = std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin(); drag_data_.reset(new DragData(drag_data, source_tab_index)); } DraggedTabControllerGtk::~DraggedTabControllerGtk() { in_destructor_ = true; // Need to delete the dragged tab here manually _before_ we reset the dragged // contents to NULL, otherwise if the view is animating to its destination // bounds, it won't be able to clean up properly since its cleanup routine // uses GetIndexForDraggedContents, which will be invalid. CleanUpDraggedTabs(); dragged_view_.reset(); drag_data_.reset(); } void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) { start_screen_point_ = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); mouse_offset_ = mouse_offset; } void DraggedTabControllerGtk::Drag() { if (!drag_data_->GetSourceTabData()->tab_ || !drag_data_->GetSourceWebContents()) { return; } bring_to_front_timer_.Stop(); EnsureDraggedView(); // Before we get to dragging anywhere, ensure that we consider ourselves // attached to the source tabstrip. if (drag_data_->GetSourceTabData()->tab_->IsVisible()) { Attach(source_tabstrip_, gfx::Point()); } if (!drag_data_->GetSourceTabData()->tab_->IsVisible()) { // TODO(jhawkins): Save focus. ContinueDragging(); } } bool DraggedTabControllerGtk::EndDrag(bool canceled) { return EndDragImpl(canceled ? CANCELED : NORMAL); } TabGtk* DraggedTabControllerGtk::GetDraggedTabForContents( WebContents* contents) { if (attached_tabstrip_ == source_tabstrip_) { for (size_t i = 0; i < drag_data_->size(); i++) { if (contents == drag_data_->get(i)->contents_) return drag_data_->get(i)->tab_; } } return NULL; } bool DraggedTabControllerGtk::IsDraggingTab(const TabGtk* tab) { for (size_t i = 0; i < drag_data_->size(); i++) { if (tab == drag_data_->get(i)->tab_) return true; } return false; } bool DraggedTabControllerGtk::IsDraggingWebContents( const WebContents* web_contents) { for (size_t i = 0; i < drag_data_->size(); i++) { if (web_contents == drag_data_->get(i)->contents_) return true; } return false; } bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) { return IsDraggingTab(tab) && attached_tabstrip_ == NULL; } DraggedTabData DraggedTabControllerGtk::InitDraggedTabData(TabGtk* tab) { int source_model_index = source_tabstrip_->GetIndexOfTab(tab); WebContents* contents = source_tabstrip_->model()->GetWebContentsAt(source_model_index); bool pinned = source_tabstrip_->IsTabPinned(tab); bool mini = source_tabstrip_->model()->IsMiniTab(source_model_index); // We need to be the delegate so we receive messages about stuff, // otherwise our dragged_contents() may be replaced and subsequently // collected/destroyed while the drag is in process, leading to // nasty crashes. content::WebContentsDelegate* original_delegate = contents->GetDelegate(); contents->SetDelegate(this); DraggedTabData dragged_tab_data(tab, contents, original_delegate, source_model_index, pinned, mini); registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source(contents)); return dragged_tab_data; } //////////////////////////////////////////////////////////////////////////////// // DraggedTabControllerGtk, content::WebContentsDelegate implementation: WebContents* DraggedTabControllerGtk::OpenURLFromTab( WebContents* source, const OpenURLParams& params) { if (drag_data_->GetSourceTabData()->original_delegate_) { OpenURLParams forward_params = params; if (params.disposition == CURRENT_TAB) forward_params.disposition = NEW_WINDOW; return drag_data_->GetSourceTabData()->original_delegate_-> OpenURLFromTab(source, forward_params); } return NULL; } void DraggedTabControllerGtk::NavigationStateChanged(const WebContents* source, unsigned changed_flags) { if (dragged_view_.get()) dragged_view_->Update(); } void DraggedTabControllerGtk::AddNewContents(WebContents* source, WebContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture, bool* was_blocked) { DCHECK(disposition != CURRENT_TAB); // Theoretically could be called while dragging if the page tries to // spawn a window. Route this message back to the browser in most cases. if (drag_data_->GetSourceTabData()->original_delegate_) { drag_data_->GetSourceTabData()->original_delegate_->AddNewContents( source, new_contents, disposition, initial_pos, user_gesture, was_blocked); } } void DraggedTabControllerGtk::LoadingStateChanged(WebContents* source) { // TODO(jhawkins): It would be nice to respond to this message by changing the // screen shot in the dragged tab. if (dragged_view_.get()) dragged_view_->Update(); } content::JavaScriptDialogManager* DraggedTabControllerGtk::GetJavaScriptDialogManager() { return GetJavaScriptDialogManagerInstance(); } //////////////////////////////////////////////////////////////////////////////// // DraggedTabControllerGtk, content::NotificationObserver implementation: void DraggedTabControllerGtk::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); WebContents* destroyed_web_contents = content::Source(source).ptr(); for (size_t i = 0; i < drag_data_->size(); ++i) { if (drag_data_->get(i)->contents_ == destroyed_web_contents) { // One of the tabs we're dragging has been destroyed. Cancel the drag. if (destroyed_web_contents->GetDelegate() == this) destroyed_web_contents->SetDelegate(NULL); drag_data_->get(i)->contents_ = NULL; drag_data_->get(i)->original_delegate_ = NULL; EndDragImpl(TAB_DESTROYED); return; } } // If we get here it means we got notification for a tab we don't know about. NOTREACHED(); } void DraggedTabControllerGtk::RequestMediaAccessPermission( content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) { ::RequestMediaAccessPermission( web_contents, Profile::FromBrowserContext(web_contents->GetBrowserContext()), request, callback); } gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const { gfx::Point creation_point = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); gfx::Point distance_from_origin = dragged_view_->GetDistanceFromTabStripOriginToMousePointer(); // TODO(dpapad): offset also because of tabstrip origin being different than // window origin. creation_point.Offset(-distance_from_origin.x(), -distance_from_origin.y()); return creation_point; } void DraggedTabControllerGtk::ContinueDragging() { // TODO(jhawkins): We don't handle the situation where the last tab is dragged // out of a window, so we'll just go with the way Windows handles dragging for // now. gfx::Point screen_point = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); // Determine whether or not we have dragged over a compatible TabStrip in // another browser window. If we have, we should attach to it and start // dragging within it. TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point); if (target_tabstrip != attached_tabstrip_) { // Make sure we're fully detached from whatever TabStrip we're attached to // (if any). if (attached_tabstrip_) Detach(); if (target_tabstrip) Attach(target_tabstrip, screen_point); } if (!target_tabstrip) { bring_to_front_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, &DraggedTabControllerGtk::BringWindowUnderMouseToFront); } if (attached_tabstrip_) MoveAttached(screen_point); else MoveDetached(screen_point); } void DraggedTabControllerGtk::RestoreSelection(TabStripModel* model) { for (size_t i = 0; i < drag_data_->size(); ++i) { WebContents* contents = drag_data_->get(i)->contents_; // If a tab is destroyed while dragging contents might be null. See // http://crbug.com/115409. if (contents) { int index = model->GetIndexOfWebContents(contents); CHECK(index != TabStripModel::kNoTab); model->AddTabAtToSelection(index); } } } void DraggedTabControllerGtk::MoveAttached(const gfx::Point& screen_point) { DCHECK(attached_tabstrip_); gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point); TabStripModel* attached_model = attached_tabstrip_->model(); std::vector tabs(drag_data_->GetDraggedTabs()); // Determine the horizontal move threshold. This is dependent on the width // of tabs. The smaller the tabs compared to the standard size, the smaller // the threshold. double unselected, selected; attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected); double ratio = unselected / TabGtk::GetStandardSize().width(); int threshold = static_cast(ratio * kHorizontalMoveThreshold); // Update the model, moving the WebContents from one index to another. // Do this only if we have moved a minimum distance since the last reorder (to // prevent jitter) or if this is the first move and the tabs are not // consecutive. if (abs(screen_point.x() - last_move_screen_x_) > threshold || (initial_move_ && !AreTabsConsecutive())) { if (initial_move_ && !AreTabsConsecutive()) { // Making dragged tabs adjacent, this is done only once, if necessary. attached_tabstrip_->model()->MoveSelectedTabsTo( drag_data_->GetSourceTabData()->source_model_index_ - drag_data_->source_tab_index()); } gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point); int to_index = GetInsertionIndexForDraggedBounds( GetEffectiveBounds(bounds)); to_index = NormalizeIndexToAttachedTabStrip(to_index); last_move_screen_x_ = screen_point.x(); attached_model->MoveSelectedTabsTo(to_index); } dragged_view_->MoveAttachedTo(dragged_view_point); initial_move_ = false; } void DraggedTabControllerGtk::MoveDetached(const gfx::Point& screen_point) { DCHECK(!attached_tabstrip_); // Just moving the dragged view. There are no changes to the model if we're // detached. dragged_view_->MoveDetachedTo(screen_point); } TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( const gfx::Point& screen_point) { GtkWidget* dragged_window = dragged_view_->widget(); dock_windows_.insert(dragged_window); gfx::NativeWindow local_window = DockInfo::GetLocalProcessWindowAtPoint( chrome::HOST_DESKTOP_TYPE_NATIVE, screen_point, dock_windows_); dock_windows_.erase(dragged_window); if (!local_window) return NULL; BrowserWindowGtk* browser = BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window); if (!browser) return NULL; TabStripGtk* other_tabstrip = browser->tabstrip(); if (!other_tabstrip->IsCompatibleWith(source_tabstrip_)) return NULL; return GetTabStripIfItContains(other_tabstrip, screen_point); } TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains( TabStripGtk* tabstrip, const gfx::Point& screen_point) const { // Make sure the specified screen point is actually within the bounds of the // specified tabstrip... gfx::Rect tabstrip_bounds = ui::GetWidgetScreenBounds(tabstrip->tabstrip_.get()); if (screen_point.x() < tabstrip_bounds.right() && screen_point.x() >= tabstrip_bounds.x()) { // TODO(beng): make this be relative to the start position of the mouse for // the source TabStrip. int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism; int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism; if (screen_point.y() >= lower_threshold && screen_point.y() <= upper_threshold) { return tabstrip; } } return NULL; } void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip, const gfx::Point& screen_point) { attached_tabstrip_ = attached_tabstrip; attached_tabstrip_->GenerateIdealBounds(); std::vector attached_dragged_tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); // Update the tab first, so we can ask it for its bounds and determine // where to insert the hidden tab. // If this is the first time Attach is called for this drag, we're attaching // to the source tabstrip, and we should assume the tab count already // includes this tab since we haven't been detached yet. If we don't do this, // the dragged representation will be a different size to others in the // tabstrip. int tab_count = attached_tabstrip_->GetTabCount(); int mini_tab_count = attached_tabstrip_->GetMiniTabCount(); if (attached_dragged_tabs.size() == 0) { tab_count += drag_data_->size(); mini_tab_count += drag_data_->mini_tab_count(); } double unselected_width = 0, selected_width = 0; attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count, &unselected_width, &selected_width); GtkWidget* parent_window = gtk_widget_get_parent( gtk_widget_get_parent(attached_tabstrip_->tabstrip_.get())); gfx::Rect window_bounds = ui::GetWidgetScreenBounds(parent_window); dragged_view_->Attach(static_cast(floor(selected_width + 0.5)), TabGtk::GetMiniWidth(), window_bounds.width()); if (attached_dragged_tabs.size() == 0) { // There is no tab in |attached_tabstrip| that corresponds to the dragged // WebContents. We must now create one. // Remove ourselves as the delegate now that the dragged WebContents // is being inserted back into a Browser. for (size_t i = 0; i < drag_data_->size(); ++i) { drag_data_->get(i)->contents_->SetDelegate(NULL); drag_data_->get(i)->original_delegate_ = NULL; } // We need to ask the tabstrip we're attached to ensure that the ideal // bounds for all its tabs are correctly generated, because the calculation // in GetInsertionIndexForDraggedBounds needs them to be to figure out the // appropriate insertion index. attached_tabstrip_->GenerateIdealBounds(); // Inserting counts as a move. We don't want the tabs to jitter when the // user moves the tab immediately after attaching it. last_move_screen_x_ = screen_point.x(); // Figure out where to insert the tab based on the bounds of the dragged // representation and the ideal bounds of the other tabs already in the // strip. ("ideal bounds" are stable even if the tabs' actual bounds are // changing due to animation). gfx::Rect bounds = GetDraggedViewTabStripBounds(GetDraggedViewPoint(screen_point)); int index = GetInsertionIndexForDraggedBounds(GetEffectiveBounds(bounds)); for (size_t i = 0; i < drag_data_->size(); ++i) { attached_tabstrip_->model()->InsertWebContentsAt( index + i, drag_data_->get(i)->contents_, drag_data_->GetAddTypesForDraggedTabAt(i)); } RestoreSelection(attached_tabstrip_->model()); attached_dragged_tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); } // We should now have a tab. DCHECK(attached_dragged_tabs.size() == drag_data_->size()); SetDraggedTabsVisible(false, false); // TODO(jhawkins): Move the corresponding window to the front. } void DraggedTabControllerGtk::Detach() { // Update the Model. TabStripModel* attached_model = attached_tabstrip_->model(); for (size_t i = 0; i < drag_data_->size(); ++i) { int index = attached_model->GetIndexOfWebContents(drag_data_->get(i)->contents_); if (index >= 0 && index < attached_model->count()) { // Sometimes, DetachWebContentsAt has consequences that result in // attached_tabstrip_ being set to NULL, so we need to save it first. attached_model->DetachWebContentsAt(index); } } // If we've removed the last tab from the tabstrip, hide the frame now. if (attached_model->empty()) HideWindow(); // Update the dragged tab. This NULL check is necessary apparently in some // conditions during automation where the view_ is destroyed inside a // function call preceding this point but after it is created. if (dragged_view_.get()) { dragged_view_->Detach(); } // Detaching resets the delegate, but we still want to be the delegate. for (size_t i = 0; i < drag_data_->size(); ++i) drag_data_->get(i)->contents_->SetDelegate(this); attached_tabstrip_ = NULL; } gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint( TabStripGtk* tabstrip, const gfx::Point& screen_point) { return screen_point - ui::GetWidgetScreenOffset(tabstrip->tabstrip_.get()); } gfx::Rect DraggedTabControllerGtk::GetDraggedViewTabStripBounds( const gfx::Point& screen_point) { gfx::Point client_point = ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); gfx::Size tab_size = dragged_view_->attached_tab_size(); return gfx::Rect(client_point.x(), client_point.y(), dragged_view_->GetTotalWidthInTabStrip(), tab_size.height()); } int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds( const gfx::Rect& dragged_bounds) { int dragged_bounds_start = gtk_util::MirroredLeftPointForRect( attached_tabstrip_->widget(), dragged_bounds); int dragged_bounds_end = gtk_util::MirroredRightPointForRect( attached_tabstrip_->widget(), dragged_bounds); int right_tab_x = 0; int index = -1; for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) { // Divides each tab into two halves to see if the dragged tab has crossed // the halfway boundary necessary to move past the next tab. gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i); gfx::Rect left_half, right_half; ideal_bounds.SplitVertically(&left_half, &right_half); right_tab_x = right_half.x(); if (dragged_bounds_start >= right_half.x() && dragged_bounds_start < right_half.right()) { index = i + 1; break; } else if (dragged_bounds_start >= left_half.x() && dragged_bounds_start < left_half.right()) { index = i; break; } } if (index == -1) { if (dragged_bounds_end > right_tab_x) index = attached_tabstrip_->GetTabCount(); else index = 0; } TabGtk* tab = GetTabMatchingDraggedContents( attached_tabstrip_, drag_data_->GetSourceWebContents()); if (tab == NULL) { // If dragged tabs are not attached yet, we don't need to constrain the // index. return index; } int max_index = attached_tabstrip_-> GetTabCount() - static_cast(drag_data_->size()); return std::max(0, std::min(max_index, index)); } gfx::Point DraggedTabControllerGtk::GetDraggedViewPoint( const gfx::Point& screen_point) { int x = screen_point.x() - dragged_view_->GetWidthInTabStripUpToMousePointer(); int y = screen_point.y() - mouse_offset_.y(); // If we're not attached, we just use x and y from above. if (attached_tabstrip_) { gfx::Rect tabstrip_bounds = ui::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get()); // Snap the dragged tab to the tabstrip if we are attached, detaching // only when the mouse position (screen_point) exceeds the screen bounds // of the tabstrip. if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x()) x = tabstrip_bounds.x(); gfx::Size tab_size = dragged_view_->attached_tab_size(); int vertical_drag_magnetism = tab_size.height() * 2; int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism; if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point) y = tabstrip_bounds.y(); // Make sure the tab can't be dragged off the right side of the tabstrip // unless the mouse pointer passes outside the bounds of the strip by // clamping the position of the dragged window to the tabstrip width less // the width of one tab until the mouse pointer (screen_point) exceeds the // screen bounds of the tabstrip. int max_x = tabstrip_bounds.right() - dragged_view_->GetTotalWidthInTabStrip(); int max_y = tabstrip_bounds.bottom() - tab_size.height(); if (x > max_x && screen_point.x() <= tabstrip_bounds.right()) x = max_x; if (y > max_y && screen_point.y() <= (tabstrip_bounds.bottom() + vertical_drag_magnetism)) { y = max_y; } } return gfx::Point(x, y); } int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const { if (index >= attached_tabstrip_->model_->count()) return attached_tabstrip_->model_->count() - 1; if (index == TabStripModel::kNoTab) return 0; return index; } TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents( TabStripGtk* tabstrip, WebContents* web_contents) { int index = tabstrip->model()->GetIndexOfWebContents(web_contents); return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); } std::vector DraggedTabControllerGtk::GetTabsMatchingDraggedContents( TabStripGtk* tabstrip) { std::vector dragged_tabs; for (size_t i = 0; i < drag_data_->size(); ++i) { if (!drag_data_->get(i)->contents_) continue; TabGtk* tab = GetTabMatchingDraggedContents(tabstrip, drag_data_->get(i)->contents_); if (tab) dragged_tabs.push_back(tab); } return dragged_tabs; } void DraggedTabControllerGtk::SetDraggedTabsVisible(bool visible, bool repaint) { std::vector dragged_tabs(GetTabsMatchingDraggedContents( attached_tabstrip_)); for (size_t i = 0; i < dragged_tabs.size(); ++i) { dragged_tabs[i]->SetVisible(visible); dragged_tabs[i]->set_dragging(!visible); if (repaint) dragged_tabs[i]->SchedulePaint(); } } bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) { bring_to_front_timer_.Stop(); // WARNING: this may be invoked multiple times. In particular, if deletion // occurs after a delay (as it does when the tab is released in the original // tab strip) and the navigation controller/tab contents is deleted before // the animation finishes, this is invoked twice. The second time through // type == TAB_DESTROYED. bool destroy_now = true; if (type != TAB_DESTROYED) { // If we never received a drag-motion event, the drag will never have // started in the sense that |dragged_view_| will be NULL. We don't need to // revert or complete the drag in that case. if (dragged_view_.get()) { if (type == CANCELED) { RevertDrag(); } else { destroy_now = CompleteDrag(); } } } else if (drag_data_->size() > 0) { RevertDrag(); } ResetDelegates(); // If we're not destroyed now, we'll be destroyed asynchronously later. if (destroy_now) source_tabstrip_->DestroyDragController(); return destroy_now; } void DraggedTabControllerGtk::RevertDrag() { // We save this here because code below will modify |attached_tabstrip_|. bool restore_window = attached_tabstrip_ != source_tabstrip_; if (attached_tabstrip_) { if (attached_tabstrip_ != source_tabstrip_) { // The tabs were inserted into another tabstrip. We need to put them back // into the original one. for (size_t i = 0; i < drag_data_->size(); ++i) { if (!drag_data_->get(i)->contents_) continue; int index = attached_tabstrip_->model()->GetIndexOfWebContents( drag_data_->get(i)->contents_); attached_tabstrip_->model()->DetachWebContentsAt(index); } // TODO(beng): (Cleanup) seems like we should use Attach() for this // somehow. attached_tabstrip_ = source_tabstrip_; for (size_t i = 0; i < drag_data_->size(); ++i) { if (!drag_data_->get(i)->contents_) continue; attached_tabstrip_->model()->InsertWebContentsAt( drag_data_->get(i)->source_model_index_, drag_data_->get(i)->contents_, drag_data_->GetAddTypesForDraggedTabAt(i)); } } else { // The tabs were moved within the tabstrip where the drag was initiated. // Move them back to their starting locations. for (size_t i = 0; i < drag_data_->size(); ++i) { if (!drag_data_->get(i)->contents_) continue; int index = attached_tabstrip_->model()->GetIndexOfWebContents( drag_data_->get(i)->contents_); source_tabstrip_->model()->MoveWebContentsAt( index, drag_data_->get(i)->source_model_index_, true); } } } else { // TODO(beng): (Cleanup) seems like we should use Attach() for this // somehow. attached_tabstrip_ = source_tabstrip_; // The tab was detached from the tabstrip where the drag began, and has not // been attached to any other tabstrip. We need to put it back into the // source tabstrip. for (size_t i = 0; i < drag_data_->size(); ++i) { if (!drag_data_->get(i)->contents_) continue; source_tabstrip_->model()->InsertWebContentsAt( drag_data_->get(i)->source_model_index_, drag_data_->get(i)->contents_, drag_data_->GetAddTypesForDraggedTabAt(i)); } } RestoreSelection(source_tabstrip_->model()); // If we're not attached to any tab strip, or attached to some other tab // strip, we need to restore the bounds of the original tab strip's frame, in // case it has been hidden. if (restore_window) ShowWindow(); SetDraggedTabsVisible(true, false); } bool DraggedTabControllerGtk::CompleteDrag() { bool destroy_immediately = true; if (attached_tabstrip_) { // We don't need to do anything other than make the tabs visible again, // since the dragged view is going away. dragged_view_->AnimateToBounds(GetAnimateBounds(), base::Bind(&DraggedTabControllerGtk::OnAnimateToBoundsComplete, base::Unretained(this))); destroy_immediately = false; } else { // Compel the model to construct a new window for the detached // WebContentses. BrowserWindowGtk* window = source_tabstrip_->window(); gfx::Rect window_bounds = window->GetRestoredBounds(); window_bounds.set_origin(GetWindowCreatePoint()); std::vector contentses; for (size_t i = 0; i < drag_data_->size(); ++i) { TabStripModelDelegate::NewStripContents item; item.web_contents = drag_data_->get(i)->contents_; item.add_types = drag_data_->GetAddTypesForDraggedTabAt(i); contentses.push_back(item); }; Browser* new_browser = source_tabstrip_->model()->delegate()->CreateNewStripWithContents( contentses, window_bounds, dock_info_, window->IsMaximized()); RestoreSelection(new_browser->tab_strip_model()); new_browser->window()->Show(); CleanUpHiddenFrame(); } return destroy_immediately; } void DraggedTabControllerGtk::ResetDelegates() { for (size_t i = 0; i < drag_data_->size(); ++i) { if (drag_data_->get(i)->contents_ && drag_data_->get(i)->contents_->GetDelegate() == this) { drag_data_->get(i)->ResetDelegate(); } } } void DraggedTabControllerGtk::EnsureDraggedView() { if (!dragged_view_.get()) { gfx::Rect rect; drag_data_->GetSourceWebContents()->GetView()->GetContainerBounds(&rect); dragged_view_.reset(new DraggedViewGtk(drag_data_.get(), mouse_offset_, rect.size())); } } gfx::Rect DraggedTabControllerGtk::GetAnimateBounds() { // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't // update its allocation until after the widget is shown, so we have to use // the tab bounds we keep track of. // // We use the requested bounds instead of the allocation because the // allocation is relative to the first windowed widget ancestor of the tab. // Because of this, we can't use the tabs allocation to get the screen bounds. std::vector tabs = GetTabsMatchingDraggedContents( attached_tabstrip_); TabGtk* tab = !base::i18n::IsRTL() ? tabs.front() : tabs.back(); gfx::Rect bounds = tab->GetRequisition(); GtkWidget* widget = tab->widget(); GtkWidget* parent = gtk_widget_get_parent(widget); bounds.Offset(ui::GetWidgetScreenOffset(parent)); return gfx::Rect(bounds.x(), bounds.y(), dragged_view_->GetTotalWidthInTabStrip(), bounds.height()); } void DraggedTabControllerGtk::HideWindow() { GtkWidget* tabstrip = source_tabstrip_->widget(); GtkWindow* window = platform_util::GetTopLevel(tabstrip); gtk_widget_hide(GTK_WIDGET(window)); } void DraggedTabControllerGtk::ShowWindow() { GtkWidget* tabstrip = source_tabstrip_->widget(); GtkWindow* window = platform_util::GetTopLevel(tabstrip); gtk_window_present(window); } void DraggedTabControllerGtk::CleanUpHiddenFrame() { // If the model we started dragging from is now empty, we must ask the // delegate to close the frame. if (source_tabstrip_->model()->empty()) source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession(); } void DraggedTabControllerGtk::CleanUpDraggedTabs() { // If we were attached to the source tabstrip, dragged tabs will be in use. If // we were detached or attached to another tabstrip, we can safely remove // them and delete them now. if (attached_tabstrip_ != source_tabstrip_) { for (size_t i = 0; i < drag_data_->size(); ++i) { if (drag_data_->get(i)->contents_) { registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source(drag_data_->get(i)->contents_)); } source_tabstrip_->DestroyDraggedTab(drag_data_->get(i)->tab_); drag_data_->get(i)->tab_ = NULL; } } } void DraggedTabControllerGtk::OnAnimateToBoundsComplete() { // Sometimes, for some reason, in automation we can be called back on a // detach even though we aren't attached to a tabstrip. Guard against that. if (attached_tabstrip_) SetDraggedTabsVisible(true, true); CleanUpHiddenFrame(); if (!in_destructor_) source_tabstrip_->DestroyDragController(); } void DraggedTabControllerGtk::BringWindowUnderMouseToFront() { // If we're going to dock to another window, bring it to the front. gfx::NativeWindow window = dock_info_.window(); if (!window) { gfx::NativeView dragged_tab = dragged_view_->widget(); dock_windows_.insert(dragged_tab); window = DockInfo::GetLocalProcessWindowAtPoint( chrome::HOST_DESKTOP_TYPE_NATIVE, gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(), dock_windows_); dock_windows_.erase(dragged_tab); } if (window) gtk_window_present(GTK_WINDOW(window)); } bool DraggedTabControllerGtk::AreTabsConsecutive() { for (size_t i = 1; i < drag_data_->size(); ++i) { if (drag_data_->get(i - 1)->source_model_index_ + 1 != drag_data_->get(i)->source_model_index_) { return false; } } return true; }