// 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/views/tabs/dragged_tab_controller.h"
#include <math.h>
#include <set>
#include "base/callback.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/base_tab.h"
#include "chrome/browser/ui/views/tabs/base_tab_strip.h"
#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/dragged_tab_view.h"
#include "chrome/browser/ui/views/tabs/native_view_photobooth.h"
#include "chrome/browser/ui/views/tabs/side_tab.h"
#include "chrome/browser/ui/views/tabs/side_tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "grit/theme_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/animation/animation.h"
#include "ui/base/animation/animation_delegate.h"
#include "ui/base/animation/slide_animation.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas_skia.h"
#include "views/events/event.h"
#include "views/screen.h"
#include "views/widget/root_view.h"
#include "views/widget/widget.h"
#include "views/window/window.h"
#if defined(OS_WIN)
#include "views/widget/widget_win.h"
#endif
#if defined(OS_LINUX)
#include <gdk/gdk.h> // NOLINT
#include <gdk/gdkkeysyms.h> // NOLINT
#endif
static const int kHorizontalMoveThreshold = 16; // Pixels.
// Distance in pixels the user must move the mouse before we consider moving
// an attached vertical tab.
static const int kVerticalMoveThreshold = 8;
// If non-null there is a drag underway.
static DraggedTabController* instance_;
namespace {
// Delay, in ms, during dragging before we bring a window to front.
const int kBringToFrontDelay = 750;
// Radius of the rect drawn by DockView.
const int kRoundedRectRadius = 4;
// Spacing between tab icons when DockView is showing a docking location that
// contains more than one tab.
const int kTabSpacing = 4;
// DockView is the view responsible for giving a visual indicator of where a
// dock is going to occur.
class DockView : public views::View {
public:
explicit DockView(DockInfo::Type type) : type_(type) {}
virtual gfx::Size GetPreferredSize() {
return gfx::Size(DockInfo::popup_width(), DockInfo::popup_height());
}
virtual void OnPaintBackground(gfx::Canvas* canvas) {
SkRect outer_rect = { SkIntToScalar(0), SkIntToScalar(0),
SkIntToScalar(width()),
SkIntToScalar(height()) };
// Fill the background rect.
SkPaint paint;
paint.setColor(SkColorSetRGB(108, 108, 108));
paint.setStyle(SkPaint::kFill_Style);
canvas->AsCanvasSkia()->drawRoundRect(
outer_rect, SkIntToScalar(kRoundedRectRadius),
SkIntToScalar(kRoundedRectRadius), paint);
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
SkBitmap* high_icon = rb.GetBitmapNamed(IDR_DOCK_HIGH);
SkBitmap* wide_icon = rb.GetBitmapNamed(IDR_DOCK_WIDE);
canvas->Save();
bool rtl_ui = base::i18n::IsRTL();
if (rtl_ui) {
// Flip canvas to draw the mirrored tab images for RTL UI.
canvas->TranslateInt(width(), 0);
canvas->ScaleInt(-1, 1);
}
int x_of_active_tab = width() / 2 + kTabSpacing / 2;
int x_of_inactive_tab = width() / 2 - high_icon->width() - kTabSpacing / 2;
switch (type_) {
case DockInfo::LEFT_OF_WINDOW:
case DockInfo::LEFT_HALF:
if (!rtl_ui)
std::swap(x_of_active_tab, x_of_inactive_tab);
canvas->DrawBitmapInt(*high_icon, x_of_active_tab,
(height() - high_icon->height()) / 2);
if (type_ == DockInfo::LEFT_OF_WINDOW) {
DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab,
(height() - high_icon->height()) / 2);
}
break;
case DockInfo::RIGHT_OF_WINDOW:
case DockInfo::RIGHT_HALF:
if (rtl_ui)
std::swap(x_of_active_tab, x_of_inactive_tab);
canvas->DrawBitmapInt(*high_icon, x_of_active_tab,
(height() - high_icon->height()) / 2);
if (type_ == DockInfo::RIGHT_OF_WINDOW) {
DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab,
(height() - high_icon->height()) / 2);
}
break;
case DockInfo::TOP_OF_WINDOW:
canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2,
height() / 2 - high_icon->height());
break;
case DockInfo::MAXIMIZE: {
SkBitmap* max_icon = rb.GetBitmapNamed(IDR_DOCK_MAX);
canvas->DrawBitmapInt(*max_icon, (width() - max_icon->width()) / 2,
(height() - max_icon->height()) / 2);
break;
}
case DockInfo::BOTTOM_HALF:
case DockInfo::BOTTOM_OF_WINDOW:
canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2,
height() / 2 + kTabSpacing / 2);
if (type_ == DockInfo::BOTTOM_OF_WINDOW) {
DrawBitmapWithAlpha(canvas, *wide_icon,
(width() - wide_icon->width()) / 2,
height() / 2 - kTabSpacing / 2 - wide_icon->height());
}
break;
default:
NOTREACHED();
break;
}
canvas->Restore();
}
private:
void DrawBitmapWithAlpha(gfx::Canvas* canvas, const SkBitmap& image,
int x, int y) {
SkPaint paint;
paint.setAlpha(128);
canvas->DrawBitmapInt(image, x, y, paint);
}
DockInfo::Type type_;
DISALLOW_COPY_AND_ASSIGN(DockView);
};
// Returns the the x-coordinate of |point| if the type of tabstrip is horizontal
// otherwise returns the y-coordinate.
int MajorAxisValue(const gfx::Point& point, BaseTabStrip* tabstrip) {
return (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) ?
point.x() : point.y();
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// DockDisplayer
// DockDisplayer is responsible for giving the user a visual indication of a
// possible dock position (as represented by DockInfo). DockDisplayer shows
// a window with a DockView in it. Two animations are used that correspond to
// the state of DockInfo::in_enable_area.
class DraggedTabController::DockDisplayer : public ui::AnimationDelegate {
public:
DockDisplayer(DraggedTabController* controller,
const DockInfo& info)
: controller_(controller),
popup_(NULL),
popup_view_(NULL),
ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)),
hidden_(false),
in_enable_area_(info.in_enable_area()) {
#if defined(OS_WIN)
// TODO(sky): This should "just work" on Gtk now.
views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP);
params.transparent = true;
params.keep_on_top = true;
popup_ = views::Widget::CreateWidget(params);
popup_->SetOpacity(0x00);
popup_->Init(NULL, info.GetPopupRect());
popup_->SetContentsView(new DockView(info.type()));
if (info.in_enable_area())
animation_.Reset(1);
else
animation_.Show();
popup_->Show();
#else
NOTIMPLEMENTED();
#endif
popup_view_ = popup_->GetNativeView();
}
~DockDisplayer() {
if (controller_)
controller_->DockDisplayerDestroyed(this);
}
// Updates the state based on |in_enable_area|.
void UpdateInEnabledArea(bool in_enable_area) {
if (in_enable_area != in_enable_area_) {
in_enable_area_ = in_enable_area;
UpdateLayeredAlpha();
}
}
// Resets the reference to the hosting DraggedTabController. This is invoked
// when the DraggedTabController is destoryed.
void clear_controller() { controller_ = NULL; }
// NativeView of the window we create.
gfx::NativeView popup_view() { return popup_view_; }
// Starts the hide animation. When the window is closed the
// DraggedTabController is notified by way of the DockDisplayerDestroyed
// method
void Hide() {
if (hidden_)
return;
if (!popup_) {
delete this;
return;
}
hidden_ = true;
animation_.Hide();
}
virtual void AnimationProgressed(const ui::Animation* animation) {
UpdateLayeredAlpha();
}
virtual void AnimationEnded(const ui::Animation* animation) {
if (!hidden_)
return;
#if defined(OS_WIN)
static_cast<views::WidgetWin*>(popup_)->Close();
#else
NOTIMPLEMENTED();
#endif
delete this;
}
virtual void UpdateLayeredAlpha() {
#if defined(OS_WIN)
double scale = in_enable_area_ ? 1 : .5;
static_cast<views::WidgetWin*>(popup_)->SetOpacity(
static_cast<BYTE>(animation_.GetCurrentValue() * scale * 255.0));
popup_->GetRootView()->SchedulePaint();
#else
NOTIMPLEMENTED();
#endif
}
private:
// DraggedTabController that created us.
DraggedTabController* controller_;
// Window we're showing.
views::Widget* popup_;
// NativeView of |popup_|. We cache this to avoid the possibility of
// invoking a method on popup_ after we close it.
gfx::NativeView popup_view_;
// Animation for when first made visible.
ui::SlideAnimation animation_;
// Have we been hidden?
bool hidden_;
// Value of DockInfo::in_enable_area.
bool in_enable_area_;
};
DraggedTabController::TabDragData::TabDragData()
: contents(NULL),
original_delegate(NULL),
source_model_index(-1),
attached_tab(NULL),
pinned(false) {
}
DraggedTabController::TabDragData::~TabDragData() {
}
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, public:
DraggedTabController::DraggedTabController()
: source_tabstrip_(NULL),
attached_tabstrip_(NULL),
source_tab_offset_(0),
offset_to_width_ratio_(0),
old_focused_view_(NULL),
last_move_screen_loc_(0),
started_drag_(false),
active_(true),
source_tab_index_(std::numeric_limits<size_t>::max()),
initial_move_(true) {
instance_ = this;
}
DraggedTabController::~DraggedTabController() {
if (instance_ == this)
instance_ = NULL;
MessageLoopForUI::current()->RemoveObserver(this);
// Need to delete the view 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.
view_.reset(NULL);
// Reset the delegate of the dragged TabContents. This ends up doing nothing
// if the drag was completed.
ResetDelegates();
}
void DraggedTabController::Init(BaseTabStrip* source_tabstrip,
BaseTab* source_tab,
const std::vector<BaseTab*>& tabs,
const gfx::Point& mouse_offset,
int source_tab_offset) {
DCHECK(!tabs.empty());
DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
source_tabstrip_ = source_tabstrip;
source_tab_offset_ = source_tab_offset;
start_screen_point_ = GetCursorScreenPoint();
mouse_offset_ = mouse_offset;
drag_data_.resize(tabs.size());
for (size_t i = 0; i < tabs.size(); ++i)
InitTabDragData(tabs[i], &(drag_data_[i]));
source_tab_index_ =
std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
// Listen for Esc key presses.
MessageLoopForUI::current()->AddObserver(this);
if (source_tab->width() > 0) {
offset_to_width_ratio_ = static_cast<float>(source_tab_offset_) /
static_cast<float>(source_tab->width());
}
InitWindowCreatePoint();
}
// static
bool DraggedTabController::IsAttachedTo(BaseTabStrip* tab_strip) {
return instance_ && instance_->active_ &&
instance_->attached_tabstrip_ == tab_strip;
}
void DraggedTabController::Drag() {
bring_to_front_timer_.Stop();
if (!started_drag_) {
if (!CanStartDrag())
return; // User hasn't dragged far enough yet.
started_drag_ = true;
SaveFocus();
Attach(source_tabstrip_, gfx::Point());
}
ContinueDragging();
}
void DraggedTabController::EndDrag(bool canceled) {
EndDragImpl(canceled ? CANCELED : NORMAL);
}
void DraggedTabController::InitTabDragData(BaseTab* tab,
TabDragData* drag_data) {
drag_data->source_model_index =
source_tabstrip_->GetModelIndexOfBaseTab(tab);
drag_data->contents = GetModel(source_tabstrip_)->GetTabContentsAt(
drag_data->source_model_index);
drag_data->pinned = source_tabstrip_->IsTabPinned(tab);
registrar_.Add(this,
NotificationType::TAB_CONTENTS_DESTROYED,
Source<TabContents>(drag_data->contents->tab_contents()));
// We need to be the delegate so we receive messages about stuff, otherwise
// our dragged TabContents may be replaced and subsequently
// collected/destroyed while the drag is in process, leading to nasty crashes.
drag_data->original_delegate =
drag_data->contents->tab_contents()->delegate();
drag_data->contents->tab_contents()->set_delegate(this);
}
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, PageNavigator implementation:
void DraggedTabController::OpenURLFromTab(TabContents* source,
const GURL& url,
const GURL& referrer,
WindowOpenDisposition disposition,
PageTransition::Type transition) {
if (source_tab_drag_data()->original_delegate) {
if (disposition == CURRENT_TAB)
disposition = NEW_WINDOW;
source_tab_drag_data()->original_delegate->OpenURLFromTab(
source, url, referrer, disposition, transition);
}
}
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, TabContentsDelegate implementation:
void DraggedTabController::NavigationStateChanged(const TabContents* source,
unsigned changed_flags) {
if (view_.get())
view_->Update();
}
void DraggedTabController::AddNewContents(TabContents* source,
TabContents* new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_pos,
bool user_gesture) {
DCHECK_NE(CURRENT_TAB, disposition);
// 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 (source_tab_drag_data()->original_delegate) {
source_tab_drag_data()->original_delegate->AddNewContents(
source, new_contents, disposition, initial_pos, user_gesture);
}
}
void DraggedTabController::ActivateContents(TabContents* contents) {
// Ignored.
}
void DraggedTabController::DeactivateContents(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...
}
void DraggedTabController::UpdateTargetURL(TabContents* source,
const GURL& url) {
// Ignored.
}
bool DraggedTabController::ShouldSuppressDialogs() {
// When a dialog is about to be shown we revert the drag. Otherwise a modal
// dialog might appear and attempt to parent itself to a hidden tabcontents.
EndDragImpl(CANCELED);
return false;
}
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, NotificationObserver implementation:
void DraggedTabController::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK_EQ(type.value, NotificationType::TAB_CONTENTS_DESTROYED);
TabContents* destroyed_contents = Source<TabContents>(source).ptr();
for (size_t i = 0; i < drag_data_.size(); ++i) {
if (drag_data_[i].contents->tab_contents() == destroyed_contents) {
// One of the tabs we're dragging has been destroyed. Cancel the drag.
if (destroyed_contents->delegate() == this)
destroyed_contents->set_delegate(NULL);
drag_data_[i].contents = NULL;
drag_data_[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();
}
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, MessageLoop::Observer implementation:
#if defined(OS_WIN)
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);
}
#else
void DraggedTabController::WillProcessEvent(GdkEvent* event) {
}
void DraggedTabController::DidProcessEvent(GdkEvent* event) {
if (event->type == GDK_KEY_PRESS &&
reinterpret_cast<GdkEventKey*>(event)->keyval == GDK_Escape) {
EndDrag(true);
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
// DraggedTabController, private:
void DraggedTabController::InitWindowCreatePoint() {
// window_create_point_ is only used in CompleteDrag() (through
// GetWindowCreatePoint() to get the start point of the docked window) when
// the attached_tabstrip_ is NULL and all the window's related bound
// information are obtained from source_tabstrip_. So, we need to get the
// first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise,
// the window_create_point_ is not in the correct coordinate system. Please
// refer to http://crbug.com/6223 comment #15 for detailed information.
views::View* first_tab = source_tabstrip_->base_tab_at_tab_index(0);
views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);
window_create_point_ = first_source_tab_point_;
window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());
}
gfx::Point DraggedTabController::GetWindowCreatePoint() const {
gfx::Point cursor_point = GetCursorScreenPoint();
if (dock_info_.type() != DockInfo::NONE && dock_info_.in_enable_area()) {
// If we're going to dock, we need to return the exact coordinate,
// otherwise we may attempt to maximize on the wrong monitor.
return cursor_point;
}
// If the cursor is outside the monitor area, move it inside. For example,
// dropping a tab onto the task bar on Windows produces this situation.
gfx::Rect work_area = views::Screen::GetMonitorWorkAreaNearestPoint(
cursor_point);
if (!work_area.IsEmpty()) {
if (cursor_point.x() < work_area.x())
cursor_point.set_x(work_area.x());
else if (cursor_point.x() > work_area.right())
cursor_point.set_x(work_area.right());
if (cursor_point.y() < work_area.y())
cursor_point.set_y(work_area.y());
else if (cursor_point.y() > work_area.bottom())
cursor_point.set_y(work_area.bottom());
}
return gfx::Point(cursor_point.x() - window_create_point_.x(),
cursor_point.y() - window_create_point_.y());
}
void DraggedTabController::UpdateDockInfo(const gfx::Point& screen_point) {
// Update the DockInfo for the current mouse coordinates.
DockInfo dock_info = GetDockInfoAtPoint(screen_point);
if (source_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP &&
((dock_info.type() == DockInfo::LEFT_OF_WINDOW &&
!base::i18n::IsRTL()) ||
(dock_info.type() == DockInfo::RIGHT_OF_WINDOW &&
base::i18n::IsRTL()))) {
// For side tabs it's way to easy to trigger to docking along the left/right
// edge, so we disable it.
dock_info = DockInfo();
}
if (!dock_info.equals(dock_info_)) {
// DockInfo for current position differs.
if (dock_info_.type() != DockInfo::NONE &&
!dock_controllers_.empty()) {
// Hide old visual indicator.
dock_controllers_.back()->Hide();
}
dock_info_ = dock_info;
if (dock_info_.type() != DockInfo::NONE) {
// Show new docking position.
DockDisplayer* controller = new DockDisplayer(this, dock_info_);
if (controller->popup_view()) {
dock_controllers_.push_back(controller);
dock_windows_.insert(controller->popup_view());
} else {
delete controller;
}
}
} else if (dock_info_.type() != DockInfo::NONE &&
!dock_controllers_.empty()) {
// Current dock position is the same as last, update the controller's
// in_enable_area state as it may have changed.
dock_controllers_.back()->UpdateInEnabledArea(dock_info_.in_enable_area());
}
}
void DraggedTabController::SaveFocus() {
DCHECK(!old_focused_view_); // This should only be invoked once.
old_focused_view_ = source_tabstrip_->GetFocusManager()->GetFocusedView();
source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_);
}
void DraggedTabController::RestoreFocus() {
if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
old_focused_view_->GetFocusManager()->SetFocusedView(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() {
// 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();
#if defined(OS_LINUX)
// We don't allow detaching in chrome os.
BaseTabStrip* target_tabstrip = source_tabstrip_;
#else
// 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.
BaseTabStrip* 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,
&DraggedTabController::BringWindowUnderMouseToFront);
}
UpdateDockInfo(screen_point);
if (attached_tabstrip_)
MoveAttached(screen_point);
else
MoveDetached(screen_point);
}
void DraggedTabController::MoveAttached(const gfx::Point& screen_point) {
DCHECK(attached_tabstrip_);
DCHECK(!view_.get());
gfx::Point dragged_view_point = GetAttachedDragPoint(screen_point);
int threshold = kVerticalMoveThreshold;
if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
TabStrip* tab_strip = static_cast<TabStrip*>(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;
tab_strip->GetCurrentTabWidths(&unselected, &selected);
double ratio = unselected / Tab::GetStandardSize().width();
threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
}
std::vector<BaseTab*> tabs(drag_data_.size());
for (size_t i = 0; i < drag_data_.size(); ++i)
tabs[i] = drag_data_[i].attached_tab;
bool did_layout = false;
// 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) or if this the first move and the tabs are not consecutive.
if (abs(MajorAxisValue(screen_point, attached_tabstrip_) -
last_move_screen_loc_) > threshold ||
(initial_move_ && !AreTabsConsecutive())) {
TabStripModel* attached_model = GetModel(attached_tabstrip_);
gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
int to_index = GetInsertionIndexForDraggedBounds(bounds);
TabContentsWrapper* last_contents =
drag_data_[drag_data_.size() - 1].contents;
int index_of_last_item =
attached_model->GetIndexOfTabContents(last_contents);
if (initial_move_) {
// TabStrip determines if the tabs needs to be animated based on model
// position. This means we need to invoke LayoutDraggedTabsAt before
// changing the model.
attached_tabstrip_->LayoutDraggedTabsAt(
tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
initial_move_);
did_layout = true;
}
attached_model->MoveSelectedTabsTo(to_index);
// Move may do nothing in certain situations (such as when dragging pinned
// tabs). Make sure the tabstrip actually changed before updating
// last_move_screen_loc_.
if (index_of_last_item !=
attached_model->GetIndexOfTabContents(last_contents)) {
last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip_);
}
}
if (!did_layout) {
attached_tabstrip_->LayoutDraggedTabsAt(
tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
initial_move_);
}
initial_move_ = false;
}
void DraggedTabController::MoveDetached(const gfx::Point& screen_point) {
DCHECK(!attached_tabstrip_);
DCHECK(view_.get());
// Move the View. There are no changes to the model if we're detached.
view_->MoveTo(screen_point);
}
DockInfo DraggedTabController::GetDockInfoAtPoint(
const gfx::Point& screen_point) {
if (attached_tabstrip_) {
// If the mouse is over a tab strip, don't offer a dock position.
return DockInfo();
}
if (dock_info_.IsValidForPoint(screen_point)) {
// It's possible any given screen coordinate has multiple docking
// positions. Check the current info first to avoid having the docking
// position bounce around.
return dock_info_;
}
gfx::NativeView dragged_hwnd = view_->GetWidget()->GetNativeView();
dock_windows_.insert(dragged_hwnd);
DockInfo info = DockInfo::GetDockInfoAtPoint(screen_point, dock_windows_);
dock_windows_.erase(dragged_hwnd);
return info;
}
BaseTabStrip* DraggedTabController::GetTabStripForPoint(
const gfx::Point& screen_point) {
gfx::NativeView dragged_view = NULL;
if (view_.get()) {
dragged_view = view_->GetWidget()->GetNativeView();
dock_windows_.insert(dragged_view);
}
gfx::NativeWindow local_window =
DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_);
if (dragged_view)
dock_windows_.erase(dragged_view);
if (!local_window)
return NULL;
BrowserView* browser =
BrowserView::GetBrowserViewForNativeWindow(local_window);
// We don't allow drops on windows that don't have tabstrips.
if (!browser ||
!browser->browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))
return NULL;
// This cast seems ugly, but the controller and the view are tighly coupled at
// creation time, so it will be okay.
BaseTabStrip* other_tabstrip =
static_cast<BaseTabStrip*>(browser->tabstrip());
if (!other_tabstrip->controller()->IsCompatibleWith(source_tabstrip_))
return NULL;
return GetTabStripIfItContains(other_tabstrip, screen_point);
}
BaseTabStrip* DraggedTabController::GetTabStripIfItContains(
BaseTabStrip* tabstrip,
const gfx::Point& screen_point) const {
static const int kVerticalDetachMagnetism = 15;
static const int kHorizontalDetachMagnetism = 15;
// Make sure the specified screen point is actually within the bounds of the
// specified tabstrip...
gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
if (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
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;
}
}
} else {
if (screen_point.y() < tabstrip_bounds.bottom() &&
screen_point.y() >= tabstrip_bounds.y()) {
int upper_threshold = tabstrip_bounds.right() +
kHorizontalDetachMagnetism;
int lower_threshold = tabstrip_bounds.x() - kHorizontalDetachMagnetism;
if (screen_point.x() >= lower_threshold &&
screen_point.x() <= upper_threshold) {
return tabstrip;
}
}
}
return NULL;
}
void DraggedTabController::Attach(BaseTabStrip* attached_tabstrip,
const gfx::Point& screen_point) {
DCHECK(!attached_tabstrip_); // We should already have detached by the time
// we get here.
attached_tabstrip_ = attached_tabstrip;
// And we don't need the dragged view.
view_.reset();
std::vector<BaseTab*> tabs =
GetTabsMatchingDraggedContents(attached_tabstrip_);
if (tabs.empty()) {
// 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.
for (size_t i = 0; i < drag_data_.size(); ++i) {
drag_data_[i].contents->tab_contents()->set_delegate(NULL);
drag_data_[i].original_delegate = NULL;
}
// Return the TabContents' to normalcy.
source_dragged_contents()->tab_contents()->set_capturing_contents(false);
// 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_loc_ = MajorAxisValue(screen_point, attached_tabstrip);
// 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::Point tab_strip_point(screen_point);
views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_strip_point);
tab_strip_point.set_x(
attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()));
tab_strip_point.Offset(-mouse_offset_.x(), -mouse_offset_.y());
gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point);
int index = GetInsertionIndexForDraggedBounds(bounds);
attached_tabstrip_->set_attaching_dragged_tab(true);
for (size_t i = 0; i < drag_data_.size(); ++i) {
int add_types = TabStripModel::ADD_NONE;
if (drag_data_[i].pinned)
add_types |= TabStripModel::ADD_PINNED;
GetModel(attached_tabstrip_)->InsertTabContentsAt(
index + i, drag_data_[i].contents, add_types);
}
attached_tabstrip_->set_attaching_dragged_tab(false);
tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
}
DCHECK_EQ(tabs.size(), drag_data_.size());
for (size_t i = 0; i < drag_data_.size(); ++i)
drag_data_[i].attached_tab = tabs[i];
attached_tabstrip_->StartedDraggingTabs(tabs);
ResetSelection(GetModel(attached_tabstrip_));
if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
// The size of the dragged tab may have changed. Adjust the x offset so that
// ratio of mouse_offset_ to original width is maintained.
std::vector<BaseTab*> tabs_to_source(tabs);
tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1,
tabs_to_source.end());
int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) -
tabs[source_tab_index_]->width() +
static_cast<int>(offset_to_width_ratio_ *
tabs[source_tab_index_]->width());
mouse_offset_.set_x(new_x);
}
// Move the corresponding window to the front.
attached_tabstrip_->GetWindow()->Activate();
}
void DraggedTabController::Detach() {
// Prevent the TabContents' HWND from being hidden by any of the model
// operations performed during the drag.
source_dragged_contents()->tab_contents()->set_capturing_contents(true);
// Calculate the drag bounds.
std::vector<gfx::Rect> drag_bounds;
std::vector<BaseTab*> attached_tabs;
for (size_t i = 0; i < drag_data_.size(); ++i)
attached_tabs.push_back(drag_data_[i].attached_tab);
attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs,
&drag_bounds);
TabStripModel* attached_model = GetModel(attached_tabstrip_);
std::vector<TabRendererData> tab_data;
for (size_t i = 0; i < drag_data_.size(); ++i) {
tab_data.push_back(drag_data_[i].attached_tab->data());
int index = attached_model->GetIndexOfTabContents(drag_data_[i].contents);
DCHECK_NE(-1, index);
// Hide the tab so that the user doesn't see it animate closed.
drag_data_[i].attached_tab->SetVisible(false);
attached_model->DetachTabContentsAt(index);
// Detaching resets the delegate, but we still want to be the delegate.
drag_data_[i].contents->tab_contents()->set_delegate(this);
// Detaching may end up deleting the tab, drop references to it.
drag_data_[i].attached_tab = NULL;
}
// If we've removed the last Tab from the TabStrip, hide the frame now.
if (attached_model->empty())
HideFrame();
// Create the dragged view.
CreateDraggedView(tab_data, drag_bounds);
attached_tabstrip_ = NULL;
}
int DraggedTabController::GetInsertionIndexForDraggedBounds(
const gfx::Rect& dragged_bounds) const {
int right_tab_x = 0;
int bottom_tab_y = 0;
int index = -1;
for (int i = 0; i < attached_tabstrip_->tab_count(); ++i) {
const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i);
if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
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;
}
} else {
// Vertical tab strip.
int max_y = ideal_bounds.bottom();
int mid_y = ideal_bounds.y() + ideal_bounds.height() / 2;
bottom_tab_y = max_y;
if (dragged_bounds.y() < mid_y) {
index = i;
break;
} else if (dragged_bounds.y() >= mid_y && dragged_bounds.y() < max_y) {
index = i + 1;
break;
}
}
}
if (index == -1) {
if ((attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP &&
dragged_bounds.right() > right_tab_x) ||
(attached_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP &&
dragged_bounds.y() >= bottom_tab_y)) {
index = GetModel(attached_tabstrip_)->count();
} else {
index = 0;
}
}
if (!drag_data_[0].attached_tab) {
// If 'attached_tab' is NULL, it means we're in the process of attaching and
// don't need to constrain the index.
return index;
}
int max_index = GetModel(attached_tabstrip_)->count() -
static_cast<int>(drag_data_.size());
return std::max(0, std::min(max_index, index));
}
gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds(
const gfx::Point& tab_strip_point) {
// attached_tab is NULL when inserting into a new tabstrip.
if (source_tab_drag_data()->attached_tab) {
return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
source_tab_drag_data()->attached_tab->width(),
source_tab_drag_data()->attached_tab->height());
}
if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
double sel_width, unselected_width;
static_cast<TabStrip*>(attached_tabstrip_)->GetCurrentTabWidths(
&sel_width, &unselected_width);
return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
static_cast<int>(sel_width),
Tab::GetStandardSize().height());
}
return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
attached_tabstrip_->width(),
SideTab::GetPreferredHeight());
}
gfx::Point DraggedTabController::GetAttachedDragPoint(
const gfx::Point& screen_point) {
DCHECK(attached_tabstrip_); // The tab must be attached.
gfx::Point tab_loc(screen_point);
views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_loc);
int x =
attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
int y = tab_loc.y() - mouse_offset_.y();
// TODO: consider caching this.
std::vector<BaseTab*> attached_tabs;
for (size_t i = 0; i < drag_data_.size(); ++i)
attached_tabs.push_back(drag_data_[i].attached_tab);
int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs);
if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
int max_x = attached_tabstrip_->width() - size;
x = std::min(std::max(x, 0), max_x);
y = 0;
} else {
x = SideTabStrip::kTabStripInset;
int max_y = attached_tabstrip_->height() - size;
y = std::min(std::max(y, SideTabStrip::kTabStripInset), max_y);
}
return gfx::Point(x, y);
}
std::vector<BaseTab*> DraggedTabController::GetTabsMatchingDraggedContents(
BaseTabStrip* tabstrip) {
TabStripModel* model = GetModel(attached_tabstrip_);
std::vector<BaseTab*> tabs;
for (size_t i = 0; i < drag_data_.size(); ++i) {
int model_index = model->GetIndexOfTabContents(drag_data_[i].contents);
if (model_index == TabStripModel::kNoTab)
return std::vector<BaseTab*>();
tabs.push_back(tabstrip->GetBaseTabAtModelIndex(model_index));
}
return tabs;
}
void DraggedTabController::EndDragImpl(EndDragType type) {
active_ = false;
bring_to_front_timer_.Stop();
// Hide the current dock controllers.
for (size_t i = 0; i < dock_controllers_.size(); ++i) {
// Be sure and clear the controller first, that way if Hide ends up
// deleting the controller it won't call us back.
dock_controllers_[i]->clear_controller();
dock_controllers_[i]->Hide();
}
dock_controllers_.clear();
dock_windows_.clear();
if (type != TAB_DESTROYED) {
// We only finish up the drag if we were actually dragging. If start_drag_
// is false, the user just clicked and released and didn't move the mouse
// enough to trigger a drag.
if (started_drag_) {
RestoreFocus();
if (type == CANCELED)
RevertDrag();
else
CompleteDrag();
}
} else if (drag_data_.size() > 1) {
RevertDrag();
} // else case the only tab we were dragging was deleted. Nothing to do.
ResetDelegates();
// Clear out drag data so we don't attempt to do anything with it.
drag_data_.clear();
source_tabstrip_->DestroyDragController();
}
void DraggedTabController::RevertDrag() {
std::vector<BaseTab*> tabs;
for (size_t i = 0; i < drag_data_.size(); ++i) {
if (drag_data_[i].contents) {
// Contents is NULL if a tab was destroyed while the drag was under way.
tabs.push_back(drag_data_[i].attached_tab);
RevertDragAt(i);
}
}
bool restore_frame = attached_tabstrip_ != source_tabstrip_;
if (attached_tabstrip_ && attached_tabstrip_ == source_tabstrip_)
source_tabstrip_->StoppedDraggingTabs(tabs);
attached_tabstrip_ = source_tabstrip_;
ResetSelection(GetModel(attached_tabstrip_));
// 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()) {
#if defined(OS_WIN)
HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView();
MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(),
restore_bounds_.width(), restore_bounds_.height(), TRUE);
#else
NOTIMPLEMENTED();
#endif
}
}
}
void DraggedTabController::ResetSelection(TabStripModel* model) {
DCHECK(model);
TabStripSelectionModel selection_model;
bool has_one_valid_tab = false;
for (size_t i = 0; i < drag_data_.size(); ++i) {
// |contents| is NULL if a tab was deleted out from under us.
if (drag_data_[i].contents) {
int index = model->GetIndexOfTabContents(drag_data_[i].contents);
DCHECK_NE(-1, index);
selection_model.AddIndexToSelection(index);
if (!has_one_valid_tab || i == source_tab_index_) {
// Reset the active/lead to the first tab. If the source tab is still
// valid we'll reset these again later on.
selection_model.set_active(index);
selection_model.set_anchor(index);
has_one_valid_tab = true;
}
}
}
if (!has_one_valid_tab)
return;
model->SetSelectionFromModel(selection_model);
}
void DraggedTabController::RevertDragAt(size_t drag_index) {
DCHECK(started_drag_);
TabDragData* data = &(drag_data_[drag_index]);
if (attached_tabstrip_) {
int index =
GetModel(attached_tabstrip_)->GetIndexOfTabContents(data->contents);
if (attached_tabstrip_ != source_tabstrip_) {
// The Tab was inserted into another TabStrip. We need to put it back
// into the original one.
GetModel(attached_tabstrip_)->DetachTabContentsAt(index);
// TODO(beng): (Cleanup) seems like we should use Attach() for this
// somehow.
GetModel(source_tabstrip_)->InsertTabContentsAt(
data->source_model_index, data->contents,
(data->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.
GetModel(source_tabstrip_)->MoveTabContentsAt(
index, data->source_model_index, false);
}
} else {
// 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.
GetModel(source_tabstrip_)->InsertTabContentsAt(
data->source_model_index, data->contents,
(data->pinned ? TabStripModel::ADD_PINNED : 0));
}
}
void DraggedTabController::CompleteDrag() {
DCHECK(started_drag_);
if (attached_tabstrip_) {
attached_tabstrip_->StoppedDraggingTabs(
GetTabsMatchingDraggedContents(attached_tabstrip_));
} else {
if (dock_info_.type() != DockInfo::NONE) {
Profile* profile = GetModel(source_tabstrip_)->profile();
switch (dock_info_.type()) {
case DockInfo::LEFT_OF_WINDOW:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Left"),
profile);
break;
case DockInfo::RIGHT_OF_WINDOW:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Right"),
profile);
break;
case DockInfo::BOTTOM_OF_WINDOW:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Bottom"),
profile);
break;
case DockInfo::TOP_OF_WINDOW:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Top"),
profile);
break;
case DockInfo::MAXIMIZE:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Maximize"),
profile);
break;
case DockInfo::LEFT_HALF:
UserMetrics::RecordAction(UserMetricsAction("DockingWindow_LeftHalf"),
profile);
break;
case DockInfo::RIGHT_HALF:
UserMetrics::RecordAction(
UserMetricsAction("DockingWindow_RightHalf"),
profile);
break;
case DockInfo::BOTTOM_HALF:
UserMetrics::RecordAction(
UserMetricsAction("DockingWindow_BottomHalf"),
profile);
break;
default:
NOTREACHED();
break;
}
}
// Compel the model to construct a new window for the detached TabContents.
views::Window* window = source_tabstrip_->GetWindow();
gfx::Rect window_bounds(window->GetNormalBounds());
window_bounds.set_origin(GetWindowCreatePoint());
// When modifying the following if statement, please make sure not to
// introduce issue listed in http://crbug.com/6223 comment #11.
bool rtl_ui = base::i18n::IsRTL();
bool has_dock_position = (dock_info_.type() != DockInfo::NONE);
if (rtl_ui && has_dock_position) {
// Mirror X axis so the docked tab is aligned using the mouse click as
// the top-right corner.
window_bounds.set_x(window_bounds.x() - window_bounds.width());
}
Browser* new_browser =
GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents(
drag_data_[0].contents, window_bounds, dock_info_,
window->IsMaximized());
TabStripModel* new_model = new_browser->tabstrip_model();
new_model->SetTabPinned(
new_model->GetIndexOfTabContents(drag_data_[0].contents),
drag_data_[0].pinned);
for (size_t i = 1; i < drag_data_.size(); ++i) {
new_model->InsertTabContentsAt(
static_cast<int>(i),
drag_data_[i].contents,
drag_data_[i].pinned ? TabStripModel::ADD_PINNED :
TabStripModel::ADD_NONE);
}
ResetSelection(new_browser->tabstrip_model());
new_browser->window()->Show();
}
CleanUpHiddenFrame();
}
void DraggedTabController::ResetDelegates() {
for (size_t i = 0; i < drag_data_.size(); ++i) {
if (drag_data_[i].contents &&
drag_data_[i].contents->tab_contents()->delegate() == this) {
drag_data_[i].contents->tab_contents()->set_delegate(
drag_data_[i].original_delegate);
}
}
}
void DraggedTabController::CreateDraggedView(
const std::vector<TabRendererData>& data,
const std::vector<gfx::Rect>& renderer_bounds) {
DCHECK(!view_.get());
DCHECK_EQ(data.size(), drag_data_.size());
// Set up the photo booth to start capturing the contents of the dragged
// TabContents.
NativeViewPhotobooth* photobooth =
NativeViewPhotobooth::Create(
source_dragged_contents()->tab_contents()->GetNativeView());
gfx::Rect content_bounds;
source_dragged_contents()->tab_contents()->GetContainerBounds(
&content_bounds);
std::vector<views::View*> renderers;
for (size_t i = 0; i < drag_data_.size(); ++i) {
BaseTab* renderer = source_tabstrip_->CreateTabForDragging();
renderer->SetData(data[i]);
renderers.push_back(renderer);
}
// DraggedTabView takes ownership of the renderers.
view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_,
content_bounds.size(), photobooth));
}
gfx::Point DraggedTabController::GetCursorScreenPoint() const {
#if defined(OS_WIN)
DWORD pos = GetMessagePos();
return gfx::Point(pos);
#else
gint x, y;
gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL);
return gfx::Point(x, y);
#endif
}
gfx::Rect DraggedTabController::GetViewScreenBounds(views::View* view) const {
gfx::Point view_topleft;
views::View::ConvertPointToScreen(view, &view_topleft);
gfx::Rect view_screen_bounds = view->GetLocalBounds();
view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());
return view_screen_bounds;
}
void DraggedTabController::HideFrame() {
#if defined(OS_WIN)
// 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_->GetWidget()->GetNativeView();
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);
#else
NOTIMPLEMENTED();
#endif
}
void DraggedTabController::CleanUpHiddenFrame() {
// If the model we started dragging from is now empty, we must ask the
// delegate to close the frame.
if (GetModel(source_tabstrip_)->empty())
GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession();
}
void DraggedTabController::DockDisplayerDestroyed(
DockDisplayer* controller) {
DockWindows::iterator dock_i =
dock_windows_.find(controller->popup_view());
if (dock_i != dock_windows_.end())
dock_windows_.erase(dock_i);
else
NOTREACHED();
std::vector<DockDisplayer*>::iterator i =
std::find(dock_controllers_.begin(), dock_controllers_.end(),
controller);
if (i != dock_controllers_.end())
dock_controllers_.erase(i);
else
NOTREACHED();
}
void DraggedTabController::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_view = view_->GetWidget()->GetNativeView();
dock_windows_.insert(dragged_view);
window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(),
dock_windows_);
dock_windows_.erase(dragged_view);
}
if (window) {
#if defined(OS_WIN)
// Move the window to the front.
SetWindowPos(window, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
// The previous call made the window appear on top of the dragged window,
// move the dragged window to the front.
SetWindowPos(view_->GetWidget()->GetNativeView(), HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
#else
NOTIMPLEMENTED();
#endif
}
}
TabStripModel* DraggedTabController::GetModel(BaseTabStrip* tabstrip) const {
return static_cast<BrowserTabStripController*>(tabstrip->controller())->
model();
}
bool DraggedTabController::AreTabsConsecutive() {
for (size_t i = 1; i < drag_data_.size(); ++i) {
if (drag_data_[i - 1].source_model_index + 1 !=
drag_data_[i].source_model_index) {
return false;
}
}
return true;
}