// Copyright (c) 2011 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/callback.h" #include "base/i18n/rtl.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/tabs/tab_strip_model.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_tab_gtk.h" #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_source.h" 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 } // namespace DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab, TabStripGtk* source_tabstrip) : dragged_contents_(NULL), original_delegate_(NULL), source_tab_(source_tab), source_tabstrip_(source_tabstrip), source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)), attached_tabstrip_(source_tabstrip), in_destructor_(false), last_move_screen_x_(0), mini_(source_tabstrip->model()->IsMiniTab(source_model_index_)), pinned_(source_tabstrip->model()->IsTabPinned(source_model_index_)) { SetDraggedContents( source_tabstrip_->model()->GetTabContentsAt(source_model_index_)); } DraggedTabControllerGtk::~DraggedTabControllerGtk() { in_destructor_ = true; CleanUpSourceTab(); // 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. dragged_tab_.reset(); SetDraggedContents(NULL); } void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) { start_screen_point_ = GetCursorScreenPoint(); mouse_offset_ = mouse_offset; } void DraggedTabControllerGtk::Drag() { if (!source_tab_ || !dragged_contents_) return; bring_to_front_timer_.Stop(); EnsureDraggedTab(); // Before we get to dragging anywhere, ensure that we consider ourselves // attached to the source tabstrip. if (source_tab_->IsVisible()) { Attach(source_tabstrip_, gfx::Point()); } if (!source_tab_->IsVisible()) { // TODO(jhawkins): Save focus. ContinueDragging(); } } bool DraggedTabControllerGtk::EndDrag(bool canceled) { return EndDragImpl(canceled ? CANCELED : NORMAL); } TabGtk* DraggedTabControllerGtk::GetDragSourceTabForContents( TabContents* contents) const { if (attached_tabstrip_ == source_tabstrip_) return contents == dragged_contents_->tab_contents() ? source_tab_ : NULL; return NULL; } bool DraggedTabControllerGtk::IsDragSourceTab(const TabGtk* tab) const { return source_tab_ == tab; } bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) const { if (!IsDragSourceTab(tab)) return false; return (attached_tabstrip_ == NULL); } //////////////////////////////////////////////////////////////////////////////// // DraggedTabControllerGtk, TabContentsDelegate implementation: void DraggedTabControllerGtk::OpenURLFromTab(TabContents* source, const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) { if (original_delegate_) { if (disposition == CURRENT_TAB) disposition = NEW_WINDOW; original_delegate_->OpenURLFromTab(source, url, referrer, disposition, transition); } } void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source, unsigned changed_flags) { if (dragged_tab_.get()) dragged_tab_->Update(); } void DraggedTabControllerGtk::AddNewContents(TabContents* source, TabContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) { 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 (original_delegate_) { original_delegate_->AddNewContents(source, new_contents, disposition, initial_pos, user_gesture); } } void DraggedTabControllerGtk::ActivateContents(TabContents* contents) { // Ignored. } void DraggedTabControllerGtk::DeactivateContents(TabContents* contents) { // Ignored. } void DraggedTabControllerGtk::LoadingStateChanged(TabContents* source) { // TODO(jhawkins): It would be nice to respond to this message by changing the // screen shot in the dragged tab. if (dragged_tab_.get()) dragged_tab_->Update(); } void DraggedTabControllerGtk::CloseContents(TabContents* source) { // Theoretically could be called by a window. Should be ignored // because window.close() is ignored (usually, even though this // method gets called.) } void DraggedTabControllerGtk::MoveContents(TabContents* source, const gfx::Rect& pos) { // Theoretically could be called by a web page trying to move its // own window. Should be ignored since we're moving the window... } bool DraggedTabControllerGtk::IsPopup(const TabContents* source) const { return false; } void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source, const GURL& url) { // Ignored. } //////////////////////////////////////////////////////////////////////////////// // DraggedTabControllerGtk, NotificationObserver implementation: void DraggedTabControllerGtk::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); DCHECK(Source(source).ptr() == dragged_contents_); EndDragImpl(TAB_DESTROYED); } void DraggedTabControllerGtk::InitWindowCreatePoint() { window_create_point_.SetPoint(mouse_offset_.x(), mouse_offset_.y()); } gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const { gfx::Point cursor_point = GetCursorScreenPoint(); return gfx::Point(cursor_point.x() - window_create_point_.x(), cursor_point.y() - window_create_point_.y()); } void DraggedTabControllerGtk::SetDraggedContents( TabContentsWrapper* new_contents) { if (dragged_contents_) { registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(dragged_contents_)); if (original_delegate_) dragged_contents_->tab_contents()->set_delegate(original_delegate_); } original_delegate_ = NULL; dragged_contents_ = new_contents; if (dragged_contents_) { registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, Source(dragged_contents_)); // 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. original_delegate_ = dragged_contents_->tab_contents()->delegate(); dragged_contents_->tab_contents()->set_delegate(this); } } 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 = 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. #if defined(OS_CHROMEOS) // We don't allow detaching on chrome os. TabStripGtk* target_tabstrip = source_tabstrip_; #else TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point); #endif 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( base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, &DraggedTabControllerGtk::BringWindowUnderMouseToFront); } MoveTab(screen_point); } void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) { gfx::Point dragged_tab_point = GetDraggedTabPoint(screen_point); if (attached_tabstrip_) { TabStripModel* attached_model = attached_tabstrip_->model(); int from_index = attached_model->GetIndexOfTabContents(dragged_contents_); // 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 TabContents from one index to another. Do // this only if we have moved a minimum distance since the last reorder (to // prevent jitter). if (abs(screen_point.x() - last_move_screen_x_) > threshold) { gfx::Rect bounds = GetDraggedTabTabStripBounds(dragged_tab_point); int to_index = GetInsertionIndexForDraggedBounds(bounds, true); to_index = NormalizeIndexToAttachedTabStrip(to_index); if (from_index != to_index) { last_move_screen_x_ = screen_point.x(); attached_model->MoveTabContentsAt(from_index, to_index, true); } } } // Move the dragged tab. There are no changes to the model if we're detached. dragged_tab_->MoveTo(dragged_tab_point); } TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint( const gfx::Point& screen_point) { GtkWidget* dragged_window = dragged_tab_->widget(); dock_windows_.insert(dragged_window); gfx::NativeWindow local_window = DockInfo::GetLocalProcessWindowAtPoint(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 = gtk_util::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; InitWindowCreatePoint(); attached_tabstrip_->GenerateIdealBounds(); TabGtk* tab = GetTabMatchingDraggedContents(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 (!tab) ++tab_count; double unselected_width = 0, selected_width = 0; attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count, &unselected_width, &selected_width); int dragged_tab_width = mini_ ? TabGtk::GetMiniWidth() : static_cast(selected_width); dragged_tab_->Attach(dragged_tab_width); if (!tab) { // There is no tab in |attached_tabstrip| that corresponds to the dragged // TabContents. We must now create one. // Remove ourselves as the delegate now that the dragged TabContents is // being inserted back into a Browser. dragged_contents_->tab_contents()->set_delegate(NULL); original_delegate_ = NULL; // Return the TabContents' to normalcy. dragged_contents_->tab_contents()->set_capturing_contents(false); // 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 = GetDraggedTabTabStripBounds(screen_point); int index = GetInsertionIndexForDraggedBounds(bounds, false); attached_tabstrip_->model()->InsertTabContentsAt( index, dragged_contents_, TabStripModel::ADD_ACTIVE | (pinned_ ? TabStripModel::ADD_PINNED : 0)); tab = GetTabMatchingDraggedContents(attached_tabstrip_); } DCHECK(tab); // We should now have a tab. tab->SetVisible(false); tab->set_dragging(true); // TODO(jhawkins): Move the corresponding window to the front. } void DraggedTabControllerGtk::Detach() { // Update the Model. TabStripModel* attached_model = attached_tabstrip_->model(); int index = attached_model->GetIndexOfTabContents(dragged_contents_); if (index >= 0 && index < attached_model->count()) { // Sometimes, DetachTabContentsAt has consequences that result in // attached_tabstrip_ being set to NULL, so we need to save it first. TabStripGtk* attached_tabstrip = attached_tabstrip_; attached_model->DetachTabContentsAt(index); attached_tabstrip->SchedulePaint(); } // 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_tab_.get()) { dragged_tab_->Detach(); } // Detaching resets the delegate, but we still want to be the delegate. dragged_contents_->tab_contents()->set_delegate(this); attached_tabstrip_ = NULL; } gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint( TabStripGtk* tabstrip, const gfx::Point& screen_point) { gfx::Point tabstrip_screen_point = gtk_util::GetWidgetScreenPosition(tabstrip->tabstrip_.get()); return screen_point.Subtract(tabstrip_screen_point); } gfx::Rect DraggedTabControllerGtk::GetDraggedTabTabStripBounds( const gfx::Point& screen_point) { gfx::Point client_point = ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); gfx::Size tab_size = dragged_tab_->attached_tab_size(); return gfx::Rect(client_point.x(), client_point.y(), tab_size.width(), tab_size.height()); } int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds( const gfx::Rect& dragged_bounds, bool is_tab_attached) const { int right_tab_x = 0; int dragged_bounds_x = base::i18n::IsRTL() ? dragged_bounds.right() : dragged_bounds.x(); dragged_bounds_x = gtk_util::MirroredXCoordinate(attached_tabstrip_->widget(), dragged_bounds_x); // Divides each tab into two halves to see if the dragged tab has crossed // the halfway boundary necessary to move past the next tab. int index = -1; for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) { gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i); gfx::Rect left_half = ideal_bounds; left_half.set_width(left_half.width() / 2); gfx::Rect right_half = ideal_bounds; right_half.set_width(ideal_bounds.width() - left_half.width()); right_half.set_x(left_half.right()); right_tab_x = right_half.right(); if (dragged_bounds_x >= right_half.x() && dragged_bounds_x < right_half.right()) { index = i + 1; break; } else if (dragged_bounds_x >= left_half.x() && dragged_bounds_x < left_half.right()) { index = i; break; } } if (index == -1) { bool at_the_end = base::i18n::IsRTL() ? dragged_bounds.x() < right_tab_x : dragged_bounds.right() > right_tab_x; index = at_the_end ? attached_tabstrip_->model()->count() : 0; } index = attached_tabstrip_->model()->ConstrainInsertionIndex(index, mini_); if (is_tab_attached && mini_ && index == attached_tabstrip_->model()->IndexOfFirstNonMiniTab()) { index--; } return index; } gfx::Point DraggedTabControllerGtk::GetDraggedTabPoint( const gfx::Point& screen_point) { int x = screen_point.x() - mouse_offset_.x(); 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 = gtk_util::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_tab_->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() - tab_size.width(); 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; } #if defined(OS_CHROMEOS) // We don't allow detaching on chromeos. This restricts dragging to the // source window. x = std::min(std::max(x, tabstrip_bounds.x()), max_x); y = tabstrip_bounds.y(); #endif } 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) const { int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_); return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index); } 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 get here it means the NavigationController is going down. Don't // attempt to do any cleanup other than resetting the delegate (if we're // still the delegate). if (dragged_contents_ && dragged_contents_->tab_contents()->delegate() == this) dragged_contents_->tab_contents()->set_delegate(NULL); dragged_contents_ = NULL; } else { // If we never received a drag-motion event, the drag will never have // started in the sense that |dragged_tab_| will be NULL. We don't need to // revert or complete the drag in that case. if (dragged_tab_.get()) { if (type == CANCELED) { RevertDrag(); } else { destroy_now = CompleteDrag(); } } if (dragged_contents_ && dragged_contents_->tab_contents()->delegate() == this) dragged_contents_->tab_contents()->set_delegate(original_delegate_); } // The delegate of the dragged contents should have been reset. Unset the // original delegate so that we don't attempt to reset the delegate when // deleted. DCHECK(!dragged_contents_ || dragged_contents_->tab_contents()->delegate() != this); original_delegate_ = NULL; // 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_) { int index = attached_tabstrip_->model()->GetIndexOfTabContents( dragged_contents_); if (attached_tabstrip_ != source_tabstrip_) { // The tab was inserted into another tabstrip. We need to put it back // into the original one. attached_tabstrip_->model()->DetachTabContentsAt(index); // TODO(beng): (Cleanup) seems like we should use Attach() for this // somehow. attached_tabstrip_ = source_tabstrip_; source_tabstrip_->model()->InsertTabContentsAt( source_model_index_, dragged_contents_, TabStripModel::ADD_ACTIVE | (pinned_ ? TabStripModel::ADD_PINNED : 0)); } else { // The tab was moved within the tabstrip where the drag was initiated. // Move it back to the starting location. source_tabstrip_->model()->MoveTabContentsAt(index, 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. source_tabstrip_->model()->InsertTabContentsAt( source_model_index_, dragged_contents_, TabStripModel::ADD_ACTIVE | (pinned_ ? TabStripModel::ADD_PINNED : 0)); } // 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(); source_tab_->SetVisible(true); source_tab_->set_dragging(false); } bool DraggedTabControllerGtk::CompleteDrag() { bool destroy_immediately = true; if (attached_tabstrip_) { // We don't need to do anything other than make the tab visible again, // since the dragged tab is going away. TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_); gfx::Rect rect = GetTabScreenBounds(tab); dragged_tab_->AnimateToBounds(GetTabScreenBounds(tab), NewCallback(this, &DraggedTabControllerGtk::OnAnimateToBoundsComplete)); destroy_immediately = false; } else { // Compel the model to construct a new window for the detached TabContents. BrowserWindowGtk* window = source_tabstrip_->window(); gfx::Rect window_bounds = window->GetRestoredBounds(); window_bounds.set_origin(GetWindowCreatePoint()); Browser* new_browser = source_tabstrip_->model()->delegate()->CreateNewStripWithContents( dragged_contents_, window_bounds, dock_info_, window->IsMaximized()); TabStripModel* new_model = new_browser->tabstrip_model(); new_model->SetTabPinned(new_model->GetIndexOfTabContents(dragged_contents_), pinned_); new_browser->window()->Show(); CleanUpHiddenFrame(); } return destroy_immediately; } void DraggedTabControllerGtk::EnsureDraggedTab() { if (!dragged_tab_.get()) { gfx::Rect rect; dragged_contents_->tab_contents()->GetContainerBounds(&rect); dragged_tab_.reset(new DraggedTabGtk(dragged_contents_->tab_contents(), mouse_offset_, rect.size(), mini_)); } } gfx::Point DraggedTabControllerGtk::GetCursorScreenPoint() const { // Get default display and screen. GdkDisplay* display = gdk_display_get_default(); // Get cursor position. int x, y; gdk_display_get_pointer(display, NULL, &x, &y, NULL); return gfx::Point(x, y); } // static gfx::Rect DraggedTabControllerGtk::GetTabScreenBounds(TabGtk* tab) { // 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. gfx::Rect bounds = tab->GetRequisition(); GtkWidget* widget = tab->widget(); GtkWidget* parent = gtk_widget_get_parent(widget); gfx::Point point = gtk_util::GetWidgetScreenPosition(parent); bounds.Offset(point); return gfx::Rect(bounds.x(), bounds.y(), bounds.width(), 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::CleanUpSourceTab() { // If we were attached to the source tabstrip, source tab will be in use // as the tab. If we were detached or attached to another tabstrip, we can // safely remove this item and delete it now. if (attached_tabstrip_ != source_tabstrip_) { source_tabstrip_->DestroyDraggedSourceTab(source_tab_); source_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_) { TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_); if (tab) { tab->SetVisible(true); tab->set_dragging(false); // Paint the tab now, otherwise there may be slight flicker between the // time the dragged tab window is destroyed and we paint. tab->SchedulePaint(); } } 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_tab_->widget(); dock_windows_.insert(dragged_tab); window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(), dock_windows_); dock_windows_.erase(dragged_tab); } if (window) gtk_window_present(GTK_WINDOW(window)); }