Refactor views app list services to allow more code sharing
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_drag_controller.cc
blob624c229e9b3ea5500cc39705dbb42f2b90efc8e6
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
7 #include <math.h>
8 #include <set>
10 #include "base/auto_reset.h"
11 #include "base/callback.h"
12 #include "base/command_line.h"
13 #include "base/i18n/rtl.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h"
17 #include "chrome/browser/ui/browser_list.h"
18 #include "chrome/browser/ui/browser_window.h"
19 #include "chrome/browser/ui/media_utils.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
22 #include "chrome/browser/ui/views/frame/browser_view.h"
23 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
24 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
25 #include "chrome/browser/ui/views/tabs/tab.h"
26 #include "chrome/browser/ui/views/tabs/tab_strip.h"
27 #include "chrome/browser/ui/views/tabs/window_finder.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "content/public/browser/invalidate_type.h"
30 #include "content/public/browser/notification_details.h"
31 #include "content/public/browser/notification_service.h"
32 #include "content/public/browser/notification_source.h"
33 #include "content/public/browser/notification_types.h"
34 #include "content/public/browser/user_metrics.h"
35 #include "content/public/browser/web_contents.h"
36 #include "content/public/browser/web_contents_view.h"
37 #include "extensions/browser/extension_function_dispatcher.h"
38 #include "ui/aura/env.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/events/event_constants.h"
41 #include "ui/events/event_utils.h"
42 #include "ui/gfx/geometry/point_conversions.h"
43 #include "ui/gfx/screen.h"
44 #include "ui/views/focus/view_storage.h"
45 #include "ui/views/widget/root_view.h"
46 #include "ui/views/widget/widget.h"
47 #include "ui/wm/core/window_modality_controller.h"
49 #if defined(USE_ASH)
50 #include "ash/accelerators/accelerator_commands.h"
51 #include "ash/wm/coordinate_conversion.h"
52 #include "ash/wm/window_state.h"
53 #include "ui/aura/env.h"
54 #include "ui/aura/window.h"
55 #include "ui/aura/window_event_dispatcher.h"
56 #include "ui/events/gestures/gesture_recognizer.h"
57 #endif
59 #if defined(OS_WIN)
60 #include "ui/aura/window.h"
61 #include "ui/events/gestures/gesture_recognizer.h"
62 #endif
64 using base::UserMetricsAction;
65 using content::OpenURLParams;
66 using content::WebContents;
68 // If non-null there is a drag underway.
69 static TabDragController* instance_ = NULL;
71 namespace {
73 // Delay, in ms, during dragging before we bring a window to front.
74 const int kBringToFrontDelay = 750;
76 // Initial delay before moving tabs when the dragged tab is close to the edge of
77 // the stacked tabs.
78 const int kMoveAttachedInitialDelay = 600;
80 // Delay for moving tabs after the initial delay has passed.
81 const int kMoveAttachedSubsequentDelay = 300;
83 const int kHorizontalMoveThreshold = 16; // Pixels.
85 // Distance from the next/previous stacked before before we consider the tab
86 // close enough to trigger moving.
87 const int kStackedDistance = 36;
89 void SetWindowPositionManaged(gfx::NativeWindow window, bool value) {
90 #if defined(USE_ASH)
91 ash::wm::GetWindowState(window)->set_window_position_managed(value);
92 #endif
95 // Returns true if |tab_strip| browser window is docked.
96 bool IsDockedOrSnapped(const TabStrip* tab_strip) {
97 #if defined(USE_ASH)
98 DCHECK(tab_strip);
99 ash::wm::WindowState* window_state =
100 ash::wm::GetWindowState(tab_strip->GetWidget()->GetNativeWindow());
101 return window_state->IsDocked() || window_state->IsSnapped();
102 #else
103 return false;
104 #endif
107 // Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate
108 // of |bounds| is adjusted by |vertical_adjustment|.
109 bool DoesRectContainVerticalPointExpanded(
110 const gfx::Rect& bounds,
111 int vertical_adjustment,
112 int y) {
113 int upper_threshold = bounds.bottom() + vertical_adjustment;
114 int lower_threshold = bounds.y() - vertical_adjustment;
115 return y >= lower_threshold && y <= upper_threshold;
118 // Adds |x_offset| to all the rectangles in |rects|.
119 void OffsetX(int x_offset, std::vector<gfx::Rect>* rects) {
120 if (x_offset == 0)
121 return;
123 for (size_t i = 0; i < rects->size(); ++i)
124 (*rects)[i].set_x((*rects)[i].x() + x_offset);
127 // WidgetObserver implementation that resets the window position managed
128 // property on Show.
129 // We're forced to do this here since BrowserFrameAsh resets the 'window
130 // position managed' property during a show and we need the property set to
131 // false before WorkspaceLayoutManager sees the visibility change.
132 class WindowPositionManagedUpdater : public views::WidgetObserver {
133 public:
134 virtual void OnWidgetVisibilityChanged(views::Widget* widget,
135 bool visible) OVERRIDE {
136 SetWindowPositionManaged(widget->GetNativeView(), false);
140 // EscapeTracker installs itself as a pre-target handler on aura::Env and runs a
141 // callback when it receives the escape key.
142 class EscapeTracker : public ui::EventHandler {
143 public:
144 explicit EscapeTracker(const base::Closure& callback)
145 : escape_callback_(callback) {
146 aura::Env::GetInstance()->AddPreTargetHandler(this);
149 virtual ~EscapeTracker() {
150 aura::Env::GetInstance()->RemovePreTargetHandler(this);
153 private:
154 // ui::EventHandler:
155 virtual void OnKeyEvent(ui::KeyEvent* key) OVERRIDE {
156 if (key->type() == ui::ET_KEY_PRESSED &&
157 key->key_code() == ui::VKEY_ESCAPE) {
158 escape_callback_.Run();
162 base::Closure escape_callback_;
164 DISALLOW_COPY_AND_ASSIGN(EscapeTracker);
167 } // namespace
169 TabDragController::TabDragData::TabDragData()
170 : contents(NULL),
171 original_delegate(NULL),
172 source_model_index(-1),
173 attached_tab(NULL),
174 pinned(false) {
177 TabDragController::TabDragData::~TabDragData() {
180 ///////////////////////////////////////////////////////////////////////////////
181 // TabDragController, public:
183 // static
184 const int TabDragController::kTouchVerticalDetachMagnetism = 50;
186 // static
187 const int TabDragController::kVerticalDetachMagnetism = 15;
189 TabDragController::TabDragController()
190 : detach_into_browser_(true),
191 event_source_(EVENT_SOURCE_MOUSE),
192 source_tabstrip_(NULL),
193 attached_tabstrip_(NULL),
194 screen_(NULL),
195 host_desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE),
196 use_aura_capture_policy_(false),
197 offset_to_width_ratio_(0),
198 old_focused_view_id_(
199 views::ViewStorage::GetInstance()->CreateStorageID()),
200 last_move_screen_loc_(0),
201 started_drag_(false),
202 active_(true),
203 source_tab_index_(std::numeric_limits<size_t>::max()),
204 initial_move_(true),
205 detach_behavior_(DETACHABLE),
206 move_behavior_(REORDER),
207 mouse_move_direction_(0),
208 is_dragging_window_(false),
209 is_dragging_new_browser_(false),
210 was_source_maximized_(false),
211 was_source_fullscreen_(false),
212 did_restore_window_(false),
213 end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING),
214 waiting_for_run_loop_to_exit_(false),
215 tab_strip_to_attach_to_after_exit_(NULL),
216 move_loop_widget_(NULL),
217 is_mutating_(false),
218 attach_x_(-1),
219 attach_index_(-1),
220 weak_factory_(this) {
221 instance_ = this;
224 TabDragController::~TabDragController() {
225 views::ViewStorage::GetInstance()->RemoveView(old_focused_view_id_);
227 if (instance_ == this)
228 instance_ = NULL;
230 if (move_loop_widget_) {
231 move_loop_widget_->RemoveObserver(this);
232 SetWindowPositionManaged(move_loop_widget_->GetNativeView(), true);
235 if (source_tabstrip_ && detach_into_browser_)
236 GetModel(source_tabstrip_)->RemoveObserver(this);
238 // Reset the delegate of the dragged WebContents. This ends up doing nothing
239 // if the drag was completed.
240 if (!detach_into_browser_)
241 ResetDelegates();
243 if (event_source_ == EVENT_SOURCE_TOUCH) {
244 TabStrip* capture_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
245 attached_tabstrip_ : source_tabstrip_;
246 capture_tabstrip->GetWidget()->ReleaseCapture();
250 void TabDragController::Init(
251 TabStrip* source_tabstrip,
252 Tab* source_tab,
253 const std::vector<Tab*>& tabs,
254 const gfx::Point& mouse_offset,
255 int source_tab_offset,
256 const ui::ListSelectionModel& initial_selection_model,
257 DetachBehavior detach_behavior,
258 MoveBehavior move_behavior,
259 EventSource event_source) {
260 DCHECK(!tabs.empty());
261 DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
262 source_tabstrip_ = source_tabstrip;
263 was_source_maximized_ = source_tabstrip->GetWidget()->IsMaximized();
264 was_source_fullscreen_ = source_tabstrip->GetWidget()->IsFullscreen();
265 screen_ = gfx::Screen::GetScreenFor(
266 source_tabstrip->GetWidget()->GetNativeView());
267 host_desktop_type_ = chrome::GetHostDesktopTypeForNativeView(
268 source_tabstrip->GetWidget()->GetNativeView());
269 #if defined(OS_LINUX)
270 use_aura_capture_policy_ = true;
271 #else
272 use_aura_capture_policy_ =
273 (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH);
274 #endif
275 start_point_in_screen_ = gfx::Point(source_tab_offset, mouse_offset.y());
276 views::View::ConvertPointToScreen(source_tab, &start_point_in_screen_);
277 event_source_ = event_source;
278 mouse_offset_ = mouse_offset;
279 detach_behavior_ = detach_behavior;
280 move_behavior_ = move_behavior;
281 last_point_in_screen_ = start_point_in_screen_;
282 last_move_screen_loc_ = start_point_in_screen_.x();
283 initial_tab_positions_ = source_tabstrip->GetTabXCoordinates();
284 if (detach_behavior == NOT_DETACHABLE)
285 detach_into_browser_ = false;
287 if (detach_into_browser_)
288 GetModel(source_tabstrip_)->AddObserver(this);
290 drag_data_.resize(tabs.size());
291 for (size_t i = 0; i < tabs.size(); ++i)
292 InitTabDragData(tabs[i], &(drag_data_[i]));
293 source_tab_index_ =
294 std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
296 // Listen for Esc key presses.
297 escape_tracker_.reset(
298 new EscapeTracker(base::Bind(&TabDragController::EndDrag,
299 weak_factory_.GetWeakPtr(),
300 END_DRAG_CANCEL)));
302 if (source_tab->width() > 0) {
303 offset_to_width_ratio_ = static_cast<float>(
304 source_tab->GetMirroredXInView(source_tab_offset)) /
305 static_cast<float>(source_tab->width());
307 InitWindowCreatePoint();
308 initial_selection_model_.Copy(initial_selection_model);
310 // Gestures don't automatically do a capture. We don't allow multiple drags at
311 // the same time, so we explicitly capture.
312 if (event_source == EVENT_SOURCE_TOUCH)
313 source_tabstrip_->GetWidget()->SetCapture(source_tabstrip_);
316 // static
317 bool TabDragController::IsAttachedTo(const TabStrip* tab_strip) {
318 return (instance_ && instance_->active() &&
319 instance_->attached_tabstrip() == tab_strip);
322 // static
323 bool TabDragController::IsActive() {
324 return instance_ && instance_->active();
327 void TabDragController::SetMoveBehavior(MoveBehavior behavior) {
328 if (started_drag())
329 return;
331 move_behavior_ = behavior;
334 void TabDragController::Drag(const gfx::Point& point_in_screen) {
335 TRACE_EVENT1("views", "TabDragController::Drag",
336 "point_in_screen", point_in_screen.ToString());
338 bring_to_front_timer_.Stop();
339 move_stacked_timer_.Stop();
341 if (waiting_for_run_loop_to_exit_)
342 return;
344 if (!started_drag_) {
345 if (!CanStartDrag(point_in_screen))
346 return; // User hasn't dragged far enough yet.
348 // On windows SaveFocus() may trigger a capture lost, which destroys us.
350 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
351 SaveFocus();
352 if (!ref)
353 return;
355 started_drag_ = true;
356 Attach(source_tabstrip_, gfx::Point());
357 if (detach_into_browser_ && static_cast<int>(drag_data_.size()) ==
358 GetModel(source_tabstrip_)->count()) {
359 if (was_source_maximized_ || was_source_fullscreen_) {
360 did_restore_window_ = true;
361 // When all tabs in a maximized browser are dragged the browser gets
362 // restored during the drag and maximized back when the drag ends.
363 views::Widget* widget = GetAttachedBrowserWidget();
364 const int last_tabstrip_width = attached_tabstrip_->tab_area_width();
365 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
366 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);
367 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source_tabstrip_,
368 point_in_screen,
369 &drag_bounds));
370 new_bounds.Offset(-widget->GetRestoredBounds().x() +
371 point_in_screen.x() -
372 mouse_offset_.x(), 0);
373 widget->SetVisibilityChangedAnimationsEnabled(false);
374 widget->Restore();
375 widget->SetBounds(new_bounds);
376 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width,
377 point_in_screen,
378 &drag_bounds);
379 widget->SetVisibilityChangedAnimationsEnabled(true);
381 RunMoveLoop(GetWindowOffset(point_in_screen));
382 return;
386 ContinueDragging(point_in_screen);
389 void TabDragController::EndDrag(EndDragReason reason) {
390 TRACE_EVENT0("views", "TabDragController::EndDrag");
392 // If we're dragging a window ignore capture lost since it'll ultimately
393 // trigger the move loop to end and we'll revert the drag when RunMoveLoop()
394 // finishes.
395 if (reason == END_DRAG_CAPTURE_LOST && is_dragging_window_)
396 return;
397 EndDragImpl(reason != END_DRAG_COMPLETE && source_tabstrip_ ?
398 CANCELED : NORMAL);
401 void TabDragController::InitTabDragData(Tab* tab,
402 TabDragData* drag_data) {
403 TRACE_EVENT0("views", "TabDragController::InitTabDragData");
404 drag_data->source_model_index =
405 source_tabstrip_->GetModelIndexOfTab(tab);
406 drag_data->contents = GetModel(source_tabstrip_)->GetWebContentsAt(
407 drag_data->source_model_index);
408 drag_data->pinned = source_tabstrip_->IsTabPinned(tab);
409 registrar_.Add(
410 this,
411 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
412 content::Source<WebContents>(drag_data->contents));
414 if (!detach_into_browser_) {
415 drag_data->original_delegate = drag_data->contents->GetDelegate();
416 drag_data->contents->SetDelegate(this);
420 ///////////////////////////////////////////////////////////////////////////////
421 // TabDragController, PageNavigator implementation:
423 WebContents* TabDragController::OpenURLFromTab(
424 WebContents* source,
425 const OpenURLParams& params) {
426 if (source_tab_drag_data()->original_delegate) {
427 OpenURLParams forward_params = params;
428 if (params.disposition == CURRENT_TAB)
429 forward_params.disposition = NEW_WINDOW;
431 return source_tab_drag_data()->original_delegate->OpenURLFromTab(
432 source, forward_params);
434 return NULL;
437 ///////////////////////////////////////////////////////////////////////////////
438 // TabDragController, content::WebContentsDelegate implementation:
440 void TabDragController::NavigationStateChanged(const WebContents* source,
441 unsigned changed_flags) {
442 if (attached_tabstrip_ ||
443 changed_flags == content::INVALIDATE_TYPE_PAGE_ACTIONS) {
444 for (size_t i = 0; i < drag_data_.size(); ++i) {
445 if (drag_data_[i].contents == source) {
446 // Pass the NavigationStateChanged call to the original delegate so
447 // that the title is updated. Do this only when we are attached as
448 // otherwise the Tab isn't in the TabStrip (except for page action
449 // updates).
450 drag_data_[i].original_delegate->NavigationStateChanged(source,
451 changed_flags);
452 break;
458 void TabDragController::AddNewContents(WebContents* source,
459 WebContents* new_contents,
460 WindowOpenDisposition disposition,
461 const gfx::Rect& initial_pos,
462 bool user_gesture,
463 bool* was_blocked) {
464 DCHECK_NE(CURRENT_TAB, disposition);
466 // Theoretically could be called while dragging if the page tries to
467 // spawn a window. Route this message back to the browser in most cases.
468 if (source_tab_drag_data()->original_delegate) {
469 source_tab_drag_data()->original_delegate->AddNewContents(
470 source, new_contents, disposition, initial_pos, user_gesture,
471 was_blocked);
475 bool TabDragController::ShouldSuppressDialogs() {
476 // When a dialog is about to be shown we revert the drag. Otherwise a modal
477 // dialog might appear and attempt to parent itself to a hidden tabcontents.
478 EndDragImpl(CANCELED);
479 return false;
482 content::JavaScriptDialogManager*
483 TabDragController::GetJavaScriptDialogManager() {
484 return GetJavaScriptDialogManagerInstance();
487 void TabDragController::RequestMediaAccessPermission(
488 content::WebContents* web_contents,
489 const content::MediaStreamRequest& request,
490 const content::MediaResponseCallback& callback) {
491 ::RequestMediaAccessPermission(
492 web_contents,
493 Profile::FromBrowserContext(web_contents->GetBrowserContext()),
494 request,
495 callback);
498 ///////////////////////////////////////////////////////////////////////////////
499 // TabDragController, content::NotificationObserver implementation:
501 void TabDragController::Observe(
502 int type,
503 const content::NotificationSource& source,
504 const content::NotificationDetails& details) {
505 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
506 WebContents* destroyed_web_contents =
507 content::Source<WebContents>(source).ptr();
508 for (size_t i = 0; i < drag_data_.size(); ++i) {
509 if (drag_data_[i].contents == destroyed_web_contents) {
510 // One of the tabs we're dragging has been destroyed. Cancel the drag.
511 if (destroyed_web_contents->GetDelegate() == this)
512 destroyed_web_contents->SetDelegate(NULL);
513 drag_data_[i].contents = NULL;
514 drag_data_[i].original_delegate = NULL;
515 EndDragImpl(TAB_DESTROYED);
516 return;
519 // If we get here it means we got notification for a tab we don't know about.
520 NOTREACHED();
523 void TabDragController::OnWidgetBoundsChanged(views::Widget* widget,
524 const gfx::Rect& new_bounds) {
525 TRACE_EVENT1("views", "TabDragController::OnWidgetBoundsChanged",
526 "new_bounds", new_bounds.ToString());
528 Drag(GetCursorScreenPoint());
531 void TabDragController::TabStripEmpty() {
532 DCHECK(detach_into_browser_);
533 GetModel(source_tabstrip_)->RemoveObserver(this);
534 // NULL out source_tabstrip_ so that we don't attempt to add back to it (in
535 // the case of a revert).
536 source_tabstrip_ = NULL;
539 ///////////////////////////////////////////////////////////////////////////////
540 // TabDragController, private:
542 void TabDragController::InitWindowCreatePoint() {
543 // window_create_point_ is only used in CompleteDrag() (through
544 // GetWindowCreatePoint() to get the start point of the docked window) when
545 // the attached_tabstrip_ is NULL and all the window's related bound
546 // information are obtained from source_tabstrip_. So, we need to get the
547 // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise,
548 // the window_create_point_ is not in the correct coordinate system. Please
549 // refer to http://crbug.com/6223 comment #15 for detailed information.
550 views::View* first_tab = source_tabstrip_->tab_at(0);
551 views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);
552 window_create_point_ = first_source_tab_point_;
553 window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());
556 gfx::Point TabDragController::GetWindowCreatePoint(
557 const gfx::Point& origin) const {
558 // If the cursor is outside the monitor area, move it inside. For example,
559 // dropping a tab onto the task bar on Windows produces this situation.
560 gfx::Rect work_area = screen_->GetDisplayNearestPoint(origin).work_area();
561 gfx::Point create_point(origin);
562 if (!work_area.IsEmpty()) {
563 if (create_point.x() < work_area.x())
564 create_point.set_x(work_area.x());
565 else if (create_point.x() > work_area.right())
566 create_point.set_x(work_area.right());
567 if (create_point.y() < work_area.y())
568 create_point.set_y(work_area.y());
569 else if (create_point.y() > work_area.bottom())
570 create_point.set_y(work_area.bottom());
572 return gfx::Point(create_point.x() - window_create_point_.x(),
573 create_point.y() - window_create_point_.y());
576 void TabDragController::SaveFocus() {
577 DCHECK(source_tabstrip_);
578 views::View* focused_view =
579 source_tabstrip_->GetFocusManager()->GetFocusedView();
580 if (focused_view)
581 views::ViewStorage::GetInstance()->StoreView(old_focused_view_id_,
582 focused_view);
583 source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_);
584 // WARNING: we may have been deleted.
587 void TabDragController::RestoreFocus() {
588 if (attached_tabstrip_ != source_tabstrip_) {
589 if (is_dragging_new_browser_) {
590 content::WebContents* active_contents = source_dragged_contents();
591 if (active_contents && !active_contents->FocusLocationBarByDefault())
592 active_contents->GetView()->Focus();
594 return;
596 views::View* old_focused_view =
597 views::ViewStorage::GetInstance()->RetrieveView(old_focused_view_id_);
598 if (!old_focused_view)
599 return;
600 old_focused_view->GetFocusManager()->SetFocusedView(old_focused_view);
603 bool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const {
604 // Determine if the mouse has moved beyond a minimum elasticity distance in
605 // any direction from the starting point.
606 static const int kMinimumDragDistance = 10;
607 int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x());
608 int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y());
609 return sqrt(pow(static_cast<float>(x_offset), 2) +
610 pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
613 void TabDragController::ContinueDragging(const gfx::Point& point_in_screen) {
614 TRACE_EVENT1("views", "TabDragController::ContinueDragging",
615 "point_in_screen", point_in_screen.ToString());
617 DCHECK(!detach_into_browser_ || attached_tabstrip_);
619 TabStrip* target_tabstrip = detach_behavior_ == DETACHABLE ?
620 GetTargetTabStripForPoint(point_in_screen) : source_tabstrip_;
621 bool tab_strip_changed = (target_tabstrip != attached_tabstrip_);
623 if (attached_tabstrip_) {
624 int move_delta = point_in_screen.x() - last_point_in_screen_.x();
625 if (move_delta > 0)
626 mouse_move_direction_ |= kMovedMouseRight;
627 else if (move_delta < 0)
628 mouse_move_direction_ |= kMovedMouseLeft;
630 last_point_in_screen_ = point_in_screen;
632 if (tab_strip_changed) {
633 is_dragging_new_browser_ = false;
634 did_restore_window_ = false;
635 if (detach_into_browser_ &&
636 DragBrowserToNewTabStrip(target_tabstrip, point_in_screen) ==
637 DRAG_BROWSER_RESULT_STOP) {
638 return;
639 } else if (!detach_into_browser_) {
640 if (attached_tabstrip_)
641 Detach(RELEASE_CAPTURE);
642 if (target_tabstrip)
643 Attach(target_tabstrip, point_in_screen);
646 if (is_dragging_window_) {
647 static_cast<base::Timer*>(&bring_to_front_timer_)->Start(FROM_HERE,
648 base::TimeDelta::FromMilliseconds(kBringToFrontDelay),
649 base::Bind(&TabDragController::BringWindowUnderPointToFront,
650 base::Unretained(this), point_in_screen));
653 if (!is_dragging_window_ && attached_tabstrip_) {
654 if (move_only()) {
655 DragActiveTabStacked(point_in_screen);
656 } else {
657 MoveAttached(point_in_screen);
658 if (tab_strip_changed) {
659 // Move the corresponding window to the front. We do this after the
660 // move as on windows activate triggers a synchronous paint.
661 attached_tabstrip_->GetWidget()->Activate();
667 TabDragController::DragBrowserResultType
668 TabDragController::DragBrowserToNewTabStrip(
669 TabStrip* target_tabstrip,
670 const gfx::Point& point_in_screen) {
671 TRACE_EVENT1("views", "TabDragController::DragBrowserToNewTabStrip",
672 "point_in_screen", point_in_screen.ToString());
674 if (!target_tabstrip) {
675 DetachIntoNewBrowserAndRunMoveLoop(point_in_screen);
676 return DRAG_BROWSER_RESULT_STOP;
678 if (is_dragging_window_) {
679 // ReleaseCapture() is going to result in calling back to us (because it
680 // results in a move). That'll cause all sorts of problems. Reset the
681 // observer so we don't get notified and process the event.
682 if (use_aura_capture_policy_) {
683 move_loop_widget_->RemoveObserver(this);
684 move_loop_widget_ = NULL;
686 views::Widget* browser_widget = GetAttachedBrowserWidget();
687 // Need to release the drag controller before starting the move loop as it's
688 // going to trigger capture lost, which cancels drag.
689 attached_tabstrip_->ReleaseDragController();
690 target_tabstrip->OwnDragController(this);
691 // Disable animations so that we don't see a close animation on aero.
692 browser_widget->SetVisibilityChangedAnimationsEnabled(false);
693 // For aura we can't release capture, otherwise it'll cancel a gesture.
694 // Instead we have to directly change capture.
695 if (use_aura_capture_policy_)
696 target_tabstrip->GetWidget()->SetCapture(attached_tabstrip_);
697 else
698 browser_widget->ReleaseCapture();
699 #if defined(OS_WIN)
700 // The Gesture recognizer does not work well currently when capture changes
701 // while a touch gesture is in progress. So we need to manually transfer
702 // gesture sequence and the GR's touch events queue to the new window. This
703 // should really be done somewhere in capture change code and or inside the
704 // GR. But we currently do not have a consistent way for doing it that would
705 // work in all cases. Hence this hack.
706 ui::GestureRecognizer::Get()->TransferEventsTo(
707 browser_widget->GetNativeView(),
708 target_tabstrip->GetWidget()->GetNativeView());
709 #endif
711 // The window is going away. Since the drag is still on going we don't want
712 // that to effect the position of any windows.
713 SetWindowPositionManaged(browser_widget->GetNativeView(), false);
715 #if !defined(OS_LINUX) || defined(OS_CHROMEOS)
716 // EndMoveLoop is going to snap the window back to its original location.
717 // Hide it so users don't see this. Hiding a window in Linux aura causes
718 // it to lose capture so skip it.
719 browser_widget->Hide();
720 #endif
721 browser_widget->EndMoveLoop();
723 // Ideally we would always swap the tabs now, but on non-ash it seems that
724 // running the move loop implicitly activates the window when done, leading
725 // to all sorts of flicker. So, on non-ash, instead we process the move
726 // after the loop completes. But on chromeos, we can do tab swapping now to
727 // avoid the tab flashing issue(crbug.com/116329).
728 if (use_aura_capture_policy_) {
729 is_dragging_window_ = false;
730 Detach(DONT_RELEASE_CAPTURE);
731 Attach(target_tabstrip, point_in_screen);
732 // Move the tabs into position.
733 MoveAttached(point_in_screen);
734 attached_tabstrip_->GetWidget()->Activate();
735 } else {
736 tab_strip_to_attach_to_after_exit_ = target_tabstrip;
739 waiting_for_run_loop_to_exit_ = true;
740 end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING;
741 return DRAG_BROWSER_RESULT_STOP;
743 Detach(DONT_RELEASE_CAPTURE);
744 Attach(target_tabstrip, point_in_screen);
745 return DRAG_BROWSER_RESULT_CONTINUE;
748 void TabDragController::DragActiveTabStacked(
749 const gfx::Point& point_in_screen) {
750 if (attached_tabstrip_->tab_count() !=
751 static_cast<int>(initial_tab_positions_.size()))
752 return; // TODO: should cancel drag if this happens.
754 int delta = point_in_screen.x() - start_point_in_screen_.x();
755 attached_tabstrip_->DragActiveTab(initial_tab_positions_, delta);
758 void TabDragController::MoveAttachedToNextStackedIndex(
759 const gfx::Point& point_in_screen) {
760 int index = attached_tabstrip_->touch_layout_->active_index();
761 if (index + 1 >= attached_tabstrip_->tab_count())
762 return;
764 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index + 1);
765 StartMoveStackedTimerIfNecessary(point_in_screen,
766 kMoveAttachedSubsequentDelay);
769 void TabDragController::MoveAttachedToPreviousStackedIndex(
770 const gfx::Point& point_in_screen) {
771 int index = attached_tabstrip_->touch_layout_->active_index();
772 if (index <= attached_tabstrip_->GetMiniTabCount())
773 return;
775 GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index - 1);
776 StartMoveStackedTimerIfNecessary(point_in_screen,
777 kMoveAttachedSubsequentDelay);
780 void TabDragController::MoveAttached(const gfx::Point& point_in_screen) {
781 DCHECK(attached_tabstrip_);
782 DCHECK(!is_dragging_window_);
784 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
786 // Determine the horizontal move threshold. This is dependent on the width
787 // of tabs. The smaller the tabs compared to the standard size, the smaller
788 // the threshold.
789 int threshold = kHorizontalMoveThreshold;
790 if (!attached_tabstrip_->touch_layout_.get()) {
791 double unselected, selected;
792 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
793 double ratio = unselected / Tab::GetStandardSize().width();
794 threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
796 // else case: touch tabs never shrink.
798 std::vector<Tab*> tabs(drag_data_.size());
799 for (size_t i = 0; i < drag_data_.size(); ++i)
800 tabs[i] = drag_data_[i].attached_tab;
802 bool did_layout = false;
803 // Update the model, moving the WebContents from one index to another. Do this
804 // only if we have moved a minimum distance since the last reorder (to prevent
805 // jitter) or if this the first move and the tabs are not consecutive.
806 if ((abs(point_in_screen.x() - last_move_screen_loc_) > threshold ||
807 (initial_move_ && !AreTabsConsecutive()))) {
808 TabStripModel* attached_model = GetModel(attached_tabstrip_);
809 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
810 int to_index = GetInsertionIndexForDraggedBounds(bounds);
811 bool do_move = true;
812 // While dragging within a tabstrip the expectation is the insertion index
813 // is based on the left edge of the tabs being dragged. OTOH when dragging
814 // into a new tabstrip (attaching) the expectation is the insertion index is
815 // based on the cursor. This proves problematic as insertion may change the
816 // size of the tabs, resulting in the index calculated before the insert
817 // differing from the index calculated after the insert. To alleviate this
818 // the index is chosen before insertion, and subsequently a new index is
819 // only used once the mouse moves enough such that the index changes based
820 // on the direction the mouse moved relative to |attach_x_| (smaller
821 // x-coordinate should yield a smaller index or larger x-coordinate yields a
822 // larger index).
823 if (attach_index_ != -1) {
824 gfx::Point tab_strip_point(point_in_screen);
825 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point);
826 const int new_x =
827 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x());
828 if (new_x < attach_x_)
829 to_index = std::min(to_index, attach_index_);
830 else
831 to_index = std::max(to_index, attach_index_);
832 if (to_index != attach_index_)
833 attach_index_ = -1; // Once a valid move is detected, don't constrain.
834 else
835 do_move = false;
837 if (do_move) {
838 WebContents* last_contents = drag_data_[drag_data_.size() - 1].contents;
839 int index_of_last_item =
840 attached_model->GetIndexOfWebContents(last_contents);
841 if (initial_move_) {
842 // TabStrip determines if the tabs needs to be animated based on model
843 // position. This means we need to invoke LayoutDraggedTabsAt before
844 // changing the model.
845 attached_tabstrip_->LayoutDraggedTabsAt(
846 tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
847 initial_move_);
848 did_layout = true;
850 attached_model->MoveSelectedTabsTo(to_index);
852 // Move may do nothing in certain situations (such as when dragging pinned
853 // tabs). Make sure the tabstrip actually changed before updating
854 // last_move_screen_loc_.
855 if (index_of_last_item !=
856 attached_model->GetIndexOfWebContents(last_contents)) {
857 last_move_screen_loc_ = point_in_screen.x();
862 if (!did_layout) {
863 attached_tabstrip_->LayoutDraggedTabsAt(
864 tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
865 initial_move_);
868 StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay);
870 initial_move_ = false;
873 void TabDragController::StartMoveStackedTimerIfNecessary(
874 const gfx::Point& point_in_screen,
875 int delay_ms) {
876 DCHECK(attached_tabstrip_);
878 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
879 if (!touch_layout)
880 return;
882 gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
883 gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
884 int index = touch_layout->active_index();
885 if (ShouldDragToNextStackedTab(bounds, index)) {
886 static_cast<base::Timer*>(&move_stacked_timer_)->Start(
887 FROM_HERE,
888 base::TimeDelta::FromMilliseconds(delay_ms),
889 base::Bind(&TabDragController::MoveAttachedToNextStackedIndex,
890 base::Unretained(this), point_in_screen));
891 } else if (ShouldDragToPreviousStackedTab(bounds, index)) {
892 static_cast<base::Timer*>(&move_stacked_timer_)->Start(
893 FROM_HERE,
894 base::TimeDelta::FromMilliseconds(delay_ms),
895 base::Bind(&TabDragController::MoveAttachedToPreviousStackedIndex,
896 base::Unretained(this), point_in_screen));
900 TabDragController::DetachPosition TabDragController::GetDetachPosition(
901 const gfx::Point& point_in_screen) {
902 DCHECK(attached_tabstrip_);
903 gfx::Point attached_point(point_in_screen);
904 views::View::ConvertPointFromScreen(attached_tabstrip_, &attached_point);
905 if (attached_point.x() < 0)
906 return DETACH_BEFORE;
907 if (attached_point.x() >= attached_tabstrip_->width())
908 return DETACH_AFTER;
909 return DETACH_ABOVE_OR_BELOW;
912 TabStrip* TabDragController::GetTargetTabStripForPoint(
913 const gfx::Point& point_in_screen) {
914 TRACE_EVENT1("views", "TabDragController::GetTargetTabStripForPoint",
915 "point_in_screen", point_in_screen.ToString());
917 if (move_only() && attached_tabstrip_) {
918 DCHECK_EQ(DETACHABLE, detach_behavior_);
919 // move_only() is intended for touch, in which case we only want to detach
920 // if the touch point moves significantly in the vertical distance.
921 gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_);
922 if (DoesRectContainVerticalPointExpanded(tabstrip_bounds,
923 kTouchVerticalDetachMagnetism,
924 point_in_screen.y()))
925 return attached_tabstrip_;
927 gfx::NativeWindow local_window =
928 GetLocalProcessWindow(point_in_screen, is_dragging_window_);
929 // Do not allow dragging into a window with a modal dialog, it causes a weird
930 // behavior. See crbug.com/336691
931 if (!wm::GetModalTransient(local_window)) {
932 TabStrip* tab_strip = GetTabStripForWindow(local_window);
933 if (tab_strip && DoesTabStripContain(tab_strip, point_in_screen))
934 return tab_strip;
937 return is_dragging_window_ ? attached_tabstrip_ : NULL;
940 TabStrip* TabDragController::GetTabStripForWindow(gfx::NativeWindow window) {
941 if (!window)
942 return NULL;
943 BrowserView* browser_view =
944 BrowserView::GetBrowserViewForNativeWindow(window);
945 // We don't allow drops on windows that don't have tabstrips.
946 if (!browser_view ||
947 !browser_view->browser()->SupportsWindowFeature(
948 Browser::FEATURE_TABSTRIP))
949 return NULL;
951 TabStrip* other_tabstrip = browser_view->tabstrip();
952 TabStrip* tab_strip =
953 attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_;
954 DCHECK(tab_strip);
956 return other_tabstrip->controller()->IsCompatibleWith(tab_strip) ?
957 other_tabstrip : NULL;
960 bool TabDragController::DoesTabStripContain(
961 TabStrip* tabstrip,
962 const gfx::Point& point_in_screen) const {
963 // Make sure the specified screen point is actually within the bounds of the
964 // specified tabstrip...
965 gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
966 return point_in_screen.x() < tabstrip_bounds.right() &&
967 point_in_screen.x() >= tabstrip_bounds.x() &&
968 DoesRectContainVerticalPointExpanded(tabstrip_bounds,
969 kVerticalDetachMagnetism,
970 point_in_screen.y());
973 void TabDragController::Attach(TabStrip* attached_tabstrip,
974 const gfx::Point& point_in_screen) {
975 TRACE_EVENT1("views", "TabDragController::Attach",
976 "point_in_screen", point_in_screen.ToString());
978 DCHECK(!attached_tabstrip_); // We should already have detached by the time
979 // we get here.
981 attached_tabstrip_ = attached_tabstrip;
983 std::vector<Tab*> tabs =
984 GetTabsMatchingDraggedContents(attached_tabstrip_);
986 if (tabs.empty()) {
987 // Transitioning from detached to attached to a new tabstrip. Add tabs to
988 // the new model.
990 selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel());
992 if (!detach_into_browser_) {
993 // Remove ourselves as the delegate now that the dragged WebContents is
994 // being inserted back into a Browser.
995 for (size_t i = 0; i < drag_data_.size(); ++i) {
996 drag_data_[i].contents->SetDelegate(NULL);
997 drag_data_[i].original_delegate = NULL;
1000 // Return the WebContents to normalcy.
1001 source_dragged_contents()->DecrementCapturerCount();
1004 // Inserting counts as a move. We don't want the tabs to jitter when the
1005 // user moves the tab immediately after attaching it.
1006 last_move_screen_loc_ = point_in_screen.x();
1008 // Figure out where to insert the tab based on the bounds of the dragged
1009 // representation and the ideal bounds of the other Tabs already in the
1010 // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
1011 // changing due to animation).
1012 gfx::Point tab_strip_point(point_in_screen);
1013 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point);
1014 tab_strip_point.set_x(
1015 attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()));
1016 tab_strip_point.Offset(0, -mouse_offset_.y());
1017 gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point);
1018 int index = GetInsertionIndexForDraggedBounds(bounds);
1019 attach_index_ = index;
1020 attach_x_ = tab_strip_point.x();
1021 base::AutoReset<bool> setter(&is_mutating_, true);
1022 for (size_t i = 0; i < drag_data_.size(); ++i) {
1023 int add_types = TabStripModel::ADD_NONE;
1024 if (attached_tabstrip_->touch_layout_.get()) {
1025 // StackedTabStripLayout positions relative to the active tab, if we
1026 // don't add the tab as active things bounce around.
1027 DCHECK_EQ(1u, drag_data_.size());
1028 add_types |= TabStripModel::ADD_ACTIVE;
1030 if (drag_data_[i].pinned)
1031 add_types |= TabStripModel::ADD_PINNED;
1032 GetModel(attached_tabstrip_)->InsertWebContentsAt(
1033 index + i, drag_data_[i].contents, add_types);
1036 tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
1038 DCHECK_EQ(tabs.size(), drag_data_.size());
1039 for (size_t i = 0; i < drag_data_.size(); ++i)
1040 drag_data_[i].attached_tab = tabs[i];
1042 attached_tabstrip_->StartedDraggingTabs(tabs);
1044 ResetSelection(GetModel(attached_tabstrip_));
1046 // The size of the dragged tab may have changed. Adjust the x offset so that
1047 // ratio of mouse_offset_ to original width is maintained.
1048 std::vector<Tab*> tabs_to_source(tabs);
1049 tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1,
1050 tabs_to_source.end());
1051 int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) -
1052 tabs[source_tab_index_]->width() +
1053 static_cast<int>(offset_to_width_ratio_ *
1054 tabs[source_tab_index_]->width());
1055 mouse_offset_.set_x(new_x);
1057 // Transfer ownership of us to the new tabstrip as well as making sure the
1058 // window has capture. This is important so that if activation changes the
1059 // drag isn't prematurely canceled.
1060 if (detach_into_browser_) {
1061 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1062 attached_tabstrip_->OwnDragController(this);
1065 // Redirect all mouse events to the TabStrip so that the tab that originated
1066 // the drag can safely be deleted.
1067 if (detach_into_browser_ || attached_tabstrip_ == source_tabstrip_) {
1068 static_cast<views::internal::RootView*>(
1069 attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler(
1070 attached_tabstrip_);
1074 void TabDragController::Detach(ReleaseCapture release_capture) {
1075 TRACE_EVENT1("views", "TabDragController::Detach",
1076 "release_capture", release_capture);
1078 attach_index_ = -1;
1080 // When the user detaches we assume they want to reorder.
1081 move_behavior_ = REORDER;
1083 // Release ownership of the drag controller and mouse capture. When we
1084 // reattach ownership is transfered.
1085 if (detach_into_browser_) {
1086 attached_tabstrip_->ReleaseDragController();
1087 if (release_capture == RELEASE_CAPTURE)
1088 attached_tabstrip_->GetWidget()->ReleaseCapture();
1091 mouse_move_direction_ = kMovedMouseLeft | kMovedMouseRight;
1093 // Prevent the WebContents HWND from being hidden by any of the model
1094 // operations performed during the drag.
1095 if (!detach_into_browser_)
1096 source_dragged_contents()->IncrementCapturerCount(gfx::Size());
1098 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
1099 TabStripModel* attached_model = GetModel(attached_tabstrip_);
1100 std::vector<TabRendererData> tab_data;
1101 for (size_t i = 0; i < drag_data_.size(); ++i) {
1102 tab_data.push_back(drag_data_[i].attached_tab->data());
1103 int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents);
1104 DCHECK_NE(-1, index);
1106 // Hide the tab so that the user doesn't see it animate closed.
1107 drag_data_[i].attached_tab->SetVisible(false);
1109 attached_model->DetachWebContentsAt(index);
1111 // Detaching resets the delegate, but we still want to be the delegate.
1112 if (!detach_into_browser_)
1113 drag_data_[i].contents->SetDelegate(this);
1115 // Detaching may end up deleting the tab, drop references to it.
1116 drag_data_[i].attached_tab = NULL;
1119 // If we've removed the last Tab from the TabStrip, hide the frame now.
1120 if (!attached_model->empty()) {
1121 if (!selection_model_before_attach_.empty() &&
1122 selection_model_before_attach_.active() >= 0 &&
1123 selection_model_before_attach_.active() < attached_model->count()) {
1124 // Restore the selection.
1125 attached_model->SetSelectionFromModel(selection_model_before_attach_);
1126 } else if (attached_tabstrip_ == source_tabstrip_ &&
1127 !initial_selection_model_.empty()) {
1128 RestoreInitialSelection();
1132 attached_tabstrip_->DraggedTabsDetached();
1133 attached_tabstrip_ = NULL;
1136 void TabDragController::DetachIntoNewBrowserAndRunMoveLoop(
1137 const gfx::Point& point_in_screen) {
1138 if (GetModel(attached_tabstrip_)->count() ==
1139 static_cast<int>(drag_data_.size())) {
1140 // All the tabs in a browser are being dragged but all the tabs weren't
1141 // initially being dragged. For this to happen the user would have to
1142 // start dragging a set of tabs, the other tabs close, then detach.
1143 RunMoveLoop(GetWindowOffset(point_in_screen));
1144 return;
1147 const int last_tabstrip_width = attached_tabstrip_->tab_area_width();
1148 std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
1149 OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);
1151 gfx::Vector2d drag_offset;
1152 Browser* browser = CreateBrowserForDrag(
1153 attached_tabstrip_, point_in_screen, &drag_offset, &drag_bounds);
1154 #if defined(OS_WIN)
1155 gfx::NativeView attached_native_view =
1156 attached_tabstrip_->GetWidget()->GetNativeView();
1157 #endif
1158 Detach(use_aura_capture_policy_ ? DONT_RELEASE_CAPTURE : RELEASE_CAPTURE);
1159 BrowserView* dragged_browser_view =
1160 BrowserView::GetBrowserViewForBrowser(browser);
1161 views::Widget* dragged_widget = dragged_browser_view->GetWidget();
1162 #if defined(OS_WIN)
1163 // The Gesture recognizer does not work well currently when capture changes
1164 // while a touch gesture is in progress. So we need to manually transfer
1165 // gesture sequence and the GR's touch events queue to the new window. This
1166 // should really be done somewhere in capture change code and or inside the
1167 // GR. But we currently do not have a consistent way for doing it that would
1168 // work in all cases. Hence this hack.
1169 ui::GestureRecognizer::Get()->TransferEventsTo(
1170 attached_native_view,
1171 dragged_widget->GetNativeView());
1172 #endif
1173 dragged_widget->SetVisibilityChangedAnimationsEnabled(false);
1174 Attach(dragged_browser_view->tabstrip(), gfx::Point());
1175 AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width,
1176 point_in_screen,
1177 &drag_bounds);
1178 WindowPositionManagedUpdater updater;
1179 dragged_widget->AddObserver(&updater);
1180 browser->window()->Show();
1181 dragged_widget->RemoveObserver(&updater);
1182 dragged_widget->SetVisibilityChangedAnimationsEnabled(true);
1183 // Activate may trigger a focus loss, destroying us.
1185 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
1186 browser->window()->Activate();
1187 if (!ref)
1188 return;
1190 RunMoveLoop(drag_offset);
1193 void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) {
1194 // If the user drags the whole window we'll assume they are going to attach to
1195 // another window and therefore want to reorder.
1196 move_behavior_ = REORDER;
1198 move_loop_widget_ = GetAttachedBrowserWidget();
1199 DCHECK(move_loop_widget_);
1200 move_loop_widget_->AddObserver(this);
1201 is_dragging_window_ = true;
1202 base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
1203 // Running the move loop releases mouse capture on non-ash, which triggers
1204 // destroying the drag loop. Release mouse capture ourself before this while
1205 // the DragController isn't owned by the TabStrip.
1206 if (host_desktop_type_ != chrome::HOST_DESKTOP_TYPE_ASH) {
1207 attached_tabstrip_->ReleaseDragController();
1208 attached_tabstrip_->GetWidget()->ReleaseCapture();
1209 attached_tabstrip_->OwnDragController(this);
1211 const views::Widget::MoveLoopSource move_loop_source =
1212 event_source_ == EVENT_SOURCE_MOUSE ?
1213 views::Widget::MOVE_LOOP_SOURCE_MOUSE :
1214 views::Widget::MOVE_LOOP_SOURCE_TOUCH;
1215 const views::Widget::MoveLoopEscapeBehavior escape_behavior =
1216 is_dragging_new_browser_ ?
1217 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_HIDE :
1218 views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_DONT_HIDE;
1219 views::Widget::MoveLoopResult result =
1220 move_loop_widget_->RunMoveLoop(
1221 drag_offset, move_loop_source, escape_behavior);
1222 content::NotificationService::current()->Notify(
1223 chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE,
1224 content::NotificationService::AllBrowserContextsAndSources(),
1225 content::NotificationService::NoDetails());
1227 if (!ref)
1228 return;
1229 // Under chromeos we immediately set the |move_loop_widget_| to NULL.
1230 if (move_loop_widget_) {
1231 move_loop_widget_->RemoveObserver(this);
1232 move_loop_widget_ = NULL;
1234 is_dragging_window_ = false;
1235 waiting_for_run_loop_to_exit_ = false;
1236 if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) {
1237 end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING;
1238 if (tab_strip_to_attach_to_after_exit_) {
1239 gfx::Point point_in_screen(GetCursorScreenPoint());
1240 Detach(DONT_RELEASE_CAPTURE);
1241 Attach(tab_strip_to_attach_to_after_exit_, point_in_screen);
1242 // Move the tabs into position.
1243 MoveAttached(point_in_screen);
1244 attached_tabstrip_->GetWidget()->Activate();
1245 // Activate may trigger a focus loss, destroying us.
1246 if (!ref)
1247 return;
1248 tab_strip_to_attach_to_after_exit_ = NULL;
1250 DCHECK(attached_tabstrip_);
1251 attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1252 } else if (active_) {
1253 EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ?
1254 END_DRAG_CANCEL : END_DRAG_COMPLETE);
1258 int TabDragController::GetInsertionIndexFrom(const gfx::Rect& dragged_bounds,
1259 int start,
1260 int delta) const {
1261 for (int i = start, tab_count = attached_tabstrip_->tab_count();
1262 i >= 0 && i < tab_count; i += delta) {
1263 const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i);
1264 gfx::Rect left_half, right_half;
1265 ideal_bounds.SplitVertically(&left_half, &right_half);
1266 if (dragged_bounds.x() >= right_half.x() &&
1267 dragged_bounds.x() < right_half.right()) {
1268 return i + 1;
1269 } else if (dragged_bounds.x() >= left_half.x() &&
1270 dragged_bounds.x() < left_half.right()) {
1271 return i;
1274 return -1;
1277 int TabDragController::GetInsertionIndexForDraggedBounds(
1278 const gfx::Rect& dragged_bounds) const {
1279 int index = -1;
1280 if (attached_tabstrip_->touch_layout_.get()) {
1281 index = GetInsertionIndexForDraggedBoundsStacked(dragged_bounds);
1282 if (index != -1) {
1283 // Only move the tab to the left/right if the user actually moved the
1284 // mouse that way. This is necessary as tabs with stacked tabs
1285 // before/after them have multiple drag positions.
1286 int active_index = attached_tabstrip_->touch_layout_->active_index();
1287 if ((index < active_index &&
1288 (mouse_move_direction_ & kMovedMouseLeft) == 0) ||
1289 (index > active_index &&
1290 (mouse_move_direction_ & kMovedMouseRight) == 0)) {
1291 index = active_index;
1294 } else {
1295 index = GetInsertionIndexFrom(dragged_bounds, 0, 1);
1297 if (index == -1) {
1298 int tab_count = attached_tabstrip_->tab_count();
1299 int right_tab_x = tab_count == 0 ? 0 :
1300 attached_tabstrip_->ideal_bounds(tab_count - 1).right();
1301 if (dragged_bounds.right() > right_tab_x) {
1302 index = GetModel(attached_tabstrip_)->count();
1303 } else {
1304 index = 0;
1308 if (!drag_data_[0].attached_tab) {
1309 // If 'attached_tab' is NULL, it means we're in the process of attaching and
1310 // don't need to constrain the index.
1311 return index;
1314 int max_index = GetModel(attached_tabstrip_)->count() -
1315 static_cast<int>(drag_data_.size());
1316 return std::max(0, std::min(max_index, index));
1319 bool TabDragController::ShouldDragToNextStackedTab(
1320 const gfx::Rect& dragged_bounds,
1321 int index) const {
1322 if (index + 1 >= attached_tabstrip_->tab_count() ||
1323 !attached_tabstrip_->touch_layout_->IsStacked(index + 1) ||
1324 (mouse_move_direction_ & kMovedMouseRight) == 0)
1325 return false;
1327 int active_x = attached_tabstrip_->ideal_bounds(index).x();
1328 int next_x = attached_tabstrip_->ideal_bounds(index + 1).x();
1329 int mid_x = std::min(next_x - kStackedDistance,
1330 active_x + (next_x - active_x) / 4);
1331 return dragged_bounds.x() >= mid_x;
1334 bool TabDragController::ShouldDragToPreviousStackedTab(
1335 const gfx::Rect& dragged_bounds,
1336 int index) const {
1337 if (index - 1 < attached_tabstrip_->GetMiniTabCount() ||
1338 !attached_tabstrip_->touch_layout_->IsStacked(index - 1) ||
1339 (mouse_move_direction_ & kMovedMouseLeft) == 0)
1340 return false;
1342 int active_x = attached_tabstrip_->ideal_bounds(index).x();
1343 int previous_x = attached_tabstrip_->ideal_bounds(index - 1).x();
1344 int mid_x = std::max(previous_x + kStackedDistance,
1345 active_x - (active_x - previous_x) / 4);
1346 return dragged_bounds.x() <= mid_x;
1349 int TabDragController::GetInsertionIndexForDraggedBoundsStacked(
1350 const gfx::Rect& dragged_bounds) const {
1351 StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
1352 int active_index = touch_layout->active_index();
1353 // Search from the active index to the front of the tabstrip. Do this as tabs
1354 // overlap each other from the active index.
1355 int index = GetInsertionIndexFrom(dragged_bounds, active_index, -1);
1356 if (index != active_index)
1357 return index;
1358 if (index == -1)
1359 return GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1361 // The position to drag to corresponds to the active tab. If the next/previous
1362 // tab is stacked, then shorten the distance used to determine insertion
1363 // bounds. We do this as GetInsertionIndexFrom() uses the bounds of the
1364 // tabs. When tabs are stacked the next/previous tab is on top of the tab.
1365 if (active_index + 1 < attached_tabstrip_->tab_count() &&
1366 touch_layout->IsStacked(active_index + 1)) {
1367 index = GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1368 if (index == -1 && ShouldDragToNextStackedTab(dragged_bounds, active_index))
1369 index = active_index + 1;
1370 else if (index == -1)
1371 index = active_index;
1372 } else if (ShouldDragToPreviousStackedTab(dragged_bounds, active_index)) {
1373 index = active_index - 1;
1375 return index;
1378 gfx::Rect TabDragController::GetDraggedViewTabStripBounds(
1379 const gfx::Point& tab_strip_point) {
1380 // attached_tab is NULL when inserting into a new tabstrip.
1381 if (source_tab_drag_data()->attached_tab) {
1382 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1383 source_tab_drag_data()->attached_tab->width(),
1384 source_tab_drag_data()->attached_tab->height());
1387 double sel_width, unselected_width;
1388 attached_tabstrip_->GetCurrentTabWidths(&sel_width, &unselected_width);
1389 return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1390 static_cast<int>(sel_width),
1391 Tab::GetStandardSize().height());
1394 gfx::Point TabDragController::GetAttachedDragPoint(
1395 const gfx::Point& point_in_screen) {
1396 DCHECK(attached_tabstrip_); // The tab must be attached.
1398 gfx::Point tab_loc(point_in_screen);
1399 views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_loc);
1400 const int x =
1401 attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
1403 // TODO: consider caching this.
1404 std::vector<Tab*> attached_tabs;
1405 for (size_t i = 0; i < drag_data_.size(); ++i)
1406 attached_tabs.push_back(drag_data_[i].attached_tab);
1407 const int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs);
1408 const int max_x = attached_tabstrip_->width() - size;
1409 return gfx::Point(std::min(std::max(x, 0), max_x), 0);
1412 std::vector<Tab*> TabDragController::GetTabsMatchingDraggedContents(
1413 TabStrip* tabstrip) {
1414 TabStripModel* model = GetModel(attached_tabstrip_);
1415 std::vector<Tab*> tabs;
1416 for (size_t i = 0; i < drag_data_.size(); ++i) {
1417 int model_index = model->GetIndexOfWebContents(drag_data_[i].contents);
1418 if (model_index == TabStripModel::kNoTab)
1419 return std::vector<Tab*>();
1420 tabs.push_back(tabstrip->tab_at(model_index));
1422 return tabs;
1425 std::vector<gfx::Rect> TabDragController::CalculateBoundsForDraggedTabs() {
1426 std::vector<gfx::Rect> drag_bounds;
1427 std::vector<Tab*> attached_tabs;
1428 for (size_t i = 0; i < drag_data_.size(); ++i)
1429 attached_tabs.push_back(drag_data_[i].attached_tab);
1430 attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs,
1431 &drag_bounds);
1432 return drag_bounds;
1435 void TabDragController::EndDragImpl(EndDragType type) {
1436 DCHECK(active_);
1437 active_ = false;
1439 bring_to_front_timer_.Stop();
1440 move_stacked_timer_.Stop();
1442 if (is_dragging_window_) {
1443 waiting_for_run_loop_to_exit_ = true;
1445 if (type == NORMAL || (type == TAB_DESTROYED && drag_data_.size() > 1)) {
1446 SetWindowPositionManaged(GetAttachedBrowserWidget()->GetNativeView(),
1447 true);
1450 // End the nested drag loop.
1451 GetAttachedBrowserWidget()->EndMoveLoop();
1454 if (type != TAB_DESTROYED) {
1455 // We only finish up the drag if we were actually dragging. If start_drag_
1456 // is false, the user just clicked and released and didn't move the mouse
1457 // enough to trigger a drag.
1458 if (started_drag_) {
1459 RestoreFocus();
1460 if (type == CANCELED)
1461 RevertDrag();
1462 else
1463 CompleteDrag();
1465 } else if (drag_data_.size() > 1) {
1466 initial_selection_model_.Clear();
1467 RevertDrag();
1468 } // else case the only tab we were dragging was deleted. Nothing to do.
1470 if (!detach_into_browser_)
1471 ResetDelegates();
1473 // Clear out drag data so we don't attempt to do anything with it.
1474 drag_data_.clear();
1476 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
1477 attached_tabstrip_ : source_tabstrip_;
1478 owning_tabstrip->DestroyDragController();
1481 void TabDragController::RevertDrag() {
1482 std::vector<Tab*> tabs;
1483 for (size_t i = 0; i < drag_data_.size(); ++i) {
1484 if (drag_data_[i].contents) {
1485 // Contents is NULL if a tab was destroyed while the drag was under way.
1486 tabs.push_back(drag_data_[i].attached_tab);
1487 RevertDragAt(i);
1491 bool restore_frame = !detach_into_browser_ &&
1492 attached_tabstrip_ != source_tabstrip_;
1493 if (attached_tabstrip_) {
1494 if (did_restore_window_)
1495 MaximizeAttachedWindow();
1496 if (attached_tabstrip_ == source_tabstrip_) {
1497 source_tabstrip_->StoppedDraggingTabs(
1498 tabs, initial_tab_positions_, move_behavior_ == MOVE_VISIBILE_TABS,
1499 false);
1500 } else {
1501 attached_tabstrip_->DraggedTabsDetached();
1505 if (initial_selection_model_.empty())
1506 ResetSelection(GetModel(source_tabstrip_));
1507 else
1508 GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_);
1510 // If we're not attached to any TabStrip, or attached to some other TabStrip,
1511 // we need to restore the bounds of the original TabStrip's frame, in case
1512 // it has been hidden.
1513 if (restore_frame && !restore_bounds_.IsEmpty())
1514 source_tabstrip_->GetWidget()->SetBounds(restore_bounds_);
1516 if (detach_into_browser_ && source_tabstrip_)
1517 source_tabstrip_->GetWidget()->Activate();
1519 // Return the WebContents to normalcy. If the tab was attached to a
1520 // TabStrip before the revert, the decrement has already occurred.
1521 // If the tab was destroyed, don't attempt to dereference the
1522 // WebContents pointer.
1523 if (!detach_into_browser_ && !attached_tabstrip_ && source_dragged_contents())
1524 source_dragged_contents()->DecrementCapturerCount();
1527 void TabDragController::ResetSelection(TabStripModel* model) {
1528 DCHECK(model);
1529 ui::ListSelectionModel selection_model;
1530 bool has_one_valid_tab = false;
1531 for (size_t i = 0; i < drag_data_.size(); ++i) {
1532 // |contents| is NULL if a tab was deleted out from under us.
1533 if (drag_data_[i].contents) {
1534 int index = model->GetIndexOfWebContents(drag_data_[i].contents);
1535 DCHECK_NE(-1, index);
1536 selection_model.AddIndexToSelection(index);
1537 if (!has_one_valid_tab || i == source_tab_index_) {
1538 // Reset the active/lead to the first tab. If the source tab is still
1539 // valid we'll reset these again later on.
1540 selection_model.set_active(index);
1541 selection_model.set_anchor(index);
1542 has_one_valid_tab = true;
1546 if (!has_one_valid_tab)
1547 return;
1549 model->SetSelectionFromModel(selection_model);
1552 void TabDragController::RestoreInitialSelection() {
1553 // First time detaching from the source tabstrip. Reset selection model to
1554 // initial_selection_model_. Before resetting though we have to remove all
1555 // the tabs from initial_selection_model_ as it was created with the tabs
1556 // still there.
1557 ui::ListSelectionModel selection_model;
1558 selection_model.Copy(initial_selection_model_);
1559 for (DragData::const_reverse_iterator i(drag_data_.rbegin());
1560 i != drag_data_.rend(); ++i) {
1561 selection_model.DecrementFrom(i->source_model_index);
1563 // We may have cleared out the selection model. Only reset it if it
1564 // contains something.
1565 if (selection_model.empty())
1566 return;
1568 // The anchor/active may have been among the tabs that were dragged out. Force
1569 // the anchor/active to be valid.
1570 if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex)
1571 selection_model.set_anchor(selection_model.selected_indices()[0]);
1572 if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex)
1573 selection_model.set_active(selection_model.selected_indices()[0]);
1574 GetModel(source_tabstrip_)->SetSelectionFromModel(selection_model);
1577 void TabDragController::RevertDragAt(size_t drag_index) {
1578 DCHECK(started_drag_);
1579 DCHECK(source_tabstrip_);
1581 base::AutoReset<bool> setter(&is_mutating_, true);
1582 TabDragData* data = &(drag_data_[drag_index]);
1583 if (attached_tabstrip_) {
1584 int index =
1585 GetModel(attached_tabstrip_)->GetIndexOfWebContents(data->contents);
1586 if (attached_tabstrip_ != source_tabstrip_) {
1587 // The Tab was inserted into another TabStrip. We need to put it back
1588 // into the original one.
1589 GetModel(attached_tabstrip_)->DetachWebContentsAt(index);
1590 // TODO(beng): (Cleanup) seems like we should use Attach() for this
1591 // somehow.
1592 GetModel(source_tabstrip_)->InsertWebContentsAt(
1593 data->source_model_index, data->contents,
1594 (data->pinned ? TabStripModel::ADD_PINNED : 0));
1595 } else {
1596 // The Tab was moved within the TabStrip where the drag was initiated.
1597 // Move it back to the starting location.
1598 GetModel(source_tabstrip_)->MoveWebContentsAt(
1599 index, data->source_model_index, false);
1601 } else {
1602 // The Tab was detached from the TabStrip where the drag began, and has not
1603 // been attached to any other TabStrip. We need to put it back into the
1604 // source TabStrip.
1605 GetModel(source_tabstrip_)->InsertWebContentsAt(
1606 data->source_model_index, data->contents,
1607 (data->pinned ? TabStripModel::ADD_PINNED : 0));
1611 void TabDragController::CompleteDrag() {
1612 DCHECK(started_drag_);
1614 if (attached_tabstrip_) {
1615 if (is_dragging_new_browser_ || did_restore_window_) {
1616 if (IsDockedOrSnapped(attached_tabstrip_)) {
1617 was_source_maximized_ = false;
1618 was_source_fullscreen_ = false;
1621 // If source window was maximized - maximize the new window as well.
1622 if (was_source_maximized_ || was_source_fullscreen_)
1623 MaximizeAttachedWindow();
1625 attached_tabstrip_->StoppedDraggingTabs(
1626 GetTabsMatchingDraggedContents(attached_tabstrip_),
1627 initial_tab_positions_,
1628 move_behavior_ == MOVE_VISIBILE_TABS,
1629 true);
1630 } else {
1631 // Compel the model to construct a new window for the detached
1632 // WebContentses.
1633 views::Widget* widget = source_tabstrip_->GetWidget();
1634 gfx::Rect window_bounds(widget->GetRestoredBounds());
1635 window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));
1637 base::AutoReset<bool> setter(&is_mutating_, true);
1639 std::vector<TabStripModelDelegate::NewStripContents> contentses;
1640 for (size_t i = 0; i < drag_data_.size(); ++i) {
1641 TabStripModelDelegate::NewStripContents item;
1642 item.web_contents = drag_data_[i].contents;
1643 item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED
1644 : TabStripModel::ADD_NONE;
1645 contentses.push_back(item);
1648 Browser* new_browser =
1649 GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents(
1650 contentses, window_bounds, widget->IsMaximized());
1651 ResetSelection(new_browser->tab_strip_model());
1652 new_browser->window()->Show();
1654 // Return the WebContents to normalcy.
1655 if (!detach_into_browser_)
1656 source_dragged_contents()->DecrementCapturerCount();
1659 CleanUpHiddenFrame();
1662 void TabDragController::MaximizeAttachedWindow() {
1663 GetAttachedBrowserWidget()->Maximize();
1664 #if defined(USE_ASH)
1665 if (was_source_fullscreen_ &&
1666 host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
1667 // In fullscreen mode it is only possible to get here if the source
1668 // was in "immersive fullscreen" mode, so toggle it back on.
1669 ash::accelerators::ToggleFullscreen();
1671 #endif
1674 void TabDragController::ResetDelegates() {
1675 DCHECK(!detach_into_browser_);
1676 for (size_t i = 0; i < drag_data_.size(); ++i) {
1677 if (drag_data_[i].contents &&
1678 drag_data_[i].contents->GetDelegate() == this) {
1679 drag_data_[i].contents->SetDelegate(
1680 drag_data_[i].original_delegate);
1685 gfx::Rect TabDragController::GetViewScreenBounds(
1686 views::View* view) const {
1687 gfx::Point view_topleft;
1688 views::View::ConvertPointToScreen(view, &view_topleft);
1689 gfx::Rect view_screen_bounds = view->GetLocalBounds();
1690 view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());
1691 return view_screen_bounds;
1694 void TabDragController::CleanUpHiddenFrame() {
1695 // If the model we started dragging from is now empty, we must ask the
1696 // delegate to close the frame.
1697 if (!detach_into_browser_ && GetModel(source_tabstrip_)->empty())
1698 GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession();
1701 void TabDragController::BringWindowUnderPointToFront(
1702 const gfx::Point& point_in_screen) {
1703 aura::Window* window = GetLocalProcessWindow(point_in_screen, true);
1705 // Only bring browser windows to front - only windows with a TabStrip can
1706 // be tab drag targets.
1707 if (!GetTabStripForWindow(window))
1708 return;
1710 if (window) {
1711 views::Widget* widget_window = views::Widget::GetWidgetForNativeView(
1712 window);
1713 if (!widget_window)
1714 return;
1716 #if defined(USE_ASH)
1717 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
1718 // TODO(varkha): The code below ensures that the phantom drag widget
1719 // is shown on top of browser windows. The code should be moved to ash/
1720 // and the phantom should be able to assert its top-most state on its own.
1721 // One strategy would be for DragWindowController to
1722 // be able to observe stacking changes to the phantom drag widget's
1723 // siblings in order to keep it on top. One way is to implement a
1724 // notification that is sent to a window parent's observers when a
1725 // stacking order is changed among the children of that same parent.
1726 // Note that OnWindowStackingChanged is sent only to the child that is the
1727 // argument of one of the Window::StackChildX calls and not to all its
1728 // siblings affected by the stacking change.
1729 aura::Window* browser_window = widget_window->GetNativeView();
1730 // Find a topmost non-popup window and stack the recipient browser above
1731 // it in order to avoid stacking the browser window on top of the phantom
1732 // drag widget created by DragWindowController in a second display.
1733 for (aura::Window::Windows::const_reverse_iterator it =
1734 browser_window->parent()->children().rbegin();
1735 it != browser_window->parent()->children().rend(); ++it) {
1736 // If the iteration reached the recipient browser window then it is
1737 // already topmost and it is safe to return with no stacking change.
1738 if (*it == browser_window)
1739 return;
1740 if ((*it)->type() != ui::wm::WINDOW_TYPE_POPUP) {
1741 widget_window->StackAbove(*it);
1742 break;
1745 } else {
1746 widget_window->StackAtTop();
1748 #else
1749 widget_window->StackAtTop();
1750 #endif
1752 // The previous call made the window appear on top of the dragged window,
1753 // move the dragged window to the front.
1754 if (is_dragging_window_)
1755 attached_tabstrip_->GetWidget()->StackAtTop();
1759 TabStripModel* TabDragController::GetModel(
1760 TabStrip* tabstrip) const {
1761 return static_cast<BrowserTabStripController*>(tabstrip->controller())->
1762 model();
1765 views::Widget* TabDragController::GetAttachedBrowserWidget() {
1766 return attached_tabstrip_->GetWidget();
1769 bool TabDragController::AreTabsConsecutive() {
1770 for (size_t i = 1; i < drag_data_.size(); ++i) {
1771 if (drag_data_[i - 1].source_model_index + 1 !=
1772 drag_data_[i].source_model_index) {
1773 return false;
1776 return true;
1779 gfx::Rect TabDragController::CalculateDraggedBrowserBounds(
1780 TabStrip* source,
1781 const gfx::Point& point_in_screen,
1782 std::vector<gfx::Rect>* drag_bounds) {
1783 gfx::Point center(0, source->height() / 2);
1784 views::View::ConvertPointToWidget(source, &center);
1785 gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds());
1786 if (source->GetWidget()->IsMaximized()) {
1787 // If the restore bounds is really small, we don't want to honor it
1788 // (dragging a really small window looks wrong), instead make sure the new
1789 // window is at least 50% the size of the old.
1790 const gfx::Size max_size(
1791 source->GetWidget()->GetWindowBoundsInScreen().size());
1792 new_bounds.set_width(
1793 std::max(max_size.width() / 2, new_bounds.width()));
1794 new_bounds.set_height(
1795 std::max(max_size.height() / 2, new_bounds.height()));
1797 new_bounds.set_y(point_in_screen.y() - center.y());
1798 switch (GetDetachPosition(point_in_screen)) {
1799 case DETACH_BEFORE:
1800 new_bounds.set_x(point_in_screen.x() - center.x());
1801 new_bounds.Offset(-mouse_offset_.x(), 0);
1802 break;
1803 case DETACH_AFTER: {
1804 gfx::Point right_edge(source->width(), 0);
1805 views::View::ConvertPointToWidget(source, &right_edge);
1806 new_bounds.set_x(point_in_screen.x() - right_edge.x());
1807 new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0);
1808 OffsetX(-(*drag_bounds)[0].x(), drag_bounds);
1809 break;
1811 default:
1812 break; // Nothing to do for DETACH_ABOVE_OR_BELOW.
1815 // To account for the extra vertical on restored windows that is absent on
1816 // maximized windows, add an additional vertical offset extracted from the tab
1817 // strip.
1818 if (source->GetWidget()->IsMaximized())
1819 new_bounds.Offset(0, -source->button_v_offset());
1820 return new_bounds;
1823 void TabDragController::AdjustBrowserAndTabBoundsForDrag(
1824 int last_tabstrip_width,
1825 const gfx::Point& point_in_screen,
1826 std::vector<gfx::Rect>* drag_bounds) {
1827 attached_tabstrip_->InvalidateLayout();
1828 attached_tabstrip_->DoLayout();
1829 const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width();
1831 // If the new tabstrip is smaller than the old resize the tabs.
1832 if (dragged_tabstrip_width < last_tabstrip_width) {
1833 const float leading_ratio =
1834 drag_bounds->front().x() / static_cast<float>(last_tabstrip_width);
1835 *drag_bounds = CalculateBoundsForDraggedTabs();
1837 if (drag_bounds->back().right() < dragged_tabstrip_width) {
1838 const int delta_x =
1839 std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width),
1840 dragged_tabstrip_width -
1841 (drag_bounds->back().right() -
1842 drag_bounds->front().x()));
1843 OffsetX(delta_x, drag_bounds);
1846 // Reposition the restored window such that the tab that was dragged remains
1847 // under the mouse cursor.
1848 gfx::Point offset(
1849 static_cast<int>((*drag_bounds)[source_tab_index_].width() *
1850 offset_to_width_ratio_) +
1851 (*drag_bounds)[source_tab_index_].x(), 0);
1852 views::View::ConvertPointToWidget(attached_tabstrip_, &offset);
1853 gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen();
1854 bounds.set_x(point_in_screen.x() - offset.x());
1855 GetAttachedBrowserWidget()->SetBounds(bounds);
1857 attached_tabstrip_->SetTabBoundsForDrag(*drag_bounds);
1860 Browser* TabDragController::CreateBrowserForDrag(
1861 TabStrip* source,
1862 const gfx::Point& point_in_screen,
1863 gfx::Vector2d* drag_offset,
1864 std::vector<gfx::Rect>* drag_bounds) {
1865 gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source,
1866 point_in_screen,
1867 drag_bounds));
1868 *drag_offset = point_in_screen - new_bounds.origin();
1870 Profile* profile =
1871 Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext());
1872 Browser::CreateParams create_params(Browser::TYPE_TABBED,
1873 profile,
1874 host_desktop_type_);
1875 create_params.initial_bounds = new_bounds;
1876 Browser* browser = new Browser(create_params);
1877 is_dragging_new_browser_ = true;
1878 SetWindowPositionManaged(browser->window()->GetNativeWindow(), false);
1879 // If the window is created maximized then the bounds we supplied are ignored.
1880 // We need to reset them again so they are honored.
1881 browser->window()->SetBounds(new_bounds);
1883 return browser;
1886 gfx::Point TabDragController::GetCursorScreenPoint() {
1887 #if defined(USE_ASH)
1888 if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH &&
1889 event_source_ == EVENT_SOURCE_TOUCH &&
1890 aura::Env::GetInstance()->is_touch_down()) {
1891 views::Widget* widget = GetAttachedBrowserWidget();
1892 DCHECK(widget);
1893 aura::Window* widget_window = widget->GetNativeWindow();
1894 DCHECK(widget_window->GetRootWindow());
1895 gfx::PointF touch_point_f;
1896 bool got_touch_point = ui::GestureRecognizer::Get()->
1897 GetLastTouchPointForTarget(widget_window, &touch_point_f);
1898 // TODO(tdresser): Switch to using gfx::PointF. See crbug.com/337824.
1899 gfx::Point touch_point = gfx::ToFlooredPoint(touch_point_f);
1900 DCHECK(got_touch_point);
1901 ash::wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point);
1902 return touch_point;
1904 #endif
1905 return screen_->GetCursorScreenPoint();
1908 gfx::Vector2d TabDragController::GetWindowOffset(
1909 const gfx::Point& point_in_screen) {
1910 TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
1911 attached_tabstrip_ : source_tabstrip_;
1912 views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView();
1914 gfx::Point point = point_in_screen;
1915 views::View::ConvertPointFromScreen(toplevel_view, &point);
1916 return point.OffsetFromOrigin();
1919 gfx::NativeWindow TabDragController::GetLocalProcessWindow(
1920 const gfx::Point& screen_point,
1921 bool exclude_dragged_view) {
1922 std::set<aura::Window*> exclude;
1923 if (exclude_dragged_view) {
1924 aura::Window* dragged_window =
1925 attached_tabstrip_->GetWidget()->GetNativeView();
1926 if (dragged_window)
1927 exclude.insert(dragged_window);
1929 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1930 // Exclude windows which are pending deletion via Browser::TabStripEmpty().
1931 // These windows can be returned in the Linux Aura port because the browser
1932 // window which was used for dragging is not hidden once all of its tabs are
1933 // attached to another browser window in DragBrowserToNewTabStrip().
1934 // TODO(pkotwicz): Fix this properly (crbug.com/358482)
1935 BrowserList* browser_list = BrowserList::GetInstance(
1936 chrome::HOST_DESKTOP_TYPE_NATIVE);
1937 for (BrowserList::const_iterator it = browser_list->begin();
1938 it != browser_list->end(); ++it) {
1939 if ((*it)->tab_strip_model()->empty())
1940 exclude.insert((*it)->window()->GetNativeWindow());
1942 #endif
1943 return GetLocalProcessWindowAtPoint(host_desktop_type_,
1944 screen_point,
1945 exclude);