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