// 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; }