summaryrefslogtreecommitdiffstats
path: root/chrome/browser/views/tabs/dragged_tab_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/views/tabs/dragged_tab_controller.cc')
-rw-r--r--chrome/browser/views/tabs/dragged_tab_controller.cc798
1 files changed, 798 insertions, 0 deletions
diff --git a/chrome/browser/views/tabs/dragged_tab_controller.cc b/chrome/browser/views/tabs/dragged_tab_controller.cc
new file mode 100644
index 0000000..81b5386
--- /dev/null
+++ b/chrome/browser/views/tabs/dragged_tab_controller.cc
@@ -0,0 +1,798 @@
+// Copyright (c) 2006-2008 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 <math.h>
+
+#include "chrome/browser/views/tabs/dragged_tab_controller.h"
+
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/frame_util.h"
+#include "chrome/browser/tab_contents.h"
+#include "chrome/browser/views/tabs/dragged_tab_view.h"
+#include "chrome/browser/views/tabs/hwnd_photobooth.h"
+#include "chrome/browser/views/tabs/tab.h"
+#include "chrome/browser/views/tabs/tab_strip.h"
+#include "chrome/browser/web_contents.h"
+#include "chrome/views/event.h"
+#include "chrome/views/root_view.h"
+#include "skia/include/SkBitmap.h"
+
+static const int kHorizontalMoveThreshold = 16; // pixels
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+// WindowFinder
+// A WindowForPoint facility that can ignore 2 provided window HWNDs.
+//
+class WindowFinder {
+ public:
+ static HWND WindowForPoint(const gfx::Point& screen_point, HWND ignore1) {
+ WindowFinder instance(screen_point, ignore1);
+ return instance.GetResult();
+ }
+ private:
+ WindowFinder(const gfx::Point& screen_point, HWND ignore1)
+ : screen_point_(screen_point.ToPOINT()),
+ ignore1_(ignore1),
+ result_(NULL) {
+ }
+
+ static BOOL CALLBACK WindowEnumProc(HWND hwnd, LPARAM lParam) {
+ WindowFinder* wf = reinterpret_cast<WindowFinder*>(lParam);
+ if (hwnd == wf->ignore1_)
+ return true;
+
+ if (::IsWindowVisible(hwnd)) {
+ CRect r;
+ ::GetWindowRect(hwnd, &r);
+ if (r.PtInRect(wf->screen_point_)) {
+ // We always deal with the root HWND.
+ wf->result_ = GetAncestor(hwnd, GA_ROOT);
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+
+ HWND GetResult() {
+ EnumThreadWindows(GetCurrentThreadId(), WindowEnumProc,
+ reinterpret_cast<LPARAM>(this));
+ return result_;
+ }
+
+ POINT screen_point_;
+ HWND ignore1_;
+ HWND result_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(WindowFinder);
+};
+
+gfx::Point ConvertScreenPointToTabStripPoint(TabStrip* tabstrip,
+ const gfx::Point& screen_point) {
+ CPoint tabstrip_topleft(0, 0);
+ ChromeViews::View::ConvertPointToScreen(tabstrip, &tabstrip_topleft);
+ return gfx::Point(screen_point.x() - tabstrip_topleft.x,
+ screen_point.y() - tabstrip_topleft.y);
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, public:
+
+DraggedTabController::DraggedTabController(Tab* source_tab,
+ TabStrip* 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),
+ old_focused_view_(NULL),
+ in_destructor_(false),
+ last_move_screen_x_(0) {
+ ChangeDraggedContents(
+ source_tabstrip_->model()->GetTabContentsAt(source_model_index_));
+ // Listen for Esc key presses.
+ MessageLoopForUI::current()->AddObserver(this);
+}
+
+DraggedTabController::~DraggedTabController() {
+ in_destructor_ = true;
+ CleanUpSourceTab();
+ MessageLoopForUI::current()->RemoveObserver(this);
+ ChangeDraggedContents(NULL); // This removes our observer.
+}
+
+void DraggedTabController::CaptureDragInfo(const gfx::Point& mouse_offset) {
+ start_screen_point_ = GetCursorScreenPoint();
+ mouse_offset_ = mouse_offset;
+}
+
+void DraggedTabController::Drag() {
+ // Before we get to dragging anywhere, ensure that we consider ourselves
+ // attached to the source tabstrip.
+ if (source_tab_->IsVisible() && CanStartDrag())
+ Attach(source_tabstrip_, gfx::Point());
+
+ if (!source_tab_->IsVisible()) {
+ SaveFocus();
+ ContinueDragging();
+ }
+}
+
+void DraggedTabController::EndDrag(bool canceled) {
+ EndDragImpl(canceled ? CANCELED : NORMAL);
+}
+
+Tab* DraggedTabController::GetDragSourceTabForContents(
+ TabContents* contents) const {
+ if (attached_tabstrip_ == source_tabstrip_)
+ return contents == dragged_contents_ ? source_tab_ : NULL;
+ return NULL;
+}
+
+bool DraggedTabController::IsDragSourceTab(Tab* tab) const {
+ return source_tab_ == tab;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, PageNavigator implementation:
+
+void DraggedTabController::OpenURLFromTab(
+ TabContents* source,
+ const GURL& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition,
+ const std::string& override_encoding) {
+ if (original_delegate_) {
+ if (disposition == CURRENT_TAB)
+ disposition = NEW_WINDOW;
+
+ original_delegate_->OpenURLFromTab(source, url, disposition, transition,
+ override_encoding);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, TabContentsDelegate implementation:
+
+void DraggedTabController::NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) {
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::ReplaceContents(TabContents* source,
+ TabContents* new_contents) {
+ DCHECK(dragged_contents_ == source);
+ source->set_delegate(NULL);
+ new_contents->set_delegate(this);
+
+ // If we're attached to a TabStrip, we need to tell the TabStrip that this
+ // TabContents was replaced.
+ if (attached_tabstrip_ && attached_tabstrip_->model() && dragged_contents_) {
+ int index =
+ attached_tabstrip_->model()->GetIndexOfTabContents(dragged_contents_);
+ if (index != TabStripModel::kNoTab)
+ attached_tabstrip_->model()->ReplaceTabContentsAt(index, new_contents);
+ }
+
+ // Update our internal state.
+ ChangeDraggedContents(new_contents);
+
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ // 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 (disposition == CURRENT_TAB) {
+ ReplaceContents(source, new_contents);
+ } else if (original_delegate_) {
+ original_delegate_->AddNewContents(source, new_contents, disposition,
+ initial_pos, user_gesture);
+ }
+}
+
+void DraggedTabController::ActivateContents(TabContents* contents) {
+ // Ignored.
+}
+
+void DraggedTabController::LoadingStateChanged(TabContents* source) {
+ // It would be nice to respond to this message by changing the
+ // screen shot in the dragged tab.
+ if (view_.get())
+ view_->Update();
+}
+
+void DraggedTabController::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 DraggedTabController::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 DraggedTabController::IsPopup(TabContents* source) {
+ return false;
+}
+
+void DraggedTabController::ToolbarSizeChanged(TabContents* source,
+ bool finished) {
+ // Dragged tabs don't care about this.
+}
+
+void DraggedTabController::URLStarredChanged(TabContents* source,
+ bool starred) {
+ // Ignored.
+}
+
+void DraggedTabController::UpdateTargetURL(TabContents* source,
+ const GURL& url) {
+ // Ignored.
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, NotificationObserver implementation:
+
+void DraggedTabController::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFY_TAB_CONTENTS_DESTROYED);
+ DCHECK(Source<TabContents>(source).ptr() == dragged_contents_);
+ EndDragImpl(TAB_DESTROYED);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, MessageLoop::Observer implementation:
+
+void DraggedTabController::WillProcessMessage(const MSG& msg) {
+}
+
+void DraggedTabController::DidProcessMessage(const MSG& msg) {
+ // If the user presses ESC during a drag, we need to abort and revert things
+ // to the way they were. This is the most reliable way to do this since no
+ // single view or window reliably receives events throughout all the various
+ // kinds of tab dragging.
+ if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
+ EndDrag(true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DraggedTabController, private:
+
+void DraggedTabController::InitWindowCreatePoint() {
+ CPoint mouse_offset_cpoint(mouse_offset_.x(), mouse_offset_.y());
+ Tab* first_tab = attached_tabstrip_->GetTabAt(0);
+ ChromeViews::View::ConvertPointToViewContainer(first_tab,
+ &mouse_offset_cpoint);
+ window_create_point_.SetPoint(mouse_offset_cpoint.x, mouse_offset_cpoint.y);
+}
+
+gfx::Point DraggedTabController::GetWindowCreatePoint() const {
+ POINT pt;
+ GetCursorPos(&pt);
+ return gfx::Point(pt.x - window_create_point_.x(),
+ pt.y - window_create_point_.y());
+}
+
+void DraggedTabController::ChangeDraggedContents(TabContents* new_contents) {
+ if (dragged_contents_) {
+ NotificationService::current()->RemoveObserver(this,
+ NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(dragged_contents_));
+ }
+ dragged_contents_ = new_contents;
+ if (dragged_contents_) {
+ NotificationService::current()->AddObserver(this,
+ NOTIFY_TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(dragged_contents_));
+ }
+}
+
+void DraggedTabController::SaveFocus() {
+ if (!old_focused_view_) {
+ old_focused_view_ = source_tab_->GetRootView()->GetFocusedView();
+ source_tab_->GetRootView()->FocusView(source_tab_);
+ }
+}
+
+void DraggedTabController::RestoreFocus() {
+ if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
+ old_focused_view_->GetRootView()->FocusView(old_focused_view_);
+ old_focused_view_ = NULL;
+}
+
+bool DraggedTabController::CanStartDrag() const {
+ // Determine if the mouse has moved beyond a minimum elasticity distance in
+ // any direction from the starting point.
+ static const int kMinimumDragDistance = 10;
+ gfx::Point screen_point = GetCursorScreenPoint();
+ int x_offset = abs(screen_point.x() - start_screen_point_.x());
+ int y_offset = abs(screen_point.y() - start_screen_point_.y());
+ return sqrt(pow(static_cast<float>(x_offset), 2) +
+ pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
+}
+
+void DraggedTabController::ContinueDragging() {
+ EnsureDraggedView();
+
+ // Note that the coordinates given to us by |drag_event| are basically
+ // useless, since they're in source_tab_ coordinates. On the surface, you'd
+ // think we could just convert them to screen coordinates, however in the
+ // situation where we're dragging the last tab in a window when multiple
+ // windows are open, the coordinates of |source_tab_| are way off in
+ // hyperspace since the window was moved there instead of being closed so
+ // that we'd keep receiving events. And our ConvertPointToScreen methods
+ // aren't really multi-screen aware. So really it's just safer to get the
+ // actual position of the mouse cursor directly from Windows here, which is
+ // guaranteed to be correct regardless of monitor config.
+ 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.
+ TabStrip* target_tabstrip = GetTabStripForPoint(screen_point);
+ if (target_tabstrip != attached_tabstrip_) {
+ if (target_tabstrip) {
+ // We may receive this event before we're fully detached from the source,
+ // we check for that and force a detach now.
+ if (attached_tabstrip_)
+ Detach();
+ Attach(target_tabstrip, screen_point);
+ } else {
+ Detach();
+ }
+ }
+ MoveTab(screen_point);
+}
+
+void DraggedTabController::MoveTab(const gfx::Point& screen_point) {
+ gfx::Point dragged_view_point = GetDraggedViewPoint(screen_point);
+
+ if (attached_tabstrip_) {
+ // 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 / Tab::GetStandardSize().width();
+ int threshold = static_cast<int>(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) {
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ int from_index =
+ attached_model->GetIndexOfTabContents(dragged_contents_);
+ gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
+ int to_index = GetInsertionIndexForDraggedBounds(bounds);
+ to_index = NormalizeIndexToAttachedTabStrip(to_index);
+ if (from_index != to_index) {
+ last_move_screen_x_ = screen_point.x();
+ attached_model->MoveTabContentsAt(from_index, to_index);
+ }
+ }
+ }
+ // Move the View. There are no changes to the model if we're detached.
+ view_->MoveTo(dragged_view_point);
+}
+
+TabStrip* DraggedTabController::GetTabStripForPoint(
+ const gfx::Point& screen_point) const {
+ HWND dragged_hwnd = view_->GetViewContainer()->GetHWND();
+ HWND other_hwnd = WindowFinder::WindowForPoint(screen_point, dragged_hwnd);
+ if (!other_hwnd)
+ return NULL;
+
+ BrowserWindow* other_frame = FrameUtil::GetBrowserWindowForHWND(other_hwnd);
+ if (other_frame) {
+ TabStrip* other_tabstrip = other_frame->GetTabStrip();
+ if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
+ return NULL;
+ return GetTabStripIfItContains(other_tabstrip, screen_point);
+ }
+ return NULL;
+}
+
+TabStrip* DraggedTabController::GetTabStripIfItContains(
+ TabStrip* tabstrip, const gfx::Point& screen_point) const {
+ static const int kVerticalDetachMagnetism = 15;
+ // Make sure the specified screen point is actually within the bounds of the
+ // specified tabstrip...
+ gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
+ 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 DraggedTabController::Attach(TabStrip* attached_tabstrip,
+ const gfx::Point& screen_point) {
+ attached_tabstrip_ = attached_tabstrip;
+ InitWindowCreatePoint();
+ attached_tabstrip_->GenerateIdealBounds();
+
+ // We don't need the photo-booth while we're attached.
+ photobooth_.reset(NULL);
+
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+
+ // Update the View 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();
+ if (!tab)
+ ++tab_count;
+ double unselected_width, selected_width = 0;
+ attached_tabstrip_->GetDesiredTabWidths(tab_count, &unselected_width,
+ &selected_width);
+ EnsureDraggedView();
+ view_->Attach(static_cast<int>(selected_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_->set_delegate(NULL);
+ original_delegate_ = NULL;
+
+ // Return the TabContents' to normalcy.
+ dragged_contents_->DidCaptureContents();
+
+ // We need to ask the TabStrip we're attached to 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(screen_point);
+ int index = GetInsertionIndexForDraggedBounds(bounds);
+ index = std::max(std::min(index, attached_tabstrip_->model()->count()), 0);
+ attached_tabstrip_->model()->InsertTabContentsAt(index, dragged_contents_,
+ true, false);
+
+ tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ }
+ DCHECK(tab); // We should now have a tab.
+ tab->SetVisible(false);
+
+ // Move the corresponding window to the front.
+ attached_tabstrip_->GetViewContainer()->MoveToFront(true);
+}
+
+void DraggedTabController::Detach() {
+ // Prevent the TabContents' HWND from being hidden by any of the model
+ // operations performed during the drag.
+ dragged_contents_->WillCaptureContents();
+
+ // Update the Model.
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ int index = attached_model->GetIndexOfTabContents(dragged_contents_);
+ if (index >= 0 && index < attached_model->count()) {
+ 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())
+ HideFrame();
+
+ // Set up the photo booth to start capturing the contents of the dragged
+ // TabContents.
+ if (!photobooth_.get())
+ photobooth_.reset(new HWNDPhotobooth(dragged_contents_->GetContainerHWND()));
+
+ // Update the View.
+ view_->Detach(photobooth_.get());
+
+ // 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_->delegate();
+ dragged_contents_->set_delegate(this);
+
+ attached_tabstrip_ = NULL;
+}
+
+int DraggedTabController::GetInsertionIndexForDraggedBounds(
+ const gfx::Rect& dragged_bounds) const {
+ int right_tab_x = 0;
+
+ // If the UI layout of the tab strip is right-to-left, we need to mirror the
+ // bounds of the dragged tab before performing the drag/drop related
+ // calculations. We mirror the dragged bounds because we determine the
+ // position of each tab on the tab strip by calling GetBounds() (without the
+ // mirroring transformation flag) which effectively means that even though
+ // the tabs are rendered from right to left, the code performs the
+ // calculation as if the tabs are laid out from left to right. Mirroring the
+ // dragged bounds adjusts the coordinates of the tab we are dragging so that
+ // it uses the same orientation used by the tabs on the tab strip.
+ gfx::Rect adjusted_bounds(dragged_bounds);
+ adjusted_bounds.set_x(
+ attached_tabstrip_->MirroredLeftPointForRect(adjusted_bounds));
+
+ 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 (adjusted_bounds.x() >= right_half.x() &&
+ adjusted_bounds.x() < right_half.right()) {
+ return i + 1;
+ } else if (adjusted_bounds.x() >= left_half.x() &&
+ adjusted_bounds.x() < left_half.right()) {
+ return i;
+ }
+ }
+ if (adjusted_bounds.right() > right_tab_x)
+ return attached_tabstrip_->model()->count();
+ return TabStripModel::kNoTab;
+}
+
+gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds(
+ const gfx::Point& screen_point) {
+ gfx::Point client_point =
+ ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
+ gfx::Size view_size = view_->attached_tab_size();
+ return gfx::Rect(client_point.x(), client_point.y(),
+ view_size.width(), view_size.height());
+}
+
+gfx::Point DraggedTabController::GetDraggedViewPoint(
+ 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 = GetViewScreenBounds(attached_tabstrip_);
+ // 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 = 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() - 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;
+ }
+ }
+ return gfx::Point(x, y);
+}
+
+
+Tab* DraggedTabController::GetTabMatchingDraggedContents(
+ TabStrip* tabstrip) const {
+ int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_);
+ return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
+}
+
+void DraggedTabController::EndDragImpl(EndDragType type) {
+ bool destroy_now = true;
+ if (type != TAB_DESTROYED) {
+ // We only finish up the drag if we were actually dragging. If we never
+ // constructed a view, the user just clicked and released and didn't move the
+ // mouse enough to trigger a drag.
+ if (view_.get()) {
+ RestoreFocus();
+ if (type == CANCELED) {
+ RevertDrag();
+ } else {
+ destroy_now = CompleteDrag();
+ }
+ }
+ } else {
+ // 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_->delegate() == this)
+ dragged_contents_->set_delegate(NULL);
+ dragged_contents_ = NULL;
+ attached_tabstrip_ = NULL;
+ }
+ // If we're not destroyed now, we'll be destroyed asynchronously later.
+ if (destroy_now)
+ source_tabstrip_->DestroyDragController();
+}
+
+void DraggedTabController::RevertDrag() {
+ // We save this here because code below will modify |attached_tabstrip_|.
+ bool restore_frame = 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_, true, false);
+ } 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_);
+ }
+ } 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_, true, false);
+ }
+ // If we're not attached to any TabStrip, or attached to some other TabStrip,
+ // we need to restore the bounds of the original TabStrip's frame, in case
+ // it has been hidden.
+ if (restore_frame) {
+ if (!restore_bounds_.IsEmpty()) {
+ HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
+ MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(),
+ restore_bounds_.width(), restore_bounds_.height(), TRUE);
+ }
+ }
+ source_tab_->SetVisible(true);
+}
+
+bool DraggedTabController::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 View is going away.
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ view_->AnimateToBounds(
+ GetViewScreenBounds(tab),
+ NewCallback(this, &DraggedTabController::OnAnimateToBoundsComplete));
+ destroy_immediately = false;
+ } else {
+ // Compel the model to construct a new window for the detached TabContents.
+ source_tabstrip_->model()->TearOffTabContents(
+ dragged_contents_,
+ GetWindowCreatePoint());
+ CleanUpHiddenFrame();
+ }
+
+ return destroy_immediately;
+}
+
+void DraggedTabController::EnsureDraggedView() {
+ if (!view_.get()) {
+ RECT wr;
+ GetWindowRect(dragged_contents_->GetContainerHWND(), &wr);
+
+ view_.reset(new DraggedTabView(dragged_contents_, mouse_offset_,
+ gfx::Size(wr.right - wr.left, wr.bottom - wr.top)));
+ }
+}
+
+gfx::Point DraggedTabController::GetCursorScreenPoint() const {
+ POINT pt;
+ GetCursorPos(&pt);
+ return gfx::Point(pt);
+}
+
+gfx::Rect DraggedTabController::GetViewScreenBounds(
+ ChromeViews::View* view) const {
+ CPoint view_topleft(0, 0);
+ ChromeViews::View::ConvertPointToScreen(view, &view_topleft);
+ CRect view_screen_bounds;
+ view->GetLocalBounds(&view_screen_bounds, true);
+ view_screen_bounds.OffsetRect(view_topleft);
+ return gfx::Rect(view_screen_bounds);
+}
+
+int DraggedTabController::NormalizeIndexToAttachedTabStrip(int index) const {
+ DCHECK(attached_tabstrip_) << "Can only be called when attached!";
+ TabStripModel* attached_model = attached_tabstrip_->model();
+ if (index >= attached_model->count())
+ return attached_model->count() - 1;
+ if (index == TabStripModel::kNoTab)
+ return 0;
+ return index;
+}
+
+void DraggedTabController::HideFrame() {
+ // We don't actually hide the window, rather we just move it way off-screen.
+ // If we actually hide it, we stop receiving drag events.
+ HWND frame_hwnd = source_tabstrip_->GetViewContainer()->GetHWND();
+ RECT wr;
+ GetWindowRect(frame_hwnd, &wr);
+ MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left,
+ wr.bottom - wr.top, TRUE);
+
+ // We also save the bounds of the window prior to it being moved, so that if
+ // the drag session is aborted we can restore them.
+ restore_bounds_ = gfx::Rect(wr);
+}
+
+void DraggedTabController::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 DraggedTabController::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 DraggedTabController::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_) {
+ Tab* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
+ if (tab)
+ tab->SetVisible(true);
+ }
+ CleanUpHiddenFrame();
+
+ if (!in_destructor_)
+ source_tabstrip_->DestroyDragController();
+}
+