• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/tabs/dragged_tab_controller.h"
6 
7 #include <math.h>
8 #include <set>
9 
10 #include "base/callback.h"
11 #include "base/i18n/rtl.h"
12 #include "chrome/browser/extensions/extension_function_dispatcher.h"
13 #include "chrome/browser/metrics/user_metrics.h"
14 #include "chrome/browser/tabs/tab_strip_model.h"
15 #include "chrome/browser/ui/browser_window.h"
16 #include "chrome/browser/ui/views/frame/browser_view.h"
17 #include "chrome/browser/ui/views/tabs/base_tab.h"
18 #include "chrome/browser/ui/views/tabs/base_tab_strip.h"
19 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
20 #include "chrome/browser/ui/views/tabs/dragged_tab_view.h"
21 #include "chrome/browser/ui/views/tabs/native_view_photobooth.h"
22 #include "chrome/browser/ui/views/tabs/side_tab.h"
23 #include "chrome/browser/ui/views/tabs/side_tab_strip.h"
24 #include "chrome/browser/ui/views/tabs/tab.h"
25 #include "chrome/browser/ui/views/tabs/tab_strip.h"
26 #include "content/browser/tab_contents/tab_contents.h"
27 #include "content/common/notification_details.h"
28 #include "content/common/notification_source.h"
29 #include "grit/theme_resources.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "ui/base/animation/animation.h"
32 #include "ui/base/animation/animation_delegate.h"
33 #include "ui/base/animation/slide_animation.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/canvas_skia.h"
36 #include "views/events/event.h"
37 #include "views/screen.h"
38 #include "views/widget/root_view.h"
39 #include "views/widget/widget.h"
40 #include "views/window/window.h"
41 
42 #if defined(OS_WIN)
43 #include "views/widget/widget_win.h"
44 #endif
45 
46 #if defined(OS_LINUX)
47 #include <gdk/gdk.h>  // NOLINT
48 #include <gdk/gdkkeysyms.h>  // NOLINT
49 #endif
50 
51 static const int kHorizontalMoveThreshold = 16;  // Pixels.
52 
53 // Distance in pixels the user must move the mouse before we consider moving
54 // an attached vertical tab.
55 static const int kVerticalMoveThreshold = 8;
56 
57 // If non-null there is a drag underway.
58 static DraggedTabController* instance_;
59 
60 namespace {
61 
62 // Delay, in ms, during dragging before we bring a window to front.
63 const int kBringToFrontDelay = 750;
64 
65 // Radius of the rect drawn by DockView.
66 const int kRoundedRectRadius = 4;
67 
68 // Spacing between tab icons when DockView is showing a docking location that
69 // contains more than one tab.
70 const int kTabSpacing = 4;
71 
72 // DockView is the view responsible for giving a visual indicator of where a
73 // dock is going to occur.
74 
75 class DockView : public views::View {
76  public:
DockView(DockInfo::Type type)77   explicit DockView(DockInfo::Type type) : type_(type) {}
78 
GetPreferredSize()79   virtual gfx::Size GetPreferredSize() {
80     return gfx::Size(DockInfo::popup_width(), DockInfo::popup_height());
81   }
82 
OnPaintBackground(gfx::Canvas * canvas)83   virtual void OnPaintBackground(gfx::Canvas* canvas) {
84     SkRect outer_rect = { SkIntToScalar(0), SkIntToScalar(0),
85                           SkIntToScalar(width()),
86                           SkIntToScalar(height()) };
87 
88     // Fill the background rect.
89     SkPaint paint;
90     paint.setColor(SkColorSetRGB(108, 108, 108));
91     paint.setStyle(SkPaint::kFill_Style);
92     canvas->AsCanvasSkia()->drawRoundRect(
93         outer_rect, SkIntToScalar(kRoundedRectRadius),
94         SkIntToScalar(kRoundedRectRadius), paint);
95 
96     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
97 
98     SkBitmap* high_icon = rb.GetBitmapNamed(IDR_DOCK_HIGH);
99     SkBitmap* wide_icon = rb.GetBitmapNamed(IDR_DOCK_WIDE);
100 
101     canvas->Save();
102     bool rtl_ui = base::i18n::IsRTL();
103     if (rtl_ui) {
104       // Flip canvas to draw the mirrored tab images for RTL UI.
105       canvas->TranslateInt(width(), 0);
106       canvas->ScaleInt(-1, 1);
107     }
108     int x_of_active_tab = width() / 2 + kTabSpacing / 2;
109     int x_of_inactive_tab = width() / 2 - high_icon->width() - kTabSpacing / 2;
110     switch (type_) {
111       case DockInfo::LEFT_OF_WINDOW:
112       case DockInfo::LEFT_HALF:
113         if (!rtl_ui)
114           std::swap(x_of_active_tab, x_of_inactive_tab);
115         canvas->DrawBitmapInt(*high_icon, x_of_active_tab,
116                               (height() - high_icon->height()) / 2);
117         if (type_ == DockInfo::LEFT_OF_WINDOW) {
118           DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab,
119                               (height() - high_icon->height()) / 2);
120         }
121         break;
122 
123 
124       case DockInfo::RIGHT_OF_WINDOW:
125       case DockInfo::RIGHT_HALF:
126         if (rtl_ui)
127           std::swap(x_of_active_tab, x_of_inactive_tab);
128         canvas->DrawBitmapInt(*high_icon, x_of_active_tab,
129                               (height() - high_icon->height()) / 2);
130         if (type_ == DockInfo::RIGHT_OF_WINDOW) {
131          DrawBitmapWithAlpha(canvas, *high_icon, x_of_inactive_tab,
132                              (height() - high_icon->height()) / 2);
133         }
134         break;
135 
136       case DockInfo::TOP_OF_WINDOW:
137         canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2,
138                               height() / 2 - high_icon->height());
139         break;
140 
141       case DockInfo::MAXIMIZE: {
142         SkBitmap* max_icon = rb.GetBitmapNamed(IDR_DOCK_MAX);
143         canvas->DrawBitmapInt(*max_icon, (width() - max_icon->width()) / 2,
144                               (height() - max_icon->height()) / 2);
145         break;
146       }
147 
148       case DockInfo::BOTTOM_HALF:
149       case DockInfo::BOTTOM_OF_WINDOW:
150         canvas->DrawBitmapInt(*wide_icon, (width() - wide_icon->width()) / 2,
151                               height() / 2 + kTabSpacing / 2);
152         if (type_ == DockInfo::BOTTOM_OF_WINDOW) {
153           DrawBitmapWithAlpha(canvas, *wide_icon,
154               (width() - wide_icon->width()) / 2,
155               height() / 2 - kTabSpacing / 2 - wide_icon->height());
156         }
157         break;
158 
159       default:
160         NOTREACHED();
161         break;
162     }
163     canvas->Restore();
164   }
165 
166  private:
DrawBitmapWithAlpha(gfx::Canvas * canvas,const SkBitmap & image,int x,int y)167   void DrawBitmapWithAlpha(gfx::Canvas* canvas, const SkBitmap& image,
168                            int x, int y) {
169     SkPaint paint;
170     paint.setAlpha(128);
171     canvas->DrawBitmapInt(image, x, y, paint);
172   }
173 
174   DockInfo::Type type_;
175 
176   DISALLOW_COPY_AND_ASSIGN(DockView);
177 };
178 
179 // Returns the the x-coordinate of |point| if the type of tabstrip is horizontal
180 // otherwise returns the y-coordinate.
MajorAxisValue(const gfx::Point & point,BaseTabStrip * tabstrip)181 int MajorAxisValue(const gfx::Point& point, BaseTabStrip* tabstrip) {
182   return (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) ?
183       point.x() : point.y();
184 }
185 
186 }  // namespace
187 
188 ///////////////////////////////////////////////////////////////////////////////
189 // DockDisplayer
190 
191 // DockDisplayer is responsible for giving the user a visual indication of a
192 // possible dock position (as represented by DockInfo). DockDisplayer shows
193 // a window with a DockView in it. Two animations are used that correspond to
194 // the state of DockInfo::in_enable_area.
195 class DraggedTabController::DockDisplayer : public ui::AnimationDelegate {
196  public:
DockDisplayer(DraggedTabController * controller,const DockInfo & info)197   DockDisplayer(DraggedTabController* controller,
198                 const DockInfo& info)
199       : controller_(controller),
200         popup_(NULL),
201         popup_view_(NULL),
202         ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)),
203         hidden_(false),
204         in_enable_area_(info.in_enable_area()) {
205 #if defined(OS_WIN)
206     // TODO(sky): This should "just work" on Gtk now.
207     views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP);
208     params.transparent = true;
209     params.keep_on_top = true;
210     popup_ = views::Widget::CreateWidget(params);
211     popup_->SetOpacity(0x00);
212     popup_->Init(NULL, info.GetPopupRect());
213     popup_->SetContentsView(new DockView(info.type()));
214     if (info.in_enable_area())
215       animation_.Reset(1);
216     else
217       animation_.Show();
218     popup_->Show();
219 #else
220     NOTIMPLEMENTED();
221 #endif
222     popup_view_ = popup_->GetNativeView();
223   }
224 
~DockDisplayer()225   ~DockDisplayer() {
226     if (controller_)
227       controller_->DockDisplayerDestroyed(this);
228   }
229 
230   // Updates the state based on |in_enable_area|.
UpdateInEnabledArea(bool in_enable_area)231   void UpdateInEnabledArea(bool in_enable_area) {
232     if (in_enable_area != in_enable_area_) {
233       in_enable_area_ = in_enable_area;
234       UpdateLayeredAlpha();
235     }
236   }
237 
238   // Resets the reference to the hosting DraggedTabController. This is invoked
239   // when the DraggedTabController is destoryed.
clear_controller()240   void clear_controller() { controller_ = NULL; }
241 
242   // NativeView of the window we create.
popup_view()243   gfx::NativeView popup_view() { return popup_view_; }
244 
245   // Starts the hide animation. When the window is closed the
246   // DraggedTabController is notified by way of the DockDisplayerDestroyed
247   // method
Hide()248   void Hide() {
249     if (hidden_)
250       return;
251 
252     if (!popup_) {
253       delete this;
254       return;
255     }
256     hidden_ = true;
257     animation_.Hide();
258   }
259 
AnimationProgressed(const ui::Animation * animation)260   virtual void AnimationProgressed(const ui::Animation* animation) {
261     UpdateLayeredAlpha();
262   }
263 
AnimationEnded(const ui::Animation * animation)264   virtual void AnimationEnded(const ui::Animation* animation) {
265     if (!hidden_)
266       return;
267 #if defined(OS_WIN)
268     static_cast<views::WidgetWin*>(popup_)->Close();
269 #else
270     NOTIMPLEMENTED();
271 #endif
272     delete this;
273   }
274 
UpdateLayeredAlpha()275   virtual void UpdateLayeredAlpha() {
276 #if defined(OS_WIN)
277     double scale = in_enable_area_ ? 1 : .5;
278     static_cast<views::WidgetWin*>(popup_)->SetOpacity(
279         static_cast<BYTE>(animation_.GetCurrentValue() * scale * 255.0));
280     popup_->GetRootView()->SchedulePaint();
281 #else
282     NOTIMPLEMENTED();
283 #endif
284   }
285 
286  private:
287   // DraggedTabController that created us.
288   DraggedTabController* controller_;
289 
290   // Window we're showing.
291   views::Widget* popup_;
292 
293   // NativeView of |popup_|. We cache this to avoid the possibility of
294   // invoking a method on popup_ after we close it.
295   gfx::NativeView popup_view_;
296 
297   // Animation for when first made visible.
298   ui::SlideAnimation animation_;
299 
300   // Have we been hidden?
301   bool hidden_;
302 
303   // Value of DockInfo::in_enable_area.
304   bool in_enable_area_;
305 };
306 
TabDragData()307 DraggedTabController::TabDragData::TabDragData()
308     : contents(NULL),
309       original_delegate(NULL),
310       source_model_index(-1),
311       attached_tab(NULL),
312       pinned(false) {
313 }
314 
~TabDragData()315 DraggedTabController::TabDragData::~TabDragData() {
316 }
317 
318 ///////////////////////////////////////////////////////////////////////////////
319 // DraggedTabController, public:
320 
DraggedTabController()321 DraggedTabController::DraggedTabController()
322     : source_tabstrip_(NULL),
323       attached_tabstrip_(NULL),
324       source_tab_offset_(0),
325       offset_to_width_ratio_(0),
326       old_focused_view_(NULL),
327       last_move_screen_loc_(0),
328       started_drag_(false),
329       active_(true),
330       source_tab_index_(std::numeric_limits<size_t>::max()),
331       initial_move_(true) {
332   instance_ = this;
333 }
334 
~DraggedTabController()335 DraggedTabController::~DraggedTabController() {
336   if (instance_ == this)
337     instance_ = NULL;
338 
339   MessageLoopForUI::current()->RemoveObserver(this);
340   // Need to delete the view here manually _before_ we reset the dragged
341   // contents to NULL, otherwise if the view is animating to its destination
342   // bounds, it won't be able to clean up properly since its cleanup routine
343   // uses GetIndexForDraggedContents, which will be invalid.
344   view_.reset(NULL);
345 
346   // Reset the delegate of the dragged TabContents. This ends up doing nothing
347   // if the drag was completed.
348   ResetDelegates();
349 }
350 
Init(BaseTabStrip * source_tabstrip,BaseTab * source_tab,const std::vector<BaseTab * > & tabs,const gfx::Point & mouse_offset,int source_tab_offset)351 void DraggedTabController::Init(BaseTabStrip* source_tabstrip,
352                                 BaseTab* source_tab,
353                                 const std::vector<BaseTab*>& tabs,
354                                 const gfx::Point& mouse_offset,
355                                 int source_tab_offset) {
356   DCHECK(!tabs.empty());
357   DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
358   source_tabstrip_ = source_tabstrip;
359   source_tab_offset_ = source_tab_offset;
360   start_screen_point_ = GetCursorScreenPoint();
361   mouse_offset_ = mouse_offset;
362 
363   drag_data_.resize(tabs.size());
364   for (size_t i = 0; i < tabs.size(); ++i)
365     InitTabDragData(tabs[i], &(drag_data_[i]));
366   source_tab_index_ =
367       std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
368 
369   // Listen for Esc key presses.
370   MessageLoopForUI::current()->AddObserver(this);
371 
372   if (source_tab->width() > 0) {
373     offset_to_width_ratio_ = static_cast<float>(source_tab_offset_) /
374         static_cast<float>(source_tab->width());
375   }
376   InitWindowCreatePoint();
377 }
378 
379 // static
IsAttachedTo(BaseTabStrip * tab_strip)380 bool DraggedTabController::IsAttachedTo(BaseTabStrip* tab_strip) {
381   return instance_ && instance_->active_ &&
382       instance_->attached_tabstrip_ == tab_strip;
383 }
384 
Drag()385 void DraggedTabController::Drag() {
386   bring_to_front_timer_.Stop();
387 
388   if (!started_drag_) {
389     if (!CanStartDrag())
390       return;  // User hasn't dragged far enough yet.
391 
392     started_drag_ = true;
393     SaveFocus();
394     Attach(source_tabstrip_, gfx::Point());
395   }
396 
397   ContinueDragging();
398 }
399 
EndDrag(bool canceled)400 void DraggedTabController::EndDrag(bool canceled) {
401   EndDragImpl(canceled ? CANCELED : NORMAL);
402 }
403 
InitTabDragData(BaseTab * tab,TabDragData * drag_data)404 void DraggedTabController::InitTabDragData(BaseTab* tab,
405                                            TabDragData* drag_data) {
406   drag_data->source_model_index =
407       source_tabstrip_->GetModelIndexOfBaseTab(tab);
408   drag_data->contents = GetModel(source_tabstrip_)->GetTabContentsAt(
409       drag_data->source_model_index);
410   drag_data->pinned = source_tabstrip_->IsTabPinned(tab);
411   registrar_.Add(this,
412                  NotificationType::TAB_CONTENTS_DESTROYED,
413                  Source<TabContents>(drag_data->contents->tab_contents()));
414 
415   // We need to be the delegate so we receive messages about stuff, otherwise
416   // our dragged TabContents may be replaced and subsequently
417   // collected/destroyed while the drag is in process, leading to nasty crashes.
418   drag_data->original_delegate =
419       drag_data->contents->tab_contents()->delegate();
420   drag_data->contents->tab_contents()->set_delegate(this);
421 }
422 
423 ///////////////////////////////////////////////////////////////////////////////
424 // DraggedTabController, PageNavigator implementation:
425 
OpenURLFromTab(TabContents * source,const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)426 void DraggedTabController::OpenURLFromTab(TabContents* source,
427                                           const GURL& url,
428                                           const GURL& referrer,
429                                           WindowOpenDisposition disposition,
430                                           PageTransition::Type transition) {
431   if (source_tab_drag_data()->original_delegate) {
432     if (disposition == CURRENT_TAB)
433       disposition = NEW_WINDOW;
434 
435     source_tab_drag_data()->original_delegate->OpenURLFromTab(
436         source, url, referrer, disposition, transition);
437   }
438 }
439 
440 ///////////////////////////////////////////////////////////////////////////////
441 // DraggedTabController, TabContentsDelegate implementation:
442 
NavigationStateChanged(const TabContents * source,unsigned changed_flags)443 void DraggedTabController::NavigationStateChanged(const TabContents* source,
444                                                   unsigned changed_flags) {
445   if (view_.get())
446     view_->Update();
447 }
448 
AddNewContents(TabContents * source,TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)449 void DraggedTabController::AddNewContents(TabContents* source,
450                                           TabContents* new_contents,
451                                           WindowOpenDisposition disposition,
452                                           const gfx::Rect& initial_pos,
453                                           bool user_gesture) {
454   DCHECK_NE(CURRENT_TAB, disposition);
455 
456   // Theoretically could be called while dragging if the page tries to
457   // spawn a window. Route this message back to the browser in most cases.
458   if (source_tab_drag_data()->original_delegate) {
459     source_tab_drag_data()->original_delegate->AddNewContents(
460         source, new_contents, disposition, initial_pos, user_gesture);
461   }
462 }
463 
ActivateContents(TabContents * contents)464 void DraggedTabController::ActivateContents(TabContents* contents) {
465   // Ignored.
466 }
467 
DeactivateContents(TabContents * contents)468 void DraggedTabController::DeactivateContents(TabContents* contents) {
469   // Ignored.
470 }
471 
LoadingStateChanged(TabContents * source)472 void DraggedTabController::LoadingStateChanged(TabContents* source) {
473   // It would be nice to respond to this message by changing the
474   // screen shot in the dragged tab.
475   if (view_.get())
476     view_->Update();
477 }
478 
CloseContents(TabContents * source)479 void DraggedTabController::CloseContents(TabContents* source) {
480   // Theoretically could be called by a window. Should be ignored
481   // because window.close() is ignored (usually, even though this
482   // method gets called.)
483 }
484 
MoveContents(TabContents * source,const gfx::Rect & pos)485 void DraggedTabController::MoveContents(TabContents* source,
486                                         const gfx::Rect& pos) {
487   // Theoretically could be called by a web page trying to move its
488   // own window. Should be ignored since we're moving the window...
489 }
490 
UpdateTargetURL(TabContents * source,const GURL & url)491 void DraggedTabController::UpdateTargetURL(TabContents* source,
492                                            const GURL& url) {
493   // Ignored.
494 }
495 
ShouldSuppressDialogs()496 bool DraggedTabController::ShouldSuppressDialogs() {
497   // When a dialog is about to be shown we revert the drag. Otherwise a modal
498   // dialog might appear and attempt to parent itself to a hidden tabcontents.
499   EndDragImpl(CANCELED);
500   return false;
501 }
502 
503 ///////////////////////////////////////////////////////////////////////////////
504 // DraggedTabController, NotificationObserver implementation:
505 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)506 void DraggedTabController::Observe(NotificationType type,
507                                    const NotificationSource& source,
508                                    const NotificationDetails& details) {
509   DCHECK_EQ(type.value, NotificationType::TAB_CONTENTS_DESTROYED);
510   TabContents* destroyed_contents = Source<TabContents>(source).ptr();
511   for (size_t i = 0; i < drag_data_.size(); ++i) {
512     if (drag_data_[i].contents->tab_contents() == destroyed_contents) {
513       // One of the tabs we're dragging has been destroyed. Cancel the drag.
514       if (destroyed_contents->delegate() == this)
515         destroyed_contents->set_delegate(NULL);
516       drag_data_[i].contents = NULL;
517       drag_data_[i].original_delegate = NULL;
518       EndDragImpl(TAB_DESTROYED);
519       return;
520     }
521   }
522   // If we get here it means we got notification for a tab we don't know about.
523   NOTREACHED();
524 }
525 
526 ///////////////////////////////////////////////////////////////////////////////
527 // DraggedTabController, MessageLoop::Observer implementation:
528 
529 #if defined(OS_WIN)
WillProcessMessage(const MSG & msg)530 void DraggedTabController::WillProcessMessage(const MSG& msg) {
531 }
532 
DidProcessMessage(const MSG & msg)533 void DraggedTabController::DidProcessMessage(const MSG& msg) {
534   // If the user presses ESC during a drag, we need to abort and revert things
535   // to the way they were. This is the most reliable way to do this since no
536   // single view or window reliably receives events throughout all the various
537   // kinds of tab dragging.
538   if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
539     EndDrag(true);
540 }
541 #else
WillProcessEvent(GdkEvent * event)542 void DraggedTabController::WillProcessEvent(GdkEvent* event) {
543 }
544 
DidProcessEvent(GdkEvent * event)545 void DraggedTabController::DidProcessEvent(GdkEvent* event) {
546   if (event->type == GDK_KEY_PRESS &&
547       reinterpret_cast<GdkEventKey*>(event)->keyval == GDK_Escape) {
548     EndDrag(true);
549   }
550 }
551 #endif
552 
553 ///////////////////////////////////////////////////////////////////////////////
554 // DraggedTabController, private:
555 
InitWindowCreatePoint()556 void DraggedTabController::InitWindowCreatePoint() {
557   // window_create_point_ is only used in CompleteDrag() (through
558   // GetWindowCreatePoint() to get the start point of the docked window) when
559   // the attached_tabstrip_ is NULL and all the window's related bound
560   // information are obtained from source_tabstrip_. So, we need to get the
561   // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise,
562   // the window_create_point_ is not in the correct coordinate system. Please
563   // refer to http://crbug.com/6223 comment #15 for detailed information.
564   views::View* first_tab = source_tabstrip_->base_tab_at_tab_index(0);
565   views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);
566   window_create_point_ = first_source_tab_point_;
567   window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());
568 }
569 
GetWindowCreatePoint() const570 gfx::Point DraggedTabController::GetWindowCreatePoint() const {
571   gfx::Point cursor_point = GetCursorScreenPoint();
572   if (dock_info_.type() != DockInfo::NONE && dock_info_.in_enable_area()) {
573     // If we're going to dock, we need to return the exact coordinate,
574     // otherwise we may attempt to maximize on the wrong monitor.
575     return cursor_point;
576   }
577   // If the cursor is outside the monitor area, move it inside. For example,
578   // dropping a tab onto the task bar on Windows produces this situation.
579   gfx::Rect work_area = views::Screen::GetMonitorWorkAreaNearestPoint(
580       cursor_point);
581   if (!work_area.IsEmpty()) {
582     if (cursor_point.x() < work_area.x())
583       cursor_point.set_x(work_area.x());
584     else if (cursor_point.x() > work_area.right())
585       cursor_point.set_x(work_area.right());
586     if (cursor_point.y() < work_area.y())
587       cursor_point.set_y(work_area.y());
588     else if (cursor_point.y() > work_area.bottom())
589       cursor_point.set_y(work_area.bottom());
590   }
591   return gfx::Point(cursor_point.x() - window_create_point_.x(),
592                     cursor_point.y() - window_create_point_.y());
593 }
594 
UpdateDockInfo(const gfx::Point & screen_point)595 void DraggedTabController::UpdateDockInfo(const gfx::Point& screen_point) {
596   // Update the DockInfo for the current mouse coordinates.
597   DockInfo dock_info = GetDockInfoAtPoint(screen_point);
598   if (source_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP &&
599       ((dock_info.type() == DockInfo::LEFT_OF_WINDOW &&
600         !base::i18n::IsRTL()) ||
601        (dock_info.type() == DockInfo::RIGHT_OF_WINDOW &&
602         base::i18n::IsRTL()))) {
603     // For side tabs it's way to easy to trigger to docking along the left/right
604     // edge, so we disable it.
605     dock_info = DockInfo();
606   }
607   if (!dock_info.equals(dock_info_)) {
608     // DockInfo for current position differs.
609     if (dock_info_.type() != DockInfo::NONE &&
610         !dock_controllers_.empty()) {
611       // Hide old visual indicator.
612       dock_controllers_.back()->Hide();
613     }
614     dock_info_ = dock_info;
615     if (dock_info_.type() != DockInfo::NONE) {
616       // Show new docking position.
617       DockDisplayer* controller = new DockDisplayer(this, dock_info_);
618       if (controller->popup_view()) {
619         dock_controllers_.push_back(controller);
620         dock_windows_.insert(controller->popup_view());
621       } else {
622         delete controller;
623       }
624     }
625   } else if (dock_info_.type() != DockInfo::NONE &&
626              !dock_controllers_.empty()) {
627     // Current dock position is the same as last, update the controller's
628     // in_enable_area state as it may have changed.
629     dock_controllers_.back()->UpdateInEnabledArea(dock_info_.in_enable_area());
630   }
631 }
632 
SaveFocus()633 void DraggedTabController::SaveFocus() {
634   DCHECK(!old_focused_view_);  // This should only be invoked once.
635   old_focused_view_ = source_tabstrip_->GetFocusManager()->GetFocusedView();
636   source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_);
637 }
638 
RestoreFocus()639 void DraggedTabController::RestoreFocus() {
640   if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
641     old_focused_view_->GetFocusManager()->SetFocusedView(old_focused_view_);
642   old_focused_view_ = NULL;
643 }
644 
CanStartDrag() const645 bool DraggedTabController::CanStartDrag() const {
646   // Determine if the mouse has moved beyond a minimum elasticity distance in
647   // any direction from the starting point.
648   static const int kMinimumDragDistance = 10;
649   gfx::Point screen_point = GetCursorScreenPoint();
650   int x_offset = abs(screen_point.x() - start_screen_point_.x());
651   int y_offset = abs(screen_point.y() - start_screen_point_.y());
652   return sqrt(pow(static_cast<float>(x_offset), 2) +
653               pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
654 }
655 
ContinueDragging()656 void DraggedTabController::ContinueDragging() {
657   // Note that the coordinates given to us by |drag_event| are basically
658   // useless, since they're in source_tab_ coordinates. On the surface, you'd
659   // think we could just convert them to screen coordinates, however in the
660   // situation where we're dragging the last tab in a window when multiple
661   // windows are open, the coordinates of |source_tab_| are way off in
662   // hyperspace since the window was moved there instead of being closed so
663   // that we'd keep receiving events. And our ConvertPointToScreen methods
664   // aren't really multi-screen aware. So really it's just safer to get the
665   // actual position of the mouse cursor directly from Windows here, which is
666   // guaranteed to be correct regardless of monitor config.
667   gfx::Point screen_point = GetCursorScreenPoint();
668 
669 #if defined(OS_LINUX)
670   // We don't allow detaching in chrome os.
671   BaseTabStrip* target_tabstrip = source_tabstrip_;
672 #else
673   // Determine whether or not we have dragged over a compatible TabStrip in
674   // another browser window. If we have, we should attach to it and start
675   // dragging within it.
676   BaseTabStrip* target_tabstrip = GetTabStripForPoint(screen_point);
677 #endif
678   if (target_tabstrip != attached_tabstrip_) {
679     // Make sure we're fully detached from whatever TabStrip we're attached to
680     // (if any).
681     if (attached_tabstrip_)
682       Detach();
683     if (target_tabstrip)
684       Attach(target_tabstrip, screen_point);
685   }
686   if (!target_tabstrip) {
687     bring_to_front_timer_.Start(
688         base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this,
689         &DraggedTabController::BringWindowUnderMouseToFront);
690   }
691 
692   UpdateDockInfo(screen_point);
693 
694   if (attached_tabstrip_)
695     MoveAttached(screen_point);
696   else
697     MoveDetached(screen_point);
698 }
699 
MoveAttached(const gfx::Point & screen_point)700 void DraggedTabController::MoveAttached(const gfx::Point& screen_point) {
701   DCHECK(attached_tabstrip_);
702   DCHECK(!view_.get());
703 
704   gfx::Point dragged_view_point = GetAttachedDragPoint(screen_point);
705 
706   int threshold = kVerticalMoveThreshold;
707   if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
708     TabStrip* tab_strip = static_cast<TabStrip*>(attached_tabstrip_);
709 
710     // Determine the horizontal move threshold. This is dependent on the width
711     // of tabs. The smaller the tabs compared to the standard size, the smaller
712     // the threshold.
713     double unselected, selected;
714     tab_strip->GetCurrentTabWidths(&unselected, &selected);
715     double ratio = unselected / Tab::GetStandardSize().width();
716     threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
717   }
718 
719   std::vector<BaseTab*> tabs(drag_data_.size());
720   for (size_t i = 0; i < drag_data_.size(); ++i)
721     tabs[i] = drag_data_[i].attached_tab;
722 
723   bool did_layout = false;
724   // Update the model, moving the TabContents from one index to another. Do this
725   // only if we have moved a minimum distance since the last reorder (to prevent
726   // jitter) or if this the first move and the tabs are not consecutive.
727   if (abs(MajorAxisValue(screen_point, attached_tabstrip_) -
728           last_move_screen_loc_) > threshold ||
729       (initial_move_ && !AreTabsConsecutive())) {
730     TabStripModel* attached_model = GetModel(attached_tabstrip_);
731     gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
732     int to_index = GetInsertionIndexForDraggedBounds(bounds);
733     TabContentsWrapper* last_contents =
734         drag_data_[drag_data_.size() - 1].contents;
735     int index_of_last_item =
736           attached_model->GetIndexOfTabContents(last_contents);
737     if (initial_move_) {
738       // TabStrip determines if the tabs needs to be animated based on model
739       // position. This means we need to invoke LayoutDraggedTabsAt before
740       // changing the model.
741       attached_tabstrip_->LayoutDraggedTabsAt(
742           tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
743           initial_move_);
744       did_layout = true;
745     }
746     attached_model->MoveSelectedTabsTo(to_index);
747     // Move may do nothing in certain situations (such as when dragging pinned
748     // tabs). Make sure the tabstrip actually changed before updating
749     // last_move_screen_loc_.
750     if (index_of_last_item !=
751         attached_model->GetIndexOfTabContents(last_contents)) {
752       last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip_);
753     }
754   }
755 
756   if (!did_layout) {
757     attached_tabstrip_->LayoutDraggedTabsAt(
758         tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
759         initial_move_);
760   }
761 
762   initial_move_ = false;
763 }
764 
MoveDetached(const gfx::Point & screen_point)765 void DraggedTabController::MoveDetached(const gfx::Point& screen_point) {
766   DCHECK(!attached_tabstrip_);
767   DCHECK(view_.get());
768 
769   // Move the View. There are no changes to the model if we're detached.
770   view_->MoveTo(screen_point);
771 }
772 
GetDockInfoAtPoint(const gfx::Point & screen_point)773 DockInfo DraggedTabController::GetDockInfoAtPoint(
774     const gfx::Point& screen_point) {
775   if (attached_tabstrip_) {
776     // If the mouse is over a tab strip, don't offer a dock position.
777     return DockInfo();
778   }
779 
780   if (dock_info_.IsValidForPoint(screen_point)) {
781     // It's possible any given screen coordinate has multiple docking
782     // positions. Check the current info first to avoid having the docking
783     // position bounce around.
784     return dock_info_;
785   }
786 
787   gfx::NativeView dragged_hwnd = view_->GetWidget()->GetNativeView();
788   dock_windows_.insert(dragged_hwnd);
789   DockInfo info = DockInfo::GetDockInfoAtPoint(screen_point, dock_windows_);
790   dock_windows_.erase(dragged_hwnd);
791   return info;
792 }
793 
GetTabStripForPoint(const gfx::Point & screen_point)794 BaseTabStrip* DraggedTabController::GetTabStripForPoint(
795     const gfx::Point& screen_point) {
796   gfx::NativeView dragged_view = NULL;
797   if (view_.get()) {
798     dragged_view = view_->GetWidget()->GetNativeView();
799     dock_windows_.insert(dragged_view);
800   }
801   gfx::NativeWindow local_window =
802       DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_);
803   if (dragged_view)
804     dock_windows_.erase(dragged_view);
805   if (!local_window)
806     return NULL;
807   BrowserView* browser =
808       BrowserView::GetBrowserViewForNativeWindow(local_window);
809   // We don't allow drops on windows that don't have tabstrips.
810   if (!browser ||
811       !browser->browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP))
812     return NULL;
813 
814   // This cast seems ugly, but the controller and the view are tighly coupled at
815   // creation time, so it will be okay.
816   BaseTabStrip* other_tabstrip =
817       static_cast<BaseTabStrip*>(browser->tabstrip());
818 
819   if (!other_tabstrip->controller()->IsCompatibleWith(source_tabstrip_))
820     return NULL;
821   return GetTabStripIfItContains(other_tabstrip, screen_point);
822 }
823 
GetTabStripIfItContains(BaseTabStrip * tabstrip,const gfx::Point & screen_point) const824 BaseTabStrip* DraggedTabController::GetTabStripIfItContains(
825     BaseTabStrip* tabstrip,
826     const gfx::Point& screen_point) const {
827   static const int kVerticalDetachMagnetism = 15;
828   static const int kHorizontalDetachMagnetism = 15;
829   // Make sure the specified screen point is actually within the bounds of the
830   // specified tabstrip...
831   gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
832   if (tabstrip->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
833     if (screen_point.x() < tabstrip_bounds.right() &&
834         screen_point.x() >= tabstrip_bounds.x()) {
835       // TODO(beng): make this be relative to the start position of the mouse
836       // for the source TabStrip.
837       int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
838       int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
839       if (screen_point.y() >= lower_threshold &&
840           screen_point.y() <= upper_threshold) {
841         return tabstrip;
842       }
843     }
844   } else {
845     if (screen_point.y() < tabstrip_bounds.bottom() &&
846         screen_point.y() >= tabstrip_bounds.y()) {
847       int upper_threshold = tabstrip_bounds.right() +
848           kHorizontalDetachMagnetism;
849       int lower_threshold = tabstrip_bounds.x() - kHorizontalDetachMagnetism;
850       if (screen_point.x() >= lower_threshold &&
851           screen_point.x() <= upper_threshold) {
852         return tabstrip;
853       }
854     }
855   }
856   return NULL;
857 }
858 
Attach(BaseTabStrip * attached_tabstrip,const gfx::Point & screen_point)859 void DraggedTabController::Attach(BaseTabStrip* attached_tabstrip,
860                                   const gfx::Point& screen_point) {
861   DCHECK(!attached_tabstrip_);  // We should already have detached by the time
862                                 // we get here.
863 
864   attached_tabstrip_ = attached_tabstrip;
865 
866   // And we don't need the dragged view.
867   view_.reset();
868 
869   std::vector<BaseTab*> tabs =
870       GetTabsMatchingDraggedContents(attached_tabstrip_);
871 
872   if (tabs.empty()) {
873     // There is no Tab in |attached_tabstrip| that corresponds to the dragged
874     // TabContents. We must now create one.
875 
876     // Remove ourselves as the delegate now that the dragged TabContents is
877     // being inserted back into a Browser.
878     for (size_t i = 0; i < drag_data_.size(); ++i) {
879       drag_data_[i].contents->tab_contents()->set_delegate(NULL);
880       drag_data_[i].original_delegate = NULL;
881     }
882 
883     // Return the TabContents' to normalcy.
884     source_dragged_contents()->tab_contents()->set_capturing_contents(false);
885 
886     // Inserting counts as a move. We don't want the tabs to jitter when the
887     // user moves the tab immediately after attaching it.
888     last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip);
889 
890     // Figure out where to insert the tab based on the bounds of the dragged
891     // representation and the ideal bounds of the other Tabs already in the
892     // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
893     // changing due to animation).
894     gfx::Point tab_strip_point(screen_point);
895     views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_strip_point);
896     tab_strip_point.set_x(
897         attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()));
898     tab_strip_point.Offset(-mouse_offset_.x(), -mouse_offset_.y());
899     gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point);
900     int index = GetInsertionIndexForDraggedBounds(bounds);
901     attached_tabstrip_->set_attaching_dragged_tab(true);
902     for (size_t i = 0; i < drag_data_.size(); ++i) {
903       int add_types = TabStripModel::ADD_NONE;
904       if (drag_data_[i].pinned)
905         add_types |= TabStripModel::ADD_PINNED;
906       GetModel(attached_tabstrip_)->InsertTabContentsAt(
907           index + i, drag_data_[i].contents, add_types);
908     }
909     attached_tabstrip_->set_attaching_dragged_tab(false);
910 
911     tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
912   }
913   DCHECK_EQ(tabs.size(), drag_data_.size());
914   for (size_t i = 0; i < drag_data_.size(); ++i)
915     drag_data_[i].attached_tab = tabs[i];
916 
917   attached_tabstrip_->StartedDraggingTabs(tabs);
918 
919   ResetSelection(GetModel(attached_tabstrip_));
920 
921   if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
922     // The size of the dragged tab may have changed. Adjust the x offset so that
923     // ratio of mouse_offset_ to original width is maintained.
924     std::vector<BaseTab*> tabs_to_source(tabs);
925     tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1,
926                          tabs_to_source.end());
927     int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) -
928         tabs[source_tab_index_]->width() +
929         static_cast<int>(offset_to_width_ratio_ *
930                          tabs[source_tab_index_]->width());
931     mouse_offset_.set_x(new_x);
932   }
933 
934   // Move the corresponding window to the front.
935   attached_tabstrip_->GetWindow()->Activate();
936 }
937 
Detach()938 void DraggedTabController::Detach() {
939   // Prevent the TabContents' HWND from being hidden by any of the model
940   // operations performed during the drag.
941   source_dragged_contents()->tab_contents()->set_capturing_contents(true);
942 
943   // Calculate the drag bounds.
944   std::vector<gfx::Rect> drag_bounds;
945   std::vector<BaseTab*> attached_tabs;
946   for (size_t i = 0; i < drag_data_.size(); ++i)
947     attached_tabs.push_back(drag_data_[i].attached_tab);
948   attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs,
949                                                     &drag_bounds);
950 
951   TabStripModel* attached_model = GetModel(attached_tabstrip_);
952   std::vector<TabRendererData> tab_data;
953   for (size_t i = 0; i < drag_data_.size(); ++i) {
954     tab_data.push_back(drag_data_[i].attached_tab->data());
955     int index = attached_model->GetIndexOfTabContents(drag_data_[i].contents);
956     DCHECK_NE(-1, index);
957 
958     // Hide the tab so that the user doesn't see it animate closed.
959     drag_data_[i].attached_tab->SetVisible(false);
960 
961     attached_model->DetachTabContentsAt(index);
962 
963     // Detaching resets the delegate, but we still want to be the delegate.
964     drag_data_[i].contents->tab_contents()->set_delegate(this);
965 
966     // Detaching may end up deleting the tab, drop references to it.
967     drag_data_[i].attached_tab = NULL;
968   }
969 
970   // If we've removed the last Tab from the TabStrip, hide the frame now.
971   if (attached_model->empty())
972     HideFrame();
973 
974   // Create the dragged view.
975   CreateDraggedView(tab_data, drag_bounds);
976 
977   attached_tabstrip_ = NULL;
978 }
979 
GetInsertionIndexForDraggedBounds(const gfx::Rect & dragged_bounds) const980 int DraggedTabController::GetInsertionIndexForDraggedBounds(
981     const gfx::Rect& dragged_bounds) const {
982   int right_tab_x = 0;
983   int bottom_tab_y = 0;
984   int index = -1;
985   for (int i = 0; i < attached_tabstrip_->tab_count(); ++i) {
986     const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i);
987     if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
988       gfx::Rect left_half = ideal_bounds;
989       left_half.set_width(left_half.width() / 2);
990       gfx::Rect right_half = ideal_bounds;
991       right_half.set_width(ideal_bounds.width() - left_half.width());
992       right_half.set_x(left_half.right());
993       right_tab_x = right_half.right();
994       if (dragged_bounds.x() >= right_half.x() &&
995           dragged_bounds.x() < right_half.right()) {
996         index = i + 1;
997         break;
998       } else if (dragged_bounds.x() >= left_half.x() &&
999                  dragged_bounds.x() < left_half.right()) {
1000         index = i;
1001         break;
1002       }
1003     } else {
1004       // Vertical tab strip.
1005       int max_y = ideal_bounds.bottom();
1006       int mid_y = ideal_bounds.y() + ideal_bounds.height() / 2;
1007       bottom_tab_y = max_y;
1008       if (dragged_bounds.y() < mid_y) {
1009         index = i;
1010         break;
1011       } else if (dragged_bounds.y() >= mid_y && dragged_bounds.y() < max_y) {
1012         index = i + 1;
1013         break;
1014       }
1015     }
1016   }
1017   if (index == -1) {
1018     if ((attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP &&
1019          dragged_bounds.right() > right_tab_x) ||
1020         (attached_tabstrip_->type() == BaseTabStrip::VERTICAL_TAB_STRIP &&
1021          dragged_bounds.y() >= bottom_tab_y)) {
1022       index = GetModel(attached_tabstrip_)->count();
1023     } else {
1024       index = 0;
1025     }
1026   }
1027 
1028   if (!drag_data_[0].attached_tab) {
1029     // If 'attached_tab' is NULL, it means we're in the process of attaching and
1030     // don't need to constrain the index.
1031     return index;
1032   }
1033 
1034   int max_index = GetModel(attached_tabstrip_)->count() -
1035       static_cast<int>(drag_data_.size());
1036   return std::max(0, std::min(max_index, index));
1037 }
1038 
GetDraggedViewTabStripBounds(const gfx::Point & tab_strip_point)1039 gfx::Rect DraggedTabController::GetDraggedViewTabStripBounds(
1040     const gfx::Point& tab_strip_point) {
1041   // attached_tab is NULL when inserting into a new tabstrip.
1042   if (source_tab_drag_data()->attached_tab) {
1043     return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1044                      source_tab_drag_data()->attached_tab->width(),
1045                      source_tab_drag_data()->attached_tab->height());
1046   }
1047 
1048   if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
1049     double sel_width, unselected_width;
1050     static_cast<TabStrip*>(attached_tabstrip_)->GetCurrentTabWidths(
1051         &sel_width, &unselected_width);
1052     return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1053                      static_cast<int>(sel_width),
1054                      Tab::GetStandardSize().height());
1055   }
1056 
1057   return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1058                    attached_tabstrip_->width(),
1059                    SideTab::GetPreferredHeight());
1060 }
1061 
GetAttachedDragPoint(const gfx::Point & screen_point)1062 gfx::Point DraggedTabController::GetAttachedDragPoint(
1063     const gfx::Point& screen_point) {
1064   DCHECK(attached_tabstrip_);  // The tab must be attached.
1065 
1066   gfx::Point tab_loc(screen_point);
1067   views::View::ConvertPointToView(NULL, attached_tabstrip_, &tab_loc);
1068   int x =
1069       attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
1070   int y = tab_loc.y() - mouse_offset_.y();
1071 
1072   // TODO: consider caching this.
1073   std::vector<BaseTab*> attached_tabs;
1074   for (size_t i = 0; i < drag_data_.size(); ++i)
1075     attached_tabs.push_back(drag_data_[i].attached_tab);
1076 
1077   int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs);
1078 
1079   if (attached_tabstrip_->type() == BaseTabStrip::HORIZONTAL_TAB_STRIP) {
1080     int max_x = attached_tabstrip_->width() - size;
1081     x = std::min(std::max(x, 0), max_x);
1082     y = 0;
1083   } else {
1084     x = SideTabStrip::kTabStripInset;
1085     int max_y = attached_tabstrip_->height() - size;
1086     y = std::min(std::max(y, SideTabStrip::kTabStripInset), max_y);
1087   }
1088   return gfx::Point(x, y);
1089 }
1090 
GetTabsMatchingDraggedContents(BaseTabStrip * tabstrip)1091 std::vector<BaseTab*> DraggedTabController::GetTabsMatchingDraggedContents(
1092     BaseTabStrip* tabstrip) {
1093   TabStripModel* model = GetModel(attached_tabstrip_);
1094   std::vector<BaseTab*> tabs;
1095   for (size_t i = 0; i < drag_data_.size(); ++i) {
1096     int model_index = model->GetIndexOfTabContents(drag_data_[i].contents);
1097     if (model_index == TabStripModel::kNoTab)
1098       return std::vector<BaseTab*>();
1099     tabs.push_back(tabstrip->GetBaseTabAtModelIndex(model_index));
1100   }
1101   return tabs;
1102 }
1103 
EndDragImpl(EndDragType type)1104 void DraggedTabController::EndDragImpl(EndDragType type) {
1105   active_ = false;
1106 
1107   bring_to_front_timer_.Stop();
1108 
1109   // Hide the current dock controllers.
1110   for (size_t i = 0; i < dock_controllers_.size(); ++i) {
1111     // Be sure and clear the controller first, that way if Hide ends up
1112     // deleting the controller it won't call us back.
1113     dock_controllers_[i]->clear_controller();
1114     dock_controllers_[i]->Hide();
1115   }
1116   dock_controllers_.clear();
1117   dock_windows_.clear();
1118 
1119   if (type != TAB_DESTROYED) {
1120     // We only finish up the drag if we were actually dragging. If start_drag_
1121     // is false, the user just clicked and released and didn't move the mouse
1122     // enough to trigger a drag.
1123     if (started_drag_) {
1124       RestoreFocus();
1125       if (type == CANCELED)
1126         RevertDrag();
1127       else
1128         CompleteDrag();
1129     }
1130   } else if (drag_data_.size() > 1) {
1131     RevertDrag();
1132   }  // else case the only tab we were dragging was deleted. Nothing to do.
1133 
1134   ResetDelegates();
1135 
1136   // Clear out drag data so we don't attempt to do anything with it.
1137   drag_data_.clear();
1138 
1139   source_tabstrip_->DestroyDragController();
1140 }
1141 
RevertDrag()1142 void DraggedTabController::RevertDrag() {
1143   std::vector<BaseTab*> tabs;
1144   for (size_t i = 0; i < drag_data_.size(); ++i) {
1145     if (drag_data_[i].contents) {
1146       // Contents is NULL if a tab was destroyed while the drag was under way.
1147       tabs.push_back(drag_data_[i].attached_tab);
1148       RevertDragAt(i);
1149     }
1150   }
1151 
1152   bool restore_frame = attached_tabstrip_ != source_tabstrip_;
1153   if (attached_tabstrip_ && attached_tabstrip_ == source_tabstrip_)
1154     source_tabstrip_->StoppedDraggingTabs(tabs);
1155 
1156   attached_tabstrip_ = source_tabstrip_;
1157 
1158   ResetSelection(GetModel(attached_tabstrip_));
1159 
1160   // If we're not attached to any TabStrip, or attached to some other TabStrip,
1161   // we need to restore the bounds of the original TabStrip's frame, in case
1162   // it has been hidden.
1163   if (restore_frame) {
1164     if (!restore_bounds_.IsEmpty()) {
1165 #if defined(OS_WIN)
1166       HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView();
1167       MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(),
1168                  restore_bounds_.width(), restore_bounds_.height(), TRUE);
1169 #else
1170       NOTIMPLEMENTED();
1171 #endif
1172     }
1173   }
1174 }
1175 
ResetSelection(TabStripModel * model)1176 void DraggedTabController::ResetSelection(TabStripModel* model) {
1177   DCHECK(model);
1178   TabStripSelectionModel selection_model;
1179   bool has_one_valid_tab = false;
1180   for (size_t i = 0; i < drag_data_.size(); ++i) {
1181     // |contents| is NULL if a tab was deleted out from under us.
1182     if (drag_data_[i].contents) {
1183       int index = model->GetIndexOfTabContents(drag_data_[i].contents);
1184       DCHECK_NE(-1, index);
1185       selection_model.AddIndexToSelection(index);
1186       if (!has_one_valid_tab || i == source_tab_index_) {
1187         // Reset the active/lead to the first tab. If the source tab is still
1188         // valid we'll reset these again later on.
1189         selection_model.set_active(index);
1190         selection_model.set_anchor(index);
1191         has_one_valid_tab = true;
1192       }
1193     }
1194   }
1195   if (!has_one_valid_tab)
1196     return;
1197 
1198   model->SetSelectionFromModel(selection_model);
1199 }
1200 
RevertDragAt(size_t drag_index)1201 void DraggedTabController::RevertDragAt(size_t drag_index) {
1202   DCHECK(started_drag_);
1203 
1204   TabDragData* data = &(drag_data_[drag_index]);
1205   if (attached_tabstrip_) {
1206     int index =
1207         GetModel(attached_tabstrip_)->GetIndexOfTabContents(data->contents);
1208     if (attached_tabstrip_ != source_tabstrip_) {
1209       // The Tab was inserted into another TabStrip. We need to put it back
1210       // into the original one.
1211       GetModel(attached_tabstrip_)->DetachTabContentsAt(index);
1212       // TODO(beng): (Cleanup) seems like we should use Attach() for this
1213       //             somehow.
1214       GetModel(source_tabstrip_)->InsertTabContentsAt(
1215           data->source_model_index, data->contents,
1216           (data->pinned ? TabStripModel::ADD_PINNED : 0));
1217     } else {
1218       // The Tab was moved within the TabStrip where the drag was initiated.
1219       // Move it back to the starting location.
1220       GetModel(source_tabstrip_)->MoveTabContentsAt(
1221           index, data->source_model_index, false);
1222     }
1223   } else {
1224     // The Tab was detached from the TabStrip where the drag began, and has not
1225     // been attached to any other TabStrip. We need to put it back into the
1226     // source TabStrip.
1227     GetModel(source_tabstrip_)->InsertTabContentsAt(
1228         data->source_model_index, data->contents,
1229         (data->pinned ? TabStripModel::ADD_PINNED : 0));
1230   }
1231 }
1232 
CompleteDrag()1233 void DraggedTabController::CompleteDrag() {
1234   DCHECK(started_drag_);
1235 
1236   if (attached_tabstrip_) {
1237     attached_tabstrip_->StoppedDraggingTabs(
1238         GetTabsMatchingDraggedContents(attached_tabstrip_));
1239   } else {
1240     if (dock_info_.type() != DockInfo::NONE) {
1241       Profile* profile = GetModel(source_tabstrip_)->profile();
1242       switch (dock_info_.type()) {
1243         case DockInfo::LEFT_OF_WINDOW:
1244           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Left"),
1245                                     profile);
1246           break;
1247 
1248         case DockInfo::RIGHT_OF_WINDOW:
1249           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Right"),
1250                                     profile);
1251           break;
1252 
1253         case DockInfo::BOTTOM_OF_WINDOW:
1254           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Bottom"),
1255                                     profile);
1256           break;
1257 
1258         case DockInfo::TOP_OF_WINDOW:
1259           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Top"),
1260                                     profile);
1261           break;
1262 
1263         case DockInfo::MAXIMIZE:
1264           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_Maximize"),
1265                                     profile);
1266           break;
1267 
1268         case DockInfo::LEFT_HALF:
1269           UserMetrics::RecordAction(UserMetricsAction("DockingWindow_LeftHalf"),
1270                                     profile);
1271           break;
1272 
1273         case DockInfo::RIGHT_HALF:
1274           UserMetrics::RecordAction(
1275               UserMetricsAction("DockingWindow_RightHalf"),
1276               profile);
1277           break;
1278 
1279         case DockInfo::BOTTOM_HALF:
1280           UserMetrics::RecordAction(
1281               UserMetricsAction("DockingWindow_BottomHalf"),
1282               profile);
1283           break;
1284 
1285         default:
1286           NOTREACHED();
1287           break;
1288       }
1289     }
1290     // Compel the model to construct a new window for the detached TabContents.
1291     views::Window* window = source_tabstrip_->GetWindow();
1292     gfx::Rect window_bounds(window->GetNormalBounds());
1293     window_bounds.set_origin(GetWindowCreatePoint());
1294     // When modifying the following if statement, please make sure not to
1295     // introduce issue listed in http://crbug.com/6223 comment #11.
1296     bool rtl_ui = base::i18n::IsRTL();
1297     bool has_dock_position = (dock_info_.type() != DockInfo::NONE);
1298     if (rtl_ui && has_dock_position) {
1299       // Mirror X axis so the docked tab is aligned using the mouse click as
1300       // the top-right corner.
1301       window_bounds.set_x(window_bounds.x() - window_bounds.width());
1302     }
1303     Browser* new_browser =
1304         GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents(
1305             drag_data_[0].contents, window_bounds, dock_info_,
1306             window->IsMaximized());
1307     TabStripModel* new_model = new_browser->tabstrip_model();
1308     new_model->SetTabPinned(
1309         new_model->GetIndexOfTabContents(drag_data_[0].contents),
1310         drag_data_[0].pinned);
1311     for (size_t i = 1; i < drag_data_.size(); ++i) {
1312       new_model->InsertTabContentsAt(
1313           static_cast<int>(i),
1314           drag_data_[i].contents,
1315           drag_data_[i].pinned ? TabStripModel::ADD_PINNED :
1316                                  TabStripModel::ADD_NONE);
1317     }
1318     ResetSelection(new_browser->tabstrip_model());
1319     new_browser->window()->Show();
1320   }
1321 
1322   CleanUpHiddenFrame();
1323 }
1324 
ResetDelegates()1325 void DraggedTabController::ResetDelegates() {
1326   for (size_t i = 0; i < drag_data_.size(); ++i) {
1327     if (drag_data_[i].contents &&
1328         drag_data_[i].contents->tab_contents()->delegate() == this) {
1329       drag_data_[i].contents->tab_contents()->set_delegate(
1330           drag_data_[i].original_delegate);
1331     }
1332   }
1333 }
1334 
CreateDraggedView(const std::vector<TabRendererData> & data,const std::vector<gfx::Rect> & renderer_bounds)1335 void DraggedTabController::CreateDraggedView(
1336     const std::vector<TabRendererData>& data,
1337     const std::vector<gfx::Rect>& renderer_bounds) {
1338   DCHECK(!view_.get());
1339   DCHECK_EQ(data.size(), drag_data_.size());
1340 
1341   // Set up the photo booth to start capturing the contents of the dragged
1342   // TabContents.
1343   NativeViewPhotobooth* photobooth =
1344       NativeViewPhotobooth::Create(
1345           source_dragged_contents()->tab_contents()->GetNativeView());
1346 
1347   gfx::Rect content_bounds;
1348   source_dragged_contents()->tab_contents()->GetContainerBounds(
1349       &content_bounds);
1350 
1351   std::vector<views::View*> renderers;
1352   for (size_t i = 0; i < drag_data_.size(); ++i) {
1353     BaseTab* renderer = source_tabstrip_->CreateTabForDragging();
1354     renderer->SetData(data[i]);
1355     renderers.push_back(renderer);
1356   }
1357   // DraggedTabView takes ownership of the renderers.
1358   view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_,
1359                                  content_bounds.size(), photobooth));
1360 }
1361 
GetCursorScreenPoint() const1362 gfx::Point DraggedTabController::GetCursorScreenPoint() const {
1363 #if defined(OS_WIN)
1364   DWORD pos = GetMessagePos();
1365   return gfx::Point(pos);
1366 #else
1367   gint x, y;
1368   gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL);
1369   return gfx::Point(x, y);
1370 #endif
1371 }
1372 
GetViewScreenBounds(views::View * view) const1373 gfx::Rect DraggedTabController::GetViewScreenBounds(views::View* view) const {
1374   gfx::Point view_topleft;
1375   views::View::ConvertPointToScreen(view, &view_topleft);
1376   gfx::Rect view_screen_bounds = view->GetLocalBounds();
1377   view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());
1378   return view_screen_bounds;
1379 }
1380 
HideFrame()1381 void DraggedTabController::HideFrame() {
1382 #if defined(OS_WIN)
1383   // We don't actually hide the window, rather we just move it way off-screen.
1384   // If we actually hide it, we stop receiving drag events.
1385   HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView();
1386   RECT wr;
1387   GetWindowRect(frame_hwnd, &wr);
1388   MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left,
1389              wr.bottom - wr.top, TRUE);
1390 
1391   // We also save the bounds of the window prior to it being moved, so that if
1392   // the drag session is aborted we can restore them.
1393   restore_bounds_ = gfx::Rect(wr);
1394 #else
1395   NOTIMPLEMENTED();
1396 #endif
1397 }
1398 
CleanUpHiddenFrame()1399 void DraggedTabController::CleanUpHiddenFrame() {
1400   // If the model we started dragging from is now empty, we must ask the
1401   // delegate to close the frame.
1402   if (GetModel(source_tabstrip_)->empty())
1403     GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession();
1404 }
1405 
DockDisplayerDestroyed(DockDisplayer * controller)1406 void DraggedTabController::DockDisplayerDestroyed(
1407     DockDisplayer* controller) {
1408   DockWindows::iterator dock_i =
1409       dock_windows_.find(controller->popup_view());
1410   if (dock_i != dock_windows_.end())
1411     dock_windows_.erase(dock_i);
1412   else
1413     NOTREACHED();
1414 
1415   std::vector<DockDisplayer*>::iterator i =
1416       std::find(dock_controllers_.begin(), dock_controllers_.end(),
1417                 controller);
1418   if (i != dock_controllers_.end())
1419     dock_controllers_.erase(i);
1420   else
1421     NOTREACHED();
1422 }
1423 
BringWindowUnderMouseToFront()1424 void DraggedTabController::BringWindowUnderMouseToFront() {
1425   // If we're going to dock to another window, bring it to the front.
1426   gfx::NativeWindow window = dock_info_.window();
1427   if (!window) {
1428     gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView();
1429     dock_windows_.insert(dragged_view);
1430     window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(),
1431                                                     dock_windows_);
1432     dock_windows_.erase(dragged_view);
1433   }
1434   if (window) {
1435 #if defined(OS_WIN)
1436     // Move the window to the front.
1437     SetWindowPos(window, HWND_TOP, 0, 0, 0, 0,
1438                  SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
1439 
1440     // The previous call made the window appear on top of the dragged window,
1441     // move the dragged window to the front.
1442     SetWindowPos(view_->GetWidget()->GetNativeView(), HWND_TOP, 0, 0, 0, 0,
1443                  SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
1444 #else
1445     NOTIMPLEMENTED();
1446 #endif
1447   }
1448 }
1449 
GetModel(BaseTabStrip * tabstrip) const1450 TabStripModel* DraggedTabController::GetModel(BaseTabStrip* tabstrip) const {
1451   return static_cast<BrowserTabStripController*>(tabstrip->controller())->
1452       model();
1453 }
1454 
AreTabsConsecutive()1455 bool DraggedTabController::AreTabsConsecutive() {
1456   for (size_t i = 1; i < drag_data_.size(); ++i) {
1457     if (drag_data_[i - 1].source_model_index + 1 !=
1458         drag_data_[i].source_model_index) {
1459       return false;
1460     }
1461   }
1462   return true;
1463 }
1464