| // Copyright (c) 2010 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/views/tabs/dragged_tab_controller.h" |
| |
| #include <math.h> |
| #include <set> |
| |
| #include "app/animation.h" |
| #include "app/slide_animation.h" |
| #include "app/l10n_util.h" |
| #include "app/resource_bundle.h" |
| #include "base/callback.h" |
| #include "base/i18n/rtl.h" |
| #include "base/keyboard_codes.h" |
| #include "chrome/browser/browser_window.h" |
| #include "chrome/browser/extensions/extension_function_dispatcher.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #include "chrome/browser/tabs/tab_strip_model.h" |
| #include "chrome/browser/metrics/user_metrics.h" |
| #include "chrome/browser/views/frame/browser_view.h" |
| #include "chrome/browser/views/tabs/base_tab.h" |
| #include "chrome/browser/views/tabs/base_tab_strip.h" |
| #include "chrome/browser/views/tabs/browser_tab_strip_controller.h" |
| #include "chrome/browser/views/tabs/dragged_tab_view.h" |
| #include "chrome/browser/views/tabs/native_view_photobooth.h" |
| #include "chrome/browser/views/tabs/side_tab.h" |
| #include "chrome/browser/views/tabs/side_tab_strip.h" |
| #include "chrome/browser/views/tabs/tab.h" |
| #include "chrome/browser/views/tabs/tab_strip.h" |
| #include "chrome/common/notification_service.h" |
| #include "gfx/canvas_skia.h" |
| #include "grit/theme_resources.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "views/event.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> |
| #include <gdk/gdkkeysyms.h> |
| #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 PaintBackground(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); |
| |
| bool rtl_ui = base::i18n::IsRTL(); |
| if (rtl_ui) { |
| // Flip canvas to draw the mirrored tab images for RTL UI. |
| canvas->Save(); |
| canvas->TranslateInt(width(), 0); |
| canvas->ScaleInt(-1, 1); |
| } |
| int x_of_active_tab = -1; |
| int x_of_inactive_tab = -1; |
| switch (type_) { |
| case DockInfo::LEFT_OF_WINDOW: |
| case DockInfo::LEFT_HALF: |
| if (!rtl_ui) { |
| x_of_active_tab = width() / 2 - high_icon->width() - kTabSpacing / 2; |
| x_of_inactive_tab = width() / 2 + kTabSpacing / 2; |
| } else { |
| // Adjust x axis for RTL UI after flippping canvas. |
| x_of_active_tab = width() / 2 + kTabSpacing / 2; |
| x_of_inactive_tab = width() / 2 - high_icon->width() - |
| kTabSpacing / 2; |
| } |
| 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) { |
| x_of_active_tab = width() / 2 + kTabSpacing / 2; |
| x_of_inactive_tab = width() / 2 - high_icon->width() - |
| kTabSpacing / 2; |
| } else { |
| // Adjust x axis for RTL UI after flippping canvas. |
| x_of_active_tab = width() / 2 - high_icon->width() - kTabSpacing / 2; |
| x_of_inactive_tab = width() / 2 + kTabSpacing / 2; |
| } |
| 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; |
| } |
| if (rtl_ui) |
| 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); |
| }; |
| |
| gfx::Point ConvertScreenPointToTabStripPoint(BaseTabStrip* tabstrip, |
| const gfx::Point& screen_point) { |
| gfx::Point tabstrip_topleft; |
| views::View::ConvertPointToScreen(tabstrip, &tabstrip_topleft); |
| return gfx::Point(screen_point.x() - tabstrip_topleft.x(), |
| screen_point.y() - tabstrip_topleft.y()); |
| } |
| |
| // 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 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) |
| views::WidgetWin* popup = new views::WidgetWin; |
| popup_ = popup; |
| popup->set_window_style(WS_POPUP); |
| popup->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW | |
| WS_EX_TOPMOST); |
| 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->SetWindowPos(HWND_TOP, 0, 0, 0, 0, |
| SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOMOVE | SWP_SHOWWINDOW); |
| #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 Animation* animation) { |
| UpdateLayeredAlpha(); |
| } |
| |
| virtual void AnimationEnded(const 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. |
| SlideAnimation animation_; |
| |
| // Have we been hidden? |
| bool hidden_; |
| |
| // Value of DockInfo::in_enable_area. |
| bool in_enable_area_; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabController, public: |
| |
| DraggedTabController::DraggedTabController(BaseTab* source_tab, |
| BaseTabStrip* source_tabstrip) |
| : dragged_contents_(NULL), |
| original_delegate_(NULL), |
| source_tabstrip_(source_tabstrip), |
| source_model_index_(source_tabstrip->GetModelIndexOfBaseTab(source_tab)), |
| attached_tabstrip_(NULL), |
| attached_tab_(NULL), |
| offset_to_width_ratio_(0), |
| old_focused_view_(NULL), |
| last_move_screen_loc_(0), |
| mini_(source_tab->data().mini), |
| pinned_(source_tabstrip->IsTabPinned(source_tab)), |
| started_drag_(false), |
| active_(true) { |
| instance_ = this; |
| SetDraggedContents( |
| GetModel(source_tabstrip_)->GetTabContentsAt(source_model_index_)); |
| // Listen for Esc key presses. |
| MessageLoopForUI::current()->AddObserver(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); |
| SetDraggedContents(NULL); // This removes our observer. |
| } |
| |
| // static |
| bool DraggedTabController::IsAttachedTo(BaseTabStrip* tab_strip) { |
| return instance_ && instance_->active_ && |
| instance_->attached_tabstrip_ == tab_strip; |
| } |
| |
| void DraggedTabController::CaptureDragInfo(views::View* tab, |
| const gfx::Point& mouse_offset) { |
| if (tab->width() > 0) { |
| offset_to_width_ratio_ = static_cast<float>(mouse_offset.x()) / |
| static_cast<float>(tab->width()); |
| } |
| start_screen_point_ = GetCursorScreenPoint(); |
| mouse_offset_ = mouse_offset; |
| InitWindowCreatePoint(); |
| } |
| |
| void DraggedTabController::Drag() { |
| bring_to_front_timer_.Stop(); |
| |
| // Before we get to dragging anywhere, ensure that we consider ourselves |
| // attached to the source tabstrip. |
| if (!started_drag_ && CanStartDrag()) { |
| started_drag_ = true; |
| SaveFocus(); |
| Attach(source_tabstrip_, gfx::Point()); |
| } |
| |
| if (started_drag_) |
| ContinueDragging(); |
| } |
| |
| void DraggedTabController::EndDrag(bool canceled) { |
| EndDragImpl(canceled ? CANCELED : NORMAL); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabController, PageNavigator implementation: |
| |
| void DraggedTabController::OpenURLFromTab(TabContents* source, |
| const GURL& url, |
| const GURL& referrer, |
| WindowOpenDisposition disposition, |
| PageTransition::Type transition) { |
| if (original_delegate_) { |
| if (disposition == CURRENT_TAB) |
| disposition = NEW_WINDOW; |
| |
| original_delegate_->OpenURLFromTab(source, url, referrer, |
| disposition, transition); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // 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(disposition != CURRENT_TAB); |
| |
| // Theoretically could be called while dragging if the page tries to |
| // spawn a window. Route this message back to the browser in most cases. |
| if (original_delegate_) { |
| original_delegate_->AddNewContents(source, new_contents, disposition, |
| initial_pos, user_gesture); |
| } |
| } |
| |
| void DraggedTabController::ActivateContents(TabContents* contents) { |
| // Ignored. |
| } |
| |
| void DraggedTabController::LoadingStateChanged(TabContents* source) { |
| // It would be nice to respond to this message by changing the |
| // screen shot in the dragged tab. |
| if (view_.get()) |
| view_->Update(); |
| } |
| |
| void DraggedTabController::CloseContents(TabContents* source) { |
| // Theoretically could be called by a window. Should be ignored |
| // because window.close() is ignored (usually, even though this |
| // method gets called.) |
| } |
| |
| void DraggedTabController::MoveContents(TabContents* source, |
| const gfx::Rect& pos) { |
| // Theoretically could be called by a web page trying to move its |
| // own window. Should be ignored since we're moving the window... |
| } |
| |
| bool DraggedTabController::IsPopup(TabContents* source) { |
| return false; |
| } |
| |
| void DraggedTabController::ToolbarSizeChanged(TabContents* source, |
| bool finished) { |
| // Dragged tabs don't care about this. |
| } |
| |
| void DraggedTabController::URLStarredChanged(TabContents* source, |
| bool starred) { |
| // Ignored. |
| } |
| |
| void DraggedTabController::UpdateTargetURL(TabContents* source, |
| const GURL& url) { |
| // Ignored. |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DraggedTabController, NotificationObserver implementation: |
| |
| void DraggedTabController::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); |
| DCHECK(Source<TabContents>(source).ptr() == dragged_contents_); |
| EndDragImpl(TAB_DESTROYED); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // 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_); |
| UpdateWindowCreatePoint(); |
| } |
| |
| void DraggedTabController::UpdateWindowCreatePoint() { |
| // See comments in InitWindowCreatePoint for details on this. |
| 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) { |
| // 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; |
| } |
| 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::SetDraggedContents(TabContents* new_contents) { |
| if (dragged_contents_) { |
| registrar_.Remove(this, |
| NotificationType::TAB_CONTENTS_DESTROYED, |
| Source<TabContents>(dragged_contents_)); |
| if (original_delegate_) |
| dragged_contents_->set_delegate(original_delegate_); |
| } |
| original_delegate_ = NULL; |
| dragged_contents_ = new_contents; |
| if (dragged_contents_) { |
| registrar_.Add(this, |
| NotificationType::TAB_CONTENTS_DESTROYED, |
| Source<TabContents>(dragged_contents_)); |
| |
| // We need to be the delegate so we receive messages about stuff, |
| // otherwise our dragged_contents() may be replaced and subsequently |
| // collected/destroyed while the drag is in process, leading to |
| // nasty crashes. |
| original_delegate_ = dragged_contents_->delegate(); |
| dragged_contents_->set_delegate(this); |
| } |
| } |
| |
| void DraggedTabController::SaveFocus() { |
| if (!old_focused_view_) { |
| old_focused_view_ = source_tabstrip_->GetRootView()->GetFocusedView(); |
| source_tabstrip_->GetRootView()->FocusView(source_tabstrip_); |
| } |
| } |
| |
| void DraggedTabController::RestoreFocus() { |
| if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_) |
| old_focused_view_->GetRootView()->FocusView(old_focused_view_); |
| old_focused_view_ = NULL; |
| } |
| |
| bool DraggedTabController::CanStartDrag() const { |
| // Determine if the mouse has moved beyond a minimum elasticity distance in |
| // any direction from the starting point. |
| static const int kMinimumDragDistance = 10; |
| gfx::Point screen_point = GetCursorScreenPoint(); |
| int x_offset = abs(screen_point.x() - start_screen_point_.x()); |
| int y_offset = abs(screen_point.y() - start_screen_point_.y()); |
| return sqrt(pow(static_cast<float>(x_offset), 2) + |
| pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance; |
| } |
| |
| void DraggedTabController::ContinueDragging() { |
| // 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_CHROMEOS) |
| // 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_) |
| MoveAttachedTab(screen_point); |
| else |
| MoveDetachedTab(screen_point); |
| } |
| |
| void DraggedTabController::MoveAttachedTab(const gfx::Point& screen_point) { |
| DCHECK(attached_tabstrip_); |
| DCHECK(!view_.get()); |
| |
| gfx::Point dragged_view_point = GetAttachedTabDragPoint(screen_point); |
| TabStripModel* attached_model = GetModel(attached_tabstrip_); |
| int from_index = attached_model->GetIndexOfTabContents(dragged_contents_); |
| |
| 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); |
| } |
| |
| // Update the model, moving the TabContents from one index to another. Do this |
| // only if we have moved a minimum distance since the last reorder (to prevent |
| // jitter). |
| if (abs(MajorAxisValue(screen_point, attached_tabstrip_) - |
| last_move_screen_loc_) > threshold) { |
| gfx::Point dragged_view_screen_point(dragged_view_point); |
| views::View::ConvertPointToScreen(attached_tabstrip_, |
| &dragged_view_screen_point); |
| gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_screen_point); |
| int to_index = GetInsertionIndexForDraggedBounds(bounds, true); |
| to_index = NormalizeIndexToAttachedTabStrip(to_index); |
| if (from_index != to_index) { |
| last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip_); |
| attached_model->MoveTabContentsAt(from_index, to_index, true); |
| } |
| } |
| |
| attached_tab_->SchedulePaint(); |
| attached_tab_->SetX(dragged_view_point.x()); |
| attached_tab_->SetX( |
| attached_tabstrip_->MirroredLeftPointForRect(attached_tab_->bounds())); |
| attached_tab_->SetY(dragged_view_point.y()); |
| attached_tab_->SchedulePaint(); |
| } |
| |
| void DraggedTabController::MoveDetachedTab(const gfx::Point& screen_point) { |
| DCHECK(!attached_tabstrip_); |
| DCHECK(view_.get()); |
| |
| int x = screen_point.x() - mouse_offset_.x(); |
| int y = screen_point.y() - mouse_offset_.y(); |
| |
| // Move the View. There are no changes to the model if we're detached. |
| view_->MoveTo(gfx::Point(x, y)); |
| } |
| |
| 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; |
| |
| BaseTabStrip* other_tabstrip = 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. |
| DCHECK(!attached_tab_); // Similarly there should be no attached tab. |
| |
| attached_tabstrip_ = attached_tabstrip; |
| |
| // We don't need the photo-booth while we're attached. |
| photobooth_.reset(NULL); |
| |
| // And we don't need the dragged view. |
| view_.reset(); |
| |
| BaseTab* tab = GetTabMatchingDraggedContents(attached_tabstrip_); |
| |
| if (!tab) { |
| // There is no Tab in |attached_tabstrip| that corresponds to the dragged |
| // TabContents. We must now create one. |
| |
| // Remove ourselves as the delegate now that the dragged TabContents is |
| // being inserted back into a Browser. |
| dragged_contents_->set_delegate(NULL); |
| original_delegate_ = NULL; |
| |
| // Return the TabContents' to normalcy. |
| dragged_contents_->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::Rect bounds = GetDraggedViewTabStripBounds(screen_point); |
| int index = GetInsertionIndexForDraggedBounds(bounds, false); |
| attached_tabstrip_->set_attaching_dragged_tab(true); |
| GetModel(attached_tabstrip_)->InsertTabContentsAt( |
| index, dragged_contents_, |
| TabStripModel::ADD_SELECTED | |
| (pinned_ ? TabStripModel::ADD_PINNED : 0)); |
| attached_tabstrip_->set_attaching_dragged_tab(false); |
| |
| tab = GetTabMatchingDraggedContents(attached_tabstrip_); |
| } |
| DCHECK(tab); // We should now have a tab. |
| attached_tab_ = tab; |
| attached_tabstrip_->StartedDraggingTab(tab); |
| |
| 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. |
| mouse_offset_.set_x(static_cast<int>(offset_to_width_ratio_ * |
| static_cast<int>(tab->width()))); |
| } |
| |
| // 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. |
| dragged_contents_->set_capturing_contents(true); |
| |
| // Update the Model. |
| TabRendererData tab_data = attached_tab_->data(); |
| TabStripModel* attached_model = GetModel(attached_tabstrip_); |
| int index = attached_model->GetIndexOfTabContents(dragged_contents_); |
| DCHECK(index != -1); |
| // Hide the tab so that the user doesn't see it animate closed. |
| attached_tab_->SetVisible(false); |
| int attached_tab_width = attached_tab_->width(); |
| attached_model->DetachTabContentsAt(index); |
| // Detaching may end up deleting the tab, drop references to it. |
| attached_tab_ = NULL; |
| |
| // If we've removed the last Tab from the TabStrip, hide the frame now. |
| if (!attached_model->HasNonPhantomTabs()) |
| HideFrame(); |
| |
| // Set up the photo booth to start capturing the contents of the dragged |
| // TabContents. |
| if (!photobooth_.get()) { |
| photobooth_.reset( |
| NativeViewPhotobooth::Create(dragged_contents_->GetNativeView())); |
| } |
| |
| // Create the dragged view. |
| EnsureDraggedView(tab_data); |
| view_->SetTabWidthAndUpdate(attached_tab_width, photobooth_.get()); |
| |
| // Detaching resets the delegate, but we still want to be the delegate. |
| dragged_contents_->set_delegate(this); |
| |
| attached_tabstrip_ = NULL; |
| } |
| |
| int DraggedTabController::GetInsertionIndexForDraggedBounds( |
| const gfx::Rect& dragged_bounds, |
| bool is_tab_attached) const { |
| int right_tab_x = 0; |
| int bottom_tab_y = 0; |
| |
| // If the UI layout of the tab strip is right-to-left, we need to mirror the |
| // bounds of the dragged tab before performing the drag/drop related |
| // calculations. We mirror the dragged bounds because we determine the |
| // position of each tab on the tab strip by calling GetBounds() (without the |
| // mirroring transformation flag) which effectively means that even though |
| // the tabs are rendered from right to left, the code performs the |
| // calculation as if the tabs are laid out from left to right. Mirroring the |
| // dragged bounds adjusts the coordinates of the tab we are dragging so that |
| // it uses the same orientation used by the tabs on the tab strip. |
| gfx::Rect adjusted_bounds(dragged_bounds); |
| adjusted_bounds.set_x( |
| attached_tabstrip_->MirroredLeftPointForRect(adjusted_bounds)); |
| |
| 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 (adjusted_bounds.x() >= right_half.x() && |
| adjusted_bounds.x() < right_half.right()) { |
| index = i + 1; |
| break; |
| } else if (adjusted_bounds.x() >= left_half.x() && |
| adjusted_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 (adjusted_bounds.y() < mid_y) { |
| index = i; |
| break; |
| } else if (adjusted_bounds.y() >= mid_y && adjusted_bounds.y() < max_y) { |
| index = i + 1; |
| break; |
| } |
| } |
| } |
| if (index == -1) { |
| if ((attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP && |
| adjusted_bounds.right() > right_tab_x) || |
| (attached_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP && |
| adjusted_bounds.y() >= bottom_tab_y)) { |
| index = GetModel(attached_tabstrip_)->count(); |
| } else { |
| index = 0; |
| } |
| } |
| |
| index = GetModel(attached_tabstrip_)->ConstrainInsertionIndex(index, mini_); |
| if (is_tab_attached && mini_ && |
| index == GetModel(attached_tabstrip_)->IndexOfFirstNonMiniTab()) { |
| index--; |
| } |
| return index; |
| } |
| |
| gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds( |
| const gfx::Point& screen_point) { |
| gfx::Point client_point = |
| ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point); |
| // attached_tab_ is NULL when inserting into a new tabstrip. |
| if (attached_tab_) { |
| return gfx::Rect(client_point.x(), client_point.y(), |
| attached_tab_->width(), 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(client_point.x(), client_point.y(), |
| static_cast<int>(sel_width), |
| Tab::GetStandardSize().height()); |
| } |
| |
| return gfx::Rect(client_point.x(), client_point.y(), |
| attached_tabstrip_->width(), |
| SideTab::GetPreferredHeight()); |
| } |
| |
| gfx::Point DraggedTabController::GetAttachedTabDragPoint( |
| const gfx::Point& screen_point) { |
| DCHECK(attached_tabstrip_); // The tab must be attached. |
| |
| int x = screen_point.x() - mouse_offset_.x(); |
| int y = screen_point.y() - mouse_offset_.y(); |
| |
| gfx::Point tab_loc(x, y); |
| views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_loc); |
| |
| x = tab_loc.x(); |
| y = tab_loc.y(); |
| |
| const gfx::Size& tab_size = attached_tab_->bounds().size(); |
| |
| if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) { |
| int max_x = attached_tabstrip_->bounds().right() - tab_size.width(); |
| x = std::min(std::max(x, 0), max_x); |
| y = 0; |
| } else { |
| x = SideTabStrip::kTabStripInset; |
| int max_y = attached_tabstrip_->bounds().bottom() - tab_size.height(); |
| y = std::min(std::max(y, SideTabStrip::kTabStripInset), max_y); |
| } |
| return gfx::Point(x, y); |
| } |
| |
| BaseTab* DraggedTabController::GetTabMatchingDraggedContents( |
| BaseTabStrip* tabstrip) const { |
| int model_index = |
| GetModel(tabstrip)->GetIndexOfTabContents(dragged_contents_); |
| return model_index == TabStripModel::kNoTab ? |
| NULL : tabstrip->GetBaseTabAtModelIndex(model_index); |
| } |
| |
| void DraggedTabController::EndDragImpl(EndDragType type) { |
| // WARNING: this may be invoked multiple times. In particular, if deletion |
| // occurs after a delay (as it does when the tab is released in the original |
| // tab strip) and the navigation controller/tab contents is deleted before |
| // the animation finishes, this is invoked twice. The second time through |
| // type == TAB_DESTROYED. |
| |
| 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(); |
| } |
| if (dragged_contents_ && dragged_contents_->delegate() == this) |
| dragged_contents_->set_delegate(original_delegate_); |
| } else { |
| // If we get here it means the NavigationController is going down. Don't |
| // attempt to do any cleanup other than resetting the delegate (if we're |
| // still the delegate). |
| if (dragged_contents_ && dragged_contents_->delegate() == this) |
| dragged_contents_->set_delegate(NULL); |
| dragged_contents_ = NULL; |
| } |
| |
| // The delegate of the dragged contents should have been reset. Unset the |
| // original delegate so that we don't attempt to reset the delegate when |
| // deleted. |
| DCHECK(!dragged_contents_ || dragged_contents_->delegate() != this); |
| original_delegate_ = NULL; |
| |
| source_tabstrip_->DestroyDragController(); |
| } |
| |
| void DraggedTabController::RevertDrag() { |
| DCHECK(started_drag_); |
| |
| // We save this here because code below will modify |attached_tabstrip_|. |
| bool restore_frame = attached_tabstrip_ != source_tabstrip_; |
| if (attached_tabstrip_) { |
| int index = GetModel(attached_tabstrip_)->GetIndexOfTabContents( |
| dragged_contents_); |
| if (attached_tabstrip_ != source_tabstrip_) { |
| // The Tab was inserted into another TabStrip. We need to put it back |
| // into the original one. |
| GetModel(attached_tabstrip_)->DetachTabContentsAt(index); |
| // TODO(beng): (Cleanup) seems like we should use Attach() for this |
| // somehow. |
| attached_tabstrip_ = source_tabstrip_; |
| GetModel(source_tabstrip_)->InsertTabContentsAt( |
| source_model_index_, dragged_contents_, |
| TabStripModel::ADD_SELECTED | |
| (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, source_model_index_, |
| true); |
| source_tabstrip_->StoppedDraggingTab(attached_tab_); |
| } |
| } else { |
| // TODO(beng): (Cleanup) seems like we should use Attach() for this |
| // somehow. |
| attached_tabstrip_ = source_tabstrip_; |
| // The Tab was detached from the TabStrip where the drag began, and has not |
| // been attached to any other TabStrip. We need to put it back into the |
| // source TabStrip. |
| GetModel(source_tabstrip_)->InsertTabContentsAt( |
| source_model_index_, dragged_contents_, |
| TabStripModel::ADD_SELECTED | |
| (pinned_ ? TabStripModel::ADD_PINNED : 0)); |
| } |
| |
| // 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::CompleteDrag() { |
| DCHECK(started_drag_); |
| |
| if (attached_tabstrip_) { |
| attached_tabstrip_->StoppedDraggingTab(attached_tab_); |
| } 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. |
| gfx::Rect browser_rect = source_tabstrip_->GetWindow()->GetBounds(); |
| gfx::Rect window_bounds( |
| GetWindowCreatePoint(), |
| gfx::Size(browser_rect.width(), browser_rect.height())); |
| // 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( |
| dragged_contents_, window_bounds, dock_info_); |
| TabStripModel* new_model = new_browser->tabstrip_model(); |
| new_model->SetTabPinned(new_model->GetIndexOfTabContents(dragged_contents_), |
| pinned_); |
| new_browser->window()->Show(); |
| } |
| |
| CleanUpHiddenFrame(); |
| } |
| |
| void DraggedTabController::EnsureDraggedView(const TabRendererData& data) { |
| if (!view_.get()) { |
| gfx::Rect tab_bounds; |
| dragged_contents_->GetContainerBounds(&tab_bounds); |
| BaseTab* renderer = source_tabstrip_->CreateTabForDragging(); |
| renderer->SetData(data); |
| // DraggedTabView takes ownership of renderer. |
| view_.reset(new DraggedTabView(renderer, mouse_offset_, |
| tab_bounds.size(), |
| Tab::GetMinimumSelectedSize())); |
| } |
| } |
| |
| 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(true); |
| view_screen_bounds.Offset(view_topleft.x(), view_topleft.y()); |
| return view_screen_bounds; |
| } |
| |
| int DraggedTabController::NormalizeIndexToAttachedTabStrip(int index) const { |
| DCHECK(attached_tabstrip_) << "Can only be called when attached!"; |
| TabStripModel* attached_model = GetModel(attached_tabstrip_); |
| if (index >= attached_model->count()) |
| return attached_model->count() - 1; |
| if (index == TabStripModel::kNoTab) |
| return 0; |
| return index; |
| } |
| |
| 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_)->HasNonPhantomTabs()) |
| 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(); |
| } |