tab_drag_controller.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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.
4
5#include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
6
7#include <math.h>
8#include <set>
9
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/extensions/extension_function_dispatcher.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h"
18#include "chrome/browser/ui/browser_window.h"
19#include "chrome/browser/ui/tabs/tab_strip_model.h"
20#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
21#include "chrome/browser/ui/views/frame/browser_view.h"
22#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
23#include "chrome/browser/ui/views/tabs/dragged_tab_view.h"
24#include "chrome/browser/ui/views/tabs/native_view_photobooth.h"
25#include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
26#include "chrome/browser/ui/views/tabs/tab.h"
27#include "chrome/browser/ui/views/tabs/tab_strip.h"
28#include "chrome/common/chrome_switches.h"
29#include "content/public/browser/notification_details.h"
30#include "content/public/browser/notification_service.h"
31#include "content/public/browser/notification_source.h"
32#include "content/public/browser/notification_types.h"
33#include "content/public/browser/user_metrics.h"
34#include "content/public/browser/web_contents.h"
35#include "content/public/browser/web_contents_view.h"
36#include "grit/theme_resources.h"
37#include "ui/base/animation/animation.h"
38#include "ui/base/animation/animation_delegate.h"
39#include "ui/base/animation/slide_animation.h"
40#include "ui/base/events/event_constants.h"
41#include "ui/base/events/event_utils.h"
42#include "ui/base/resource/resource_bundle.h"
43#include "ui/gfx/canvas.h"
44#include "ui/gfx/image/image_skia.h"
45#include "ui/gfx/screen.h"
46#include "ui/views/widget/root_view.h"
47#include "ui/views/widget/widget.h"
48
49#if defined(USE_ASH)
50#include "ash/shell.h"
51#include "ash/wm/coordinate_conversion.h"
52#include "ash/wm/property_util.h"
53#include "ash/wm/window_util.h"
54#include "ui/aura/env.h"
55#include "ui/aura/root_window.h"
56#include "ui/base/gestures/gesture_recognizer.h"
57#endif
58
59using content::OpenURLParams;
60using content::UserMetricsAction;
61using content::WebContents;
62
63static const int kHorizontalMoveThreshold = 16;  // Pixels.
64
65// Distance from the next/previous stacked before before we consider the tab
66// close enough to trigger moving.
67static const int kStackedDistance = 36;
68
69// If non-null there is a drag underway.
70static TabDragController* instance_ = NULL;
71
72namespace {
73
74// Delay, in ms, during dragging before we bring a window to front.
75const int kBringToFrontDelay = 750;
76
77// Initial delay before moving tabs when the dragged tab is close to the edge of
78// the stacked tabs.
79const int kMoveAttachedInitialDelay = 600;
80
81// Delay for moving tabs after the initial delay has passed.
82const int kMoveAttachedSubsequentDelay = 300;
83
84// Radius of the rect drawn by DockView.
85const int kRoundedRectRadius = 4;
86
87// Spacing between tab icons when DockView is showing a docking location that
88// contains more than one tab.
89const int kTabSpacing = 4;
90
91// DockView is the view responsible for giving a visual indicator of where a
92// dock is going to occur.
93
94class DockView : public views::View {
95 public:
96  explicit DockView(DockInfo::Type type) : type_(type) {}
97
98  virtual gfx::Size GetPreferredSize() OVERRIDE {
99    return gfx::Size(DockInfo::popup_width(), DockInfo::popup_height());
100  }
101
102  virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
103    // Fill the background rect.
104    SkPaint paint;
105    paint.setColor(SkColorSetRGB(108, 108, 108));
106    paint.setStyle(SkPaint::kFill_Style);
107    canvas->DrawRoundRect(GetLocalBounds(), kRoundedRectRadius, paint);
108
109    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
110
111    gfx::ImageSkia* high_icon = rb.GetImageSkiaNamed(IDR_DOCK_HIGH);
112    gfx::ImageSkia* wide_icon = rb.GetImageSkiaNamed(IDR_DOCK_WIDE);
113
114    canvas->Save();
115    bool rtl_ui = base::i18n::IsRTL();
116    if (rtl_ui) {
117      // Flip canvas to draw the mirrored tab images for RTL UI.
118      canvas->Translate(gfx::Vector2d(width(), 0));
119      canvas->Scale(-1, 1);
120    }
121    int x_of_active_tab = width() / 2 + kTabSpacing / 2;
122    int x_of_inactive_tab = width() / 2 - high_icon->width() - kTabSpacing / 2;
123    switch (type_) {
124      case DockInfo::LEFT_OF_WINDOW:
125      case DockInfo::LEFT_HALF:
126        if (!rtl_ui)
127          std::swap(x_of_active_tab, x_of_inactive_tab);
128        canvas->DrawImageInt(*high_icon, x_of_active_tab,
129                             (height() - high_icon->height()) / 2);
130        if (type_ == DockInfo::LEFT_OF_WINDOW) {
131          DrawImageWithAlpha(canvas, *high_icon, x_of_inactive_tab,
132                             (height() - high_icon->height()) / 2);
133        }
134        break;
135
136
137      case DockInfo::RIGHT_OF_WINDOW:
138      case DockInfo::RIGHT_HALF:
139        if (rtl_ui)
140          std::swap(x_of_active_tab, x_of_inactive_tab);
141        canvas->DrawImageInt(*high_icon, x_of_active_tab,
142                             (height() - high_icon->height()) / 2);
143        if (type_ == DockInfo::RIGHT_OF_WINDOW) {
144         DrawImageWithAlpha(canvas, *high_icon, x_of_inactive_tab,
145                           (height() - high_icon->height()) / 2);
146        }
147        break;
148
149      case DockInfo::TOP_OF_WINDOW:
150        canvas->DrawImageInt(*wide_icon, (width() - wide_icon->width()) / 2,
151                             height() / 2 - high_icon->height());
152        break;
153
154      case DockInfo::MAXIMIZE: {
155        gfx::ImageSkia* max_icon = rb.GetImageSkiaNamed(IDR_DOCK_MAX);
156        canvas->DrawImageInt(*max_icon, (width() - max_icon->width()) / 2,
157                             (height() - max_icon->height()) / 2);
158        break;
159      }
160
161      case DockInfo::BOTTOM_HALF:
162      case DockInfo::BOTTOM_OF_WINDOW:
163        canvas->DrawImageInt(*wide_icon, (width() - wide_icon->width()) / 2,
164                             height() / 2 + kTabSpacing / 2);
165        if (type_ == DockInfo::BOTTOM_OF_WINDOW) {
166          DrawImageWithAlpha(canvas, *wide_icon,
167              (width() - wide_icon->width()) / 2,
168              height() / 2 - kTabSpacing / 2 - wide_icon->height());
169        }
170        break;
171
172      default:
173        NOTREACHED();
174        break;
175    }
176    canvas->Restore();
177  }
178
179 private:
180  void DrawImageWithAlpha(gfx::Canvas* canvas, const gfx::ImageSkia& image,
181                          int x, int y) {
182    SkPaint paint;
183    paint.setAlpha(128);
184    canvas->DrawImageInt(image, x, y, paint);
185  }
186
187  DockInfo::Type type_;
188
189  DISALLOW_COPY_AND_ASSIGN(DockView);
190};
191
192void SetTrackedByWorkspace(gfx::NativeWindow window, bool value) {
193#if defined(USE_ASH)
194  ash::SetTrackedByWorkspace(window, value);
195#endif
196}
197
198void SetWindowPositionManaged(gfx::NativeWindow window, bool value) {
199#if defined(USE_ASH)
200  ash::wm::SetWindowPositionManaged(window, value);
201#endif
202}
203
204// Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate
205// of |bounds| is adjusted by |vertical_adjustment|.
206bool DoesRectContainVerticalPointExpanded(
207    const gfx::Rect& bounds,
208    int vertical_adjustment,
209    int y) {
210  int upper_threshold = bounds.bottom() + vertical_adjustment;
211  int lower_threshold = bounds.y() - vertical_adjustment;
212  return y >= lower_threshold && y <= upper_threshold;
213}
214
215// WidgetObserver implementation that resets the window position managed
216// property on Show.
217// We're forced to do this here since BrowserFrameAura resets the 'window
218// position managed' property during a show and we need the property set to
219// false before WorkspaceLayoutManager2 sees the visibility change.
220class WindowPositionManagedUpdater : public views::WidgetObserver {
221 public:
222  virtual void OnWidgetVisibilityChanged(views::Widget* widget,
223                                         bool visible) OVERRIDE {
224    SetWindowPositionManaged(widget->GetNativeView(), false);
225  }
226};
227
228}  // namespace
229
230///////////////////////////////////////////////////////////////////////////////
231// DockDisplayer
232
233// DockDisplayer is responsible for giving the user a visual indication of a
234// possible dock position (as represented by DockInfo). DockDisplayer shows
235// a window with a DockView in it. Two animations are used that correspond to
236// the state of DockInfo::in_enable_area.
237class TabDragController::DockDisplayer : public ui::AnimationDelegate {
238 public:
239  DockDisplayer(TabDragController* controller, const DockInfo& info)
240      : controller_(controller),
241        popup_(NULL),
242        popup_view_(NULL),
243        animation_(this),
244        hidden_(false),
245        in_enable_area_(info.in_enable_area()) {
246    popup_ = new views::Widget;
247    views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
248    params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
249    params.keep_on_top = true;
250    params.bounds = info.GetPopupRect();
251    popup_->Init(params);
252    popup_->SetContentsView(new DockView(info.type()));
253    popup_->SetOpacity(0x00);
254    if (info.in_enable_area())
255      animation_.Reset(1);
256    else
257      animation_.Show();
258    popup_->Show();
259    popup_view_ = popup_->GetNativeView();
260  }
261
262  virtual ~DockDisplayer() {
263    if (controller_)
264      controller_->DockDisplayerDestroyed(this);
265  }
266
267  // Updates the state based on |in_enable_area|.
268  void UpdateInEnabledArea(bool in_enable_area) {
269    if (in_enable_area != in_enable_area_) {
270      in_enable_area_ = in_enable_area;
271      UpdateLayeredAlpha();
272    }
273  }
274
275  // Resets the reference to the hosting TabDragController. This is
276  // invoked when the TabDragController is destroyed.
277  void clear_controller() { controller_ = NULL; }
278
279  // NativeView of the window we create.
280  gfx::NativeView popup_view() { return popup_view_; }
281
282  // Starts the hide animation. When the window is closed the
283  // TabDragController is notified by way of the DockDisplayerDestroyed
284  // method
285  void Hide() {
286    if (hidden_)
287      return;
288
289    if (!popup_) {
290      delete this;
291      return;
292    }
293    hidden_ = true;
294    animation_.Hide();
295  }
296
297  virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE {
298    UpdateLayeredAlpha();
299  }
300
301  virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE {
302    if (!hidden_)
303      return;
304    popup_->Close();
305    delete this;
306  }
307
308 private:
309  void UpdateLayeredAlpha() {
310    double scale = in_enable_area_ ? 1 : .5;
311    popup_->SetOpacity(static_cast<unsigned char>(animation_.GetCurrentValue() *
312        scale * 255.0));
313  }
314
315  // TabDragController that created us.
316  TabDragController* controller_;
317
318  // Window we're showing.
319  views::Widget* popup_;
320
321  // NativeView of |popup_|. We cache this to avoid the possibility of
322  // invoking a method on popup_ after we close it.
323  gfx::NativeView popup_view_;
324
325  // Animation for when first made visible.
326  ui::SlideAnimation animation_;
327
328  // Have we been hidden?
329  bool hidden_;
330
331  // Value of DockInfo::in_enable_area.
332  bool in_enable_area_;
333};
334
335TabDragController::TabDragData::TabDragData()
336    : contents(NULL),
337      original_delegate(NULL),
338      source_model_index(-1),
339      attached_tab(NULL),
340      pinned(false) {
341}
342
343TabDragController::TabDragData::~TabDragData() {
344}
345
346///////////////////////////////////////////////////////////////////////////////
347// TabDragController, public:
348
349// static
350const int TabDragController::kTouchVerticalDetachMagnetism = 50;
351
352// static
353const int TabDragController::kVerticalDetachMagnetism = 15;
354
355TabDragController::TabDragController()
356    : detach_into_browser_(ShouldDetachIntoNewBrowser()),
357      event_source_(EVENT_SOURCE_MOUSE),
358      source_tabstrip_(NULL),
359      attached_tabstrip_(NULL),
360      screen_(NULL),
361      host_desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE),
362      offset_to_width_ratio_(0),
363      old_focused_view_(NULL),
364      last_move_screen_loc_(0),
365      started_drag_(false),
366      active_(true),
367      source_tab_index_(std::numeric_limits<size_t>::max()),
368      initial_move_(true),
369      detach_behavior_(DETACHABLE),
370      move_behavior_(REORDER),
371      mouse_move_direction_(0),
372      is_dragging_window_(false),
373      end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING),
374      waiting_for_run_loop_to_exit_(false),
375      tab_strip_to_attach_to_after_exit_(NULL),
376      move_loop_widget_(NULL),
377      destroyed_(NULL),
378      is_mutating_(false) {
379  instance_ = this;
380}
381
382TabDragController::~TabDragController() {
383  if (instance_ == this)
384    instance_ = NULL;
385
386  if (destroyed_)
387    *destroyed_ = true;
388
389  if (move_loop_widget_) {
390    move_loop_widget_->RemoveObserver(this);
391    SetTrackedByWorkspace(move_loop_widget_->GetNativeView(), true);
392    SetWindowPositionManaged(move_loop_widget_->GetNativeView(), true);
393  }
394
395  if (source_tabstrip_ && detach_into_browser_)
396    GetModel(source_tabstrip_)->RemoveObserver(this);
397
398  base::MessageLoopForUI::current()->RemoveObserver(this);
399
400  // Need to delete the view here manually _before_ we reset the dragged
401  // contents to NULL, otherwise if the view is animating to its destination
402  // bounds, it won't be able to clean up properly since its cleanup routine
403  // uses GetIndexForDraggedContents, which will be invalid.
404  view_.reset(NULL);
405
406  // Reset the delegate of the dragged WebContents. This ends up doing nothing
407  // if the drag was completed.
408  if (!detach_into_browser_)
409    ResetDelegates();
410}
411
412void TabDragController::Init(
413    TabStrip* source_tabstrip,
414    Tab* source_tab,
415    const std::vector<Tab*>& tabs,
416    const gfx::Point& mouse_offset,
417    int source_tab_offset,
418    const ui::ListSelectionModel& initial_selection_model,
419    DetachBehavior detach_behavior,
420    MoveBehavior move_behavior,
421    EventSource event_source) {
422  DCHECK(!tabs.empty());
423  DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
424  source_tabstrip_ = source_tabstrip;
425  screen_ = gfx::Screen::GetScreenFor(
426      source_tabstrip->GetWidget()->GetNativeView());
427  host_desktop_type_ = chrome::GetHostDesktopTypeForNativeView(
428      source_tabstrip->GetWidget()->GetNativeView());
429  start_point_in_screen_ = gfx::Point(source_tab_offset, mouse_offset.y());
430  views::View::ConvertPointToScreen(source_tab, &start_point_in_screen_);
431  event_source_ = event_source;
432  mouse_offset_ = mouse_offset;
433  detach_behavior_ = detach_behavior;
434  move_behavior_ = move_behavior;
435  last_point_in_screen_ = start_point_in_screen_;
436  last_move_screen_loc_ = start_point_in_screen_.x();
437  initial_tab_positions_ = source_tabstrip->GetTabXCoordinates();
438  if (detach_behavior == NOT_DETACHABLE)
439    detach_into_browser_ = false;
440
441  if (detach_into_browser_)
442    GetModel(source_tabstrip_)->AddObserver(this);
443
444  drag_data_.resize(tabs.size());
445  for (size_t i = 0; i < tabs.size(); ++i)
446    InitTabDragData(tabs[i], &(drag_data_[i]));
447  source_tab_index_ =
448      std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
449
450  // Listen for Esc key presses.
451  base::MessageLoopForUI::current()->AddObserver(this);
452
453  if (source_tab->width() > 0) {
454    offset_to_width_ratio_ = static_cast<float>(
455        source_tab->GetMirroredXInView(source_tab_offset)) /
456        static_cast<float>(source_tab->width());
457  }
458  InitWindowCreatePoint();
459  initial_selection_model_.Copy(initial_selection_model);
460}
461
462// static
463bool TabDragController::IsAttachedTo(TabStrip* tab_strip) {
464  return (instance_ && instance_->active() &&
465          instance_->attached_tabstrip() == tab_strip);
466}
467
468// static
469bool TabDragController::IsActive() {
470  return instance_ && instance_->active();
471}
472
473// static
474bool TabDragController::ShouldDetachIntoNewBrowser() {
475#if defined(USE_AURA)
476  return true;
477#else
478  return CommandLine::ForCurrentProcess()->HasSwitch(
479      switches::kTabBrowserDragging);
480#endif
481}
482
483void TabDragController::SetMoveBehavior(MoveBehavior behavior) {
484  if (started_drag())
485    return;
486
487  move_behavior_ = behavior;
488}
489
490void TabDragController::Drag(const gfx::Point& point_in_screen) {
491  bring_to_front_timer_.Stop();
492  move_stacked_timer_.Stop();
493
494  if (waiting_for_run_loop_to_exit_)
495    return;
496
497  if (!started_drag_) {
498    if (!CanStartDrag(point_in_screen))
499      return;  // User hasn't dragged far enough yet.
500
501    started_drag_ = true;
502    SaveFocus();
503    Attach(source_tabstrip_, gfx::Point());
504    if (detach_into_browser_ && static_cast<int>(drag_data_.size()) ==
505        GetModel(source_tabstrip_)->count()) {
506      RunMoveLoop(GetWindowOffset(point_in_screen));
507      return;
508    }
509  }
510
511  ContinueDragging(point_in_screen);
512}
513
514void TabDragController::EndDrag(EndDragReason reason) {
515  // If we're dragging a window ignore capture lost since it'll ultimately
516  // trigger the move loop to end and we'll revert the drag when RunMoveLoop()
517  // finishes.
518  if (reason == END_DRAG_CAPTURE_LOST && is_dragging_window_)
519    return;
520  EndDragImpl(reason != END_DRAG_COMPLETE && source_tabstrip_ ?
521              CANCELED : NORMAL);
522}
523
524void TabDragController::InitTabDragData(Tab* tab,
525                                        TabDragData* drag_data) {
526  drag_data->source_model_index =
527      source_tabstrip_->GetModelIndexOfTab(tab);
528  drag_data->contents = GetModel(source_tabstrip_)->GetWebContentsAt(
529      drag_data->source_model_index);
530  drag_data->pinned = source_tabstrip_->IsTabPinned(tab);
531  registrar_.Add(
532      this,
533      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
534      content::Source<WebContents>(drag_data->contents));
535
536  if (!detach_into_browser_) {
537    drag_data->original_delegate = drag_data->contents->GetDelegate();
538    drag_data->contents->SetDelegate(this);
539  }
540}
541
542///////////////////////////////////////////////////////////////////////////////
543// TabDragController, PageNavigator implementation:
544
545WebContents* TabDragController::OpenURLFromTab(
546    WebContents* source,
547    const OpenURLParams& params) {
548  if (source_tab_drag_data()->original_delegate) {
549    OpenURLParams forward_params = params;
550    if (params.disposition == CURRENT_TAB)
551      forward_params.disposition = NEW_WINDOW;
552
553    return source_tab_drag_data()->original_delegate->OpenURLFromTab(
554        source, forward_params);
555  }
556  return NULL;
557}
558
559///////////////////////////////////////////////////////////////////////////////
560// TabDragController, content::WebContentsDelegate implementation:
561
562void TabDragController::NavigationStateChanged(const WebContents* source,
563                                               unsigned changed_flags) {
564  if (attached_tabstrip_) {
565    for (size_t i = 0; i < drag_data_.size(); ++i) {
566      if (drag_data_[i].contents == source) {
567        // Pass the NavigationStateChanged call to the original delegate so
568        // that the title is updated. Do this only when we are attached as
569        // otherwise the Tab isn't in the TabStrip.
570        drag_data_[i].original_delegate->NavigationStateChanged(source,
571                                                                changed_flags);
572        break;
573      }
574    }
575  }
576  if (view_.get())
577    view_->Update();
578}
579
580void TabDragController::AddNewContents(WebContents* source,
581                                       WebContents* new_contents,
582                                       WindowOpenDisposition disposition,
583                                       const gfx::Rect& initial_pos,
584                                       bool user_gesture,
585                                       bool* was_blocked) {
586  DCHECK_NE(CURRENT_TAB, disposition);
587
588  // Theoretically could be called while dragging if the page tries to
589  // spawn a window. Route this message back to the browser in most cases.
590  if (source_tab_drag_data()->original_delegate) {
591    source_tab_drag_data()->original_delegate->AddNewContents(
592        source, new_contents, disposition, initial_pos, user_gesture,
593        was_blocked);
594  }
595}
596
597void TabDragController::LoadingStateChanged(WebContents* source) {
598  // It would be nice to respond to this message by changing the
599  // screen shot in the dragged tab.
600  if (view_.get())
601    view_->Update();
602}
603
604bool TabDragController::ShouldSuppressDialogs() {
605  // When a dialog is about to be shown we revert the drag. Otherwise a modal
606  // dialog might appear and attempt to parent itself to a hidden tabcontents.
607  EndDragImpl(CANCELED);
608  return false;
609}
610
611content::JavaScriptDialogManager*
612TabDragController::GetJavaScriptDialogManager() {
613  return GetJavaScriptDialogManagerInstance();
614}
615
616///////////////////////////////////////////////////////////////////////////////
617// TabDragController, content::NotificationObserver implementation:
618
619void TabDragController::Observe(
620    int type,
621    const content::NotificationSource& source,
622    const content::NotificationDetails& details) {
623  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
624  WebContents* destroyed_web_contents =
625      content::Source<WebContents>(source).ptr();
626  for (size_t i = 0; i < drag_data_.size(); ++i) {
627    if (drag_data_[i].contents == destroyed_web_contents) {
628      // One of the tabs we're dragging has been destroyed. Cancel the drag.
629      if (destroyed_web_contents->GetDelegate() == this)
630        destroyed_web_contents->SetDelegate(NULL);
631      drag_data_[i].contents = NULL;
632      drag_data_[i].original_delegate = NULL;
633      EndDragImpl(TAB_DESTROYED);
634      return;
635    }
636  }
637  // If we get here it means we got notification for a tab we don't know about.
638  NOTREACHED();
639}
640
641///////////////////////////////////////////////////////////////////////////////
642// TabDragController, MessageLoop::Observer implementation:
643
644base::EventStatus TabDragController::WillProcessEvent(
645    const base::NativeEvent& event) {
646  return base::EVENT_CONTINUE;
647}
648
649void TabDragController::DidProcessEvent(const base::NativeEvent& event) {
650  // If the user presses ESC during a drag, we need to abort and revert things
651  // to the way they were. This is the most reliable way to do this since no
652  // single view or window reliably receives events throughout all the various
653  // kinds of tab dragging.
654  if (ui::EventTypeFromNative(event) == ui::ET_KEY_PRESSED &&
655      ui::KeyboardCodeFromNative(event) == ui::VKEY_ESCAPE) {
656    EndDrag(END_DRAG_CANCEL);
657  }
658}
659
660void TabDragController::OnWidgetBoundsChanged(views::Widget* widget,
661                                              const gfx::Rect& new_bounds) {
662  Drag(GetCursorScreenPoint());
663}
664
665void TabDragController::TabStripEmpty() {
666  DCHECK(detach_into_browser_);
667  GetModel(source_tabstrip_)->RemoveObserver(this);
668  // NULL out source_tabstrip_ so that we don't attempt to add back to it (in
669  // the case of a revert).
670  source_tabstrip_ = NULL;
671}
672
673///////////////////////////////////////////////////////////////////////////////
674// TabDragController, private:
675
676void TabDragController::InitWindowCreatePoint() {
677  // window_create_point_ is only used in CompleteDrag() (through
678  // GetWindowCreatePoint() to get the start point of the docked window) when
679  // the attached_tabstrip_ is NULL and all the window's related bound
680  // information are obtained from source_tabstrip_. So, we need to get the
681  // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise,
682  // the window_create_point_ is not in the correct coordinate system. Please
683  // refer to http://crbug.com/6223 comment #15 for detailed information.
684  views::View* first_tab = source_tabstrip_->tab_at(0);
685  views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);
686  window_create_point_ = first_source_tab_point_;
687  window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());
688}
689
690gfx::Point TabDragController::GetWindowCreatePoint(
691    const gfx::Point& origin) const {
692  if (dock_info_.type() != DockInfo::NONE && dock_info_.in_enable_area()) {
693    // If we're going to dock, we need to return the exact coordinate,
694    // otherwise we may attempt to maximize on the wrong monitor.
695    return origin;
696  }
697
698  // If the cursor is outside the monitor area, move it inside. For example,
699  // dropping a tab onto the task bar on Windows produces this situation.
700  gfx::Rect work_area = screen_->GetDisplayNearestPoint(origin).work_area();
701  gfx::Point create_point(origin);
702  if (!work_area.IsEmpty()) {
703    if (create_point.x() < work_area.x())
704      create_point.set_x(work_area.x());
705    else if (create_point.x() > work_area.right())
706      create_point.set_x(work_area.right());
707    if (create_point.y() < work_area.y())
708      create_point.set_y(work_area.y());
709    else if (create_point.y() > work_area.bottom())
710      create_point.set_y(work_area.bottom());
711  }
712  return gfx::Point(create_point.x() - window_create_point_.x(),
713                    create_point.y() - window_create_point_.y());
714}
715
716void TabDragController::UpdateDockInfo(const gfx::Point& point_in_screen) {
717  // Update the DockInfo for the current mouse coordinates.
718  DockInfo dock_info = GetDockInfoAtPoint(point_in_screen);
719  if (!dock_info.equals(dock_info_)) {
720    // DockInfo for current position differs.
721    if (dock_info_.type() != DockInfo::NONE &&
722        !dock_controllers_.empty()) {
723      // Hide old visual indicator.
724      dock_controllers_.back()->Hide();
725    }
726    dock_info_ = dock_info;
727    if (dock_info_.type() != DockInfo::NONE) {
728      // Show new docking position.
729      DockDisplayer* controller = new DockDisplayer(this, dock_info_);
730      if (controller->popup_view()) {
731        dock_controllers_.push_back(controller);
732        dock_windows_.insert(controller->popup_view());
733      } else {
734        delete controller;
735      }
736    }
737  } else if (dock_info_.type() != DockInfo::NONE &&
738             !dock_controllers_.empty()) {
739    // Current dock position is the same as last, update the controller's
740    // in_enable_area state as it may have changed.
741    dock_controllers_.back()->UpdateInEnabledArea(dock_info_.in_enable_area());
742  }
743}
744
745void TabDragController::SaveFocus() {
746  DCHECK(!old_focused_view_);  // This should only be invoked once.
747  DCHECK(source_tabstrip_);
748  old_focused_view_ = source_tabstrip_->GetFocusManager()->GetFocusedView();
749  source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_);
750}
751
752void TabDragController::RestoreFocus() {
753  if (old_focused_view_ && attached_tabstrip_ == source_tabstrip_)
754    old_focused_view_->GetFocusManager()->SetFocusedView(old_focused_view_);
755  old_focused_view_ = NULL;
756}
757
758bool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const {
759  // Determine if the mouse has moved beyond a minimum elasticity distance in
760  // any direction from the starting point.
761  static const int kMinimumDragDistance = 10;
762  int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x());
763  int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y());
764  return sqrt(pow(static_cast<float>(x_offset), 2) +
765              pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
766}
767
768void TabDragController::ContinueDragging(const gfx::Point& point_in_screen) {
769  DCHECK(!detach_into_browser_ || attached_tabstrip_);
770
771  TabStrip* target_tabstrip = detach_behavior_ == DETACHABLE ?
772      GetTargetTabStripForPoint(point_in_screen) : source_tabstrip_;
773  bool tab_strip_changed = (target_tabstrip != attached_tabstrip_);
774
775  if (attached_tabstrip_) {
776    int move_delta = point_in_screen.x() - last_point_in_screen_.x();
777    if (move_delta > 0)
778      mouse_move_direction_ |= kMovedMouseRight;
779    else if (move_delta < 0)
780      mouse_move_direction_ |= kMovedMouseLeft;
781  }
782  last_point_in_screen_ = point_in_screen;
783
784  if (tab_strip_changed) {
785    if (detach_into_browser_ &&
786        DragBrowserToNewTabStrip(target_tabstrip, point_in_screen) ==
787        DRAG_BROWSER_RESULT_STOP) {
788      return;
789    } else if (!detach_into_browser_) {
790      if (attached_tabstrip_)
791        Detach(RELEASE_CAPTURE);
792      if (target_tabstrip)
793        Attach(target_tabstrip, point_in_screen);
794    }
795  }
796  if (view_.get() || is_dragging_window_) {
797    static_cast<base::Timer*>(&bring_to_front_timer_)->Start(FROM_HERE,
798        base::TimeDelta::FromMilliseconds(kBringToFrontDelay),
799        base::Bind(&TabDragController::BringWindowUnderPointToFront,
800                   base::Unretained(this), point_in_screen));
801  }
802
803  UpdateDockInfo(point_in_screen);
804
805  if (!is_dragging_window_) {
806    if (attached_tabstrip_) {
807      if (move_only()) {
808        DragActiveTabStacked(point_in_screen);
809      } else {
810        MoveAttached(point_in_screen);
811        if (tab_strip_changed) {
812          // Move the corresponding window to the front. We do this after the
813          // move as on windows activate triggers a synchronous paint.
814          attached_tabstrip_->GetWidget()->Activate();
815        }
816      }
817    } else {
818      MoveDetached(point_in_screen);
819    }
820  }
821}
822
823TabDragController::DragBrowserResultType
824TabDragController::DragBrowserToNewTabStrip(
825    TabStrip* target_tabstrip,
826    const gfx::Point& point_in_screen) {
827  if (!target_tabstrip) {
828    DetachIntoNewBrowserAndRunMoveLoop(point_in_screen);
829    return DRAG_BROWSER_RESULT_STOP;
830  }
831  if (is_dragging_window_) {
832    // ReleaseCapture() is going to result in calling back to us (because it
833    // results in a move). That'll cause all sorts of problems.  Reset the
834    // observer so we don't get notified and process the event.
835    if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
836      move_loop_widget_->RemoveObserver(this);
837      move_loop_widget_ = NULL;
838    }
839    views::Widget* browser_widget = GetAttachedBrowserWidget();
840    // Need to release the drag controller before starting the move loop as it's
841    // going to trigger capture lost, which cancels drag.
842    attached_tabstrip_->ReleaseDragController();
843    target_tabstrip->OwnDragController(this);
844    // Disable animations so that we don't see a close animation on aero.
845    browser_widget->SetVisibilityChangedAnimationsEnabled(false);
846    // For aura we can't release capture, otherwise it'll cancel a gesture.
847    // Instead we have to directly change capture.
848    if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH)
849      target_tabstrip->GetWidget()->SetCapture(attached_tabstrip_);
850    else
851      browser_widget->ReleaseCapture();
852    // The window is going away. Since the drag is still on going we don't want
853    // that to effect the position of any windows.
854    SetWindowPositionManaged(browser_widget->GetNativeView(), false);
855
856    // EndMoveLoop is going to snap the window back to its original location.
857    // Hide it so users don't see this.
858    browser_widget->Hide();
859    browser_widget->EndMoveLoop();
860
861    // Ideally we would always swap the tabs now, but on non-ash it seems that
862    // running the move loop implicitly activates the window when done, leading
863    // to all sorts of flicker. So, on non-ash, instead we process the move
864    // after the loop completes. But on chromeos, we can do tab swapping now to
865    // avoid the tab flashing issue(crbug.com/116329).
866    if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
867      is_dragging_window_ = false;
868      Detach(DONT_RELEASE_CAPTURE);
869      Attach(target_tabstrip, point_in_screen);
870      // Move the tabs into position.
871      MoveAttached(point_in_screen);
872      attached_tabstrip_->GetWidget()->Activate();
873    } else {
874      tab_strip_to_attach_to_after_exit_ = target_tabstrip;
875    }
876
877    waiting_for_run_loop_to_exit_ = true;
878    end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING;
879    return DRAG_BROWSER_RESULT_STOP;
880  }
881  Detach(DONT_RELEASE_CAPTURE);
882  Attach(target_tabstrip, point_in_screen);
883  return DRAG_BROWSER_RESULT_CONTINUE;
884}
885
886void TabDragController::DragActiveTabStacked(
887    const gfx::Point& point_in_screen) {
888  if (attached_tabstrip_->tab_count() !=
889      static_cast<int>(initial_tab_positions_.size()))
890    return;  // TODO: should cancel drag if this happens.
891
892  int delta = point_in_screen.x() - start_point_in_screen_.x();
893  attached_tabstrip_->DragActiveTab(initial_tab_positions_, delta);
894}
895
896void TabDragController::MoveAttachedToNextStackedIndex(
897    const gfx::Point& point_in_screen) {
898  int index = attached_tabstrip_->touch_layout_->active_index();
899  if (index + 1 >= attached_tabstrip_->tab_count())
900    return;
901
902  GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index + 1);
903  StartMoveStackedTimerIfNecessary(point_in_screen,
904                                   kMoveAttachedSubsequentDelay);
905}
906
907void TabDragController::MoveAttachedToPreviousStackedIndex(
908    const gfx::Point& point_in_screen) {
909  int index = attached_tabstrip_->touch_layout_->active_index();
910  if (index <= attached_tabstrip_->GetMiniTabCount())
911    return;
912
913  GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index - 1);
914  StartMoveStackedTimerIfNecessary(point_in_screen,
915                                   kMoveAttachedSubsequentDelay);
916}
917
918void TabDragController::MoveAttached(const gfx::Point& point_in_screen) {
919  DCHECK(attached_tabstrip_);
920  DCHECK(!view_.get());
921  DCHECK(!is_dragging_window_);
922
923  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
924
925  // Determine the horizontal move threshold. This is dependent on the width
926  // of tabs. The smaller the tabs compared to the standard size, the smaller
927  // the threshold.
928  int threshold = kHorizontalMoveThreshold;
929  if (!attached_tabstrip_->touch_layout_.get()) {
930    double unselected, selected;
931    attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
932    double ratio = unselected / Tab::GetStandardSize().width();
933    threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
934  }
935  // else case: touch tabs never shrink.
936
937  std::vector<Tab*> tabs(drag_data_.size());
938  for (size_t i = 0; i < drag_data_.size(); ++i)
939    tabs[i] = drag_data_[i].attached_tab;
940
941  bool did_layout = false;
942  // Update the model, moving the WebContents from one index to another. Do this
943  // only if we have moved a minimum distance since the last reorder (to prevent
944  // jitter) or if this the first move and the tabs are not consecutive.
945  if ((abs(point_in_screen.x() - last_move_screen_loc_) > threshold ||
946        (initial_move_ && !AreTabsConsecutive()))) {
947    TabStripModel* attached_model = GetModel(attached_tabstrip_);
948    gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
949    int to_index = GetInsertionIndexForDraggedBounds(bounds);
950    WebContents* last_contents = drag_data_[drag_data_.size() - 1].contents;
951    int index_of_last_item =
952          attached_model->GetIndexOfWebContents(last_contents);
953    if (initial_move_) {
954      // TabStrip determines if the tabs needs to be animated based on model
955      // position. This means we need to invoke LayoutDraggedTabsAt before
956      // changing the model.
957      attached_tabstrip_->LayoutDraggedTabsAt(
958          tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
959          initial_move_);
960      did_layout = true;
961    }
962    attached_model->MoveSelectedTabsTo(to_index);
963
964    // Move may do nothing in certain situations (such as when dragging pinned
965    // tabs). Make sure the tabstrip actually changed before updating
966    // last_move_screen_loc_.
967    if (index_of_last_item !=
968        attached_model->GetIndexOfWebContents(last_contents)) {
969      last_move_screen_loc_ = point_in_screen.x();
970    }
971  }
972
973  if (!did_layout) {
974    attached_tabstrip_->LayoutDraggedTabsAt(
975        tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
976        initial_move_);
977  }
978
979  StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay);
980
981  initial_move_ = false;
982}
983
984void TabDragController::MoveDetached(const gfx::Point& point_in_screen) {
985  DCHECK(!attached_tabstrip_);
986  DCHECK(view_.get());
987  DCHECK(!is_dragging_window_);
988
989  // Move the View. There are no changes to the model if we're detached.
990  view_->MoveTo(point_in_screen);
991}
992
993void TabDragController::StartMoveStackedTimerIfNecessary(
994    const gfx::Point& point_in_screen,
995    int delay_ms) {
996  DCHECK(attached_tabstrip_);
997
998  StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
999  if (!touch_layout)
1000    return;
1001
1002  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
1003  gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
1004  int index = touch_layout->active_index();
1005  if (ShouldDragToNextStackedTab(bounds, index)) {
1006    static_cast<base::Timer*>(&move_stacked_timer_)->Start(
1007        FROM_HERE,
1008        base::TimeDelta::FromMilliseconds(delay_ms),
1009        base::Bind(&TabDragController::MoveAttachedToNextStackedIndex,
1010                   base::Unretained(this), point_in_screen));
1011  } else if (ShouldDragToPreviousStackedTab(bounds, index)) {
1012    static_cast<base::Timer*>(&move_stacked_timer_)->Start(
1013        FROM_HERE,
1014        base::TimeDelta::FromMilliseconds(delay_ms),
1015        base::Bind(&TabDragController::MoveAttachedToPreviousStackedIndex,
1016                   base::Unretained(this), point_in_screen));
1017  }
1018}
1019
1020TabDragController::DetachPosition TabDragController::GetDetachPosition(
1021    const gfx::Point& point_in_screen) {
1022  DCHECK(attached_tabstrip_);
1023  gfx::Point attached_point(point_in_screen);
1024  views::View::ConvertPointToTarget(NULL, attached_tabstrip_, &attached_point);
1025  if (attached_point.x() < 0)
1026    return DETACH_BEFORE;
1027  if (attached_point.x() >= attached_tabstrip_->width())
1028    return DETACH_AFTER;
1029  return DETACH_ABOVE_OR_BELOW;
1030}
1031
1032DockInfo TabDragController::GetDockInfoAtPoint(
1033    const gfx::Point& point_in_screen) {
1034  // TODO: add support for dock info when |detach_into_browser_| is true.
1035  if (attached_tabstrip_ || detach_into_browser_) {
1036    // If the mouse is over a tab strip, don't offer a dock position.
1037    return DockInfo();
1038  }
1039
1040  if (dock_info_.IsValidForPoint(point_in_screen)) {
1041    // It's possible any given screen coordinate has multiple docking
1042    // positions. Check the current info first to avoid having the docking
1043    // position bounce around.
1044    return dock_info_;
1045  }
1046
1047  gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView();
1048  dock_windows_.insert(dragged_view);
1049  DockInfo info = DockInfo::GetDockInfoAtPoint(
1050      host_desktop_type_,
1051      point_in_screen,
1052      dock_windows_);
1053  dock_windows_.erase(dragged_view);
1054  return info;
1055}
1056
1057TabStrip* TabDragController::GetTargetTabStripForPoint(
1058    const gfx::Point& point_in_screen) {
1059  if (move_only() && attached_tabstrip_) {
1060    DCHECK_EQ(DETACHABLE, detach_behavior_);
1061    // move_only() is intended for touch, in which case we only want to detach
1062    // if the touch point moves significantly in the vertical distance.
1063    gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_);
1064    if (DoesRectContainVerticalPointExpanded(tabstrip_bounds,
1065                                             kTouchVerticalDetachMagnetism,
1066                                             point_in_screen.y()))
1067      return attached_tabstrip_;
1068  }
1069  gfx::NativeView dragged_view = NULL;
1070  if (view_.get())
1071    dragged_view = view_->GetWidget()->GetNativeView();
1072  else if (is_dragging_window_)
1073    dragged_view = attached_tabstrip_->GetWidget()->GetNativeView();
1074  if (dragged_view)
1075    dock_windows_.insert(dragged_view);
1076  gfx::NativeWindow local_window =
1077      DockInfo::GetLocalProcessWindowAtPoint(
1078          host_desktop_type_,
1079          point_in_screen,
1080          dock_windows_);
1081  if (dragged_view)
1082    dock_windows_.erase(dragged_view);
1083  TabStrip* tab_strip = GetTabStripForWindow(local_window);
1084  if (tab_strip && DoesTabStripContain(tab_strip, point_in_screen))
1085    return tab_strip;
1086  return is_dragging_window_ ? attached_tabstrip_ : NULL;
1087}
1088
1089TabStrip* TabDragController::GetTabStripForWindow(gfx::NativeWindow window) {
1090  if (!window)
1091    return NULL;
1092  BrowserView* browser_view =
1093      BrowserView::GetBrowserViewForNativeWindow(window);
1094  // We don't allow drops on windows that don't have tabstrips.
1095  if (!browser_view ||
1096      !browser_view->browser()->SupportsWindowFeature(
1097          Browser::FEATURE_TABSTRIP))
1098    return NULL;
1099
1100  TabStrip* other_tabstrip = browser_view->tabstrip();
1101  TabStrip* tab_strip =
1102      attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_;
1103  DCHECK(tab_strip);
1104
1105  return other_tabstrip->controller()->IsCompatibleWith(tab_strip) ?
1106      other_tabstrip : NULL;
1107}
1108
1109bool TabDragController::DoesTabStripContain(
1110    TabStrip* tabstrip,
1111    const gfx::Point& point_in_screen) const {
1112  // Make sure the specified screen point is actually within the bounds of the
1113  // specified tabstrip...
1114  gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
1115  return point_in_screen.x() < tabstrip_bounds.right() &&
1116      point_in_screen.x() >= tabstrip_bounds.x() &&
1117      DoesRectContainVerticalPointExpanded(tabstrip_bounds,
1118                                           kVerticalDetachMagnetism,
1119                                           point_in_screen.y());
1120}
1121
1122void TabDragController::Attach(TabStrip* attached_tabstrip,
1123                               const gfx::Point& point_in_screen) {
1124  DCHECK(!attached_tabstrip_);  // We should already have detached by the time
1125                                // we get here.
1126
1127  attached_tabstrip_ = attached_tabstrip;
1128
1129  // And we don't need the dragged view.
1130  view_.reset();
1131
1132  std::vector<Tab*> tabs =
1133      GetTabsMatchingDraggedContents(attached_tabstrip_);
1134
1135  if (tabs.empty()) {
1136    // Transitioning from detached to attached to a new tabstrip. Add tabs to
1137    // the new model.
1138
1139    selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel());
1140
1141    if (!detach_into_browser_) {
1142      // Remove ourselves as the delegate now that the dragged WebContents is
1143      // being inserted back into a Browser.
1144      for (size_t i = 0; i < drag_data_.size(); ++i) {
1145        drag_data_[i].contents->SetDelegate(NULL);
1146        drag_data_[i].original_delegate = NULL;
1147      }
1148
1149      // Return the WebContents to normalcy.
1150      source_dragged_contents()->DecrementCapturerCount();
1151    }
1152
1153    // Inserting counts as a move. We don't want the tabs to jitter when the
1154    // user moves the tab immediately after attaching it.
1155    last_move_screen_loc_ = point_in_screen.x();
1156
1157    // Figure out where to insert the tab based on the bounds of the dragged
1158    // representation and the ideal bounds of the other Tabs already in the
1159    // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
1160    // changing due to animation).
1161    gfx::Point tab_strip_point(point_in_screen);
1162    views::View::ConvertPointToTarget(NULL, attached_tabstrip_,
1163                                      &tab_strip_point);
1164    tab_strip_point.set_x(
1165        attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()));
1166    tab_strip_point.Offset(-mouse_offset_.x(), -mouse_offset_.y());
1167    gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point);
1168    int index = GetInsertionIndexForDraggedBounds(bounds);
1169    base::AutoReset<bool> setter(&is_mutating_, true);
1170    for (size_t i = 0; i < drag_data_.size(); ++i) {
1171      int add_types = TabStripModel::ADD_NONE;
1172      if (attached_tabstrip_->touch_layout_.get()) {
1173        // StackedTabStripLayout positions relative to the active tab, if we
1174        // don't add the tab as active things bounce around.
1175        DCHECK_EQ(1u, drag_data_.size());
1176        add_types |= TabStripModel::ADD_ACTIVE;
1177      }
1178      if (drag_data_[i].pinned)
1179        add_types |= TabStripModel::ADD_PINNED;
1180      GetModel(attached_tabstrip_)->InsertWebContentsAt(
1181          index + i, drag_data_[i].contents, add_types);
1182    }
1183
1184    tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
1185  }
1186  DCHECK_EQ(tabs.size(), drag_data_.size());
1187  for (size_t i = 0; i < drag_data_.size(); ++i)
1188    drag_data_[i].attached_tab = tabs[i];
1189
1190  attached_tabstrip_->StartedDraggingTabs(tabs);
1191
1192  ResetSelection(GetModel(attached_tabstrip_));
1193
1194  // The size of the dragged tab may have changed. Adjust the x offset so that
1195  // ratio of mouse_offset_ to original width is maintained.
1196  std::vector<Tab*> tabs_to_source(tabs);
1197  tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1,
1198                       tabs_to_source.end());
1199  int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) -
1200      tabs[source_tab_index_]->width() +
1201      static_cast<int>(offset_to_width_ratio_ *
1202                       tabs[source_tab_index_]->width());
1203  mouse_offset_.set_x(new_x);
1204
1205  // Transfer ownership of us to the new tabstrip as well as making sure the
1206  // window has capture. This is important so that if activation changes the
1207  // drag isn't prematurely canceled.
1208  if (detach_into_browser_) {
1209    attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1210    attached_tabstrip_->OwnDragController(this);
1211  }
1212
1213  // Redirect all mouse events to the TabStrip so that the tab that originated
1214  // the drag can safely be deleted.
1215  if (detach_into_browser_ || attached_tabstrip_ == source_tabstrip_) {
1216    static_cast<views::internal::RootView*>(
1217        attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler(
1218            attached_tabstrip_);
1219  }
1220}
1221
1222void TabDragController::Detach(ReleaseCapture release_capture) {
1223  // When the user detaches we assume they want to reorder.
1224  move_behavior_ = REORDER;
1225
1226  // Release ownership of the drag controller and mouse capture. When we
1227  // reattach ownership is transfered.
1228  if (detach_into_browser_) {
1229    attached_tabstrip_->ReleaseDragController();
1230    if (release_capture == RELEASE_CAPTURE)
1231      attached_tabstrip_->GetWidget()->ReleaseCapture();
1232  }
1233
1234  mouse_move_direction_ = kMovedMouseLeft | kMovedMouseRight;
1235
1236  // Prevent the WebContents HWND from being hidden by any of the model
1237  // operations performed during the drag.
1238  if (!detach_into_browser_)
1239    source_dragged_contents()->IncrementCapturerCount();
1240
1241  std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs(0);
1242  TabStripModel* attached_model = GetModel(attached_tabstrip_);
1243  std::vector<TabRendererData> tab_data;
1244  for (size_t i = 0; i < drag_data_.size(); ++i) {
1245    tab_data.push_back(drag_data_[i].attached_tab->data());
1246    int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents);
1247    DCHECK_NE(-1, index);
1248
1249    // Hide the tab so that the user doesn't see it animate closed.
1250    drag_data_[i].attached_tab->SetVisible(false);
1251
1252    attached_model->DetachWebContentsAt(index);
1253
1254    // Detaching resets the delegate, but we still want to be the delegate.
1255    if (!detach_into_browser_)
1256      drag_data_[i].contents->SetDelegate(this);
1257
1258    // Detaching may end up deleting the tab, drop references to it.
1259    drag_data_[i].attached_tab = NULL;
1260  }
1261
1262  // If we've removed the last Tab from the TabStrip, hide the frame now.
1263  if (!attached_model->empty()) {
1264    if (!selection_model_before_attach_.empty() &&
1265        selection_model_before_attach_.active() >= 0 &&
1266        selection_model_before_attach_.active() < attached_model->count()) {
1267      // Restore the selection.
1268      attached_model->SetSelectionFromModel(selection_model_before_attach_);
1269    } else if (attached_tabstrip_ == source_tabstrip_ &&
1270               !initial_selection_model_.empty()) {
1271      // First time detaching from the source tabstrip. Reset selection model to
1272      // initial_selection_model_. Before resetting though we have to remove all
1273      // the tabs from initial_selection_model_ as it was created with the tabs
1274      // still there.
1275      ui::ListSelectionModel selection_model;
1276      selection_model.Copy(initial_selection_model_);
1277      for (DragData::const_reverse_iterator i(drag_data_.rbegin());
1278           i != drag_data_.rend(); ++i) {
1279        selection_model.DecrementFrom(i->source_model_index);
1280      }
1281      // We may have cleared out the selection model. Only reset it if it
1282      // contains something.
1283      if (!selection_model.empty())
1284        attached_model->SetSelectionFromModel(selection_model);
1285    }
1286  } else if (!detach_into_browser_) {
1287    HideFrame();
1288  }
1289
1290  // Create the dragged view.
1291  if (!detach_into_browser_)
1292    CreateDraggedView(tab_data, drag_bounds);
1293
1294  attached_tabstrip_->DraggedTabsDetached();
1295  attached_tabstrip_ = NULL;
1296}
1297
1298void TabDragController::DetachIntoNewBrowserAndRunMoveLoop(
1299    const gfx::Point& point_in_screen) {
1300  if (GetModel(attached_tabstrip_)->count() ==
1301      static_cast<int>(drag_data_.size())) {
1302    // All the tabs in a browser are being dragged but all the tabs weren't
1303    // initially being dragged. For this to happen the user would have to
1304    // start dragging a set of tabs, the other tabs close, then detach.
1305    RunMoveLoop(GetWindowOffset(point_in_screen));
1306    return;
1307  }
1308
1309  // Create a new browser to house the dragged tabs and have the OS run a move
1310  // loop.
1311  gfx::Point attached_point = GetAttachedDragPoint(point_in_screen);
1312
1313  // Calculate the bounds for the tabs from the attached_tab_strip. We do this
1314  // so that the tabs don't change size when detached.
1315  std::vector<gfx::Rect> drag_bounds =
1316      CalculateBoundsForDraggedTabs(attached_point.x());
1317
1318  // Stash the current window size and tab area width.
1319  gfx::Size source_size =
1320      attached_tabstrip_->GetWidget()->GetWindowBoundsInScreen().size();
1321  int available_source_width = attached_tabstrip_->tab_area_width();
1322
1323  gfx::Vector2d drag_offset;
1324  Browser* browser = CreateBrowserForDrag(
1325      attached_tabstrip_, point_in_screen, &drag_offset, &drag_bounds);
1326  Detach(DONT_RELEASE_CAPTURE);
1327  BrowserView* dragged_browser_view =
1328      BrowserView::GetBrowserViewForBrowser(browser);
1329  dragged_browser_view->GetWidget()->SetVisibilityChangedAnimationsEnabled(
1330      false);
1331  Attach(dragged_browser_view->tabstrip(), gfx::Point());
1332
1333  // If the window size has changed, the tab positioning will be quite off.
1334  if (source_size !=
1335          attached_tabstrip_->GetWidget()->GetWindowBoundsInScreen().size()) {
1336    // First, scale the drag bounds such that they fit within the new window
1337    // while maintaining the same relative positions. This scales the tabs
1338    // down so that they occupy the same relative width on the new tab strip,
1339    // clamping to minimum tab width.
1340      int available_attached_width = attached_tabstrip_->tab_area_width();
1341    float x_scale =
1342        static_cast<float>(available_attached_width) / available_source_width;
1343    int x_offset = std::ceil((1.0 - x_scale) * drag_bounds[0].x());
1344    int accumulated_width_offset = 0;
1345    for (size_t i = 0; i < drag_bounds.size(); ++i) {
1346      gfx::Rect& tab_bounds = drag_bounds[i];
1347      tab_bounds.Offset(-(x_offset + accumulated_width_offset), 0);
1348      int old_width = tab_bounds.width();
1349      int min_width = (i == source_tab_index_) ?
1350          drag_data_[i].attached_tab->GetMinimumSelectedSize().width() :
1351          drag_data_[i].attached_tab->GetMinimumUnselectedSize().width();
1352      int new_width =
1353          std::max(min_width, static_cast<int>(std::ceil(old_width * x_scale)));
1354      tab_bounds.set_width(new_width);
1355      accumulated_width_offset += (old_width - tab_bounds.width());
1356    }
1357
1358    // Next, re-position the restored window such that the tab that was dragged
1359    // remains centered under the mouse cursor. The two offsets needed here are
1360    // the offset of the dragged tab in widget coordinates, and half the dragged
1361    // tab width. The sum of these is the horizontal distance from the mouse
1362    // cursor to the window edge.
1363    gfx::Point offset(drag_bounds[source_tab_index_].origin());
1364    views::View::ConvertPointToWidget(attached_tabstrip_, &offset);
1365    int half_tab_width = drag_bounds[source_tab_index_].width() / 2;
1366    gfx::Rect new_bounds = browser->window()->GetBounds();
1367    new_bounds.set_x(point_in_screen.x() - offset.x() - half_tab_width);
1368
1369    // To account for the extra vertical on restored windows that is absent
1370    // on maximized windows, add an additional vertical offset extracted from
1371    // the tab strip.
1372    new_bounds.Offset(0, -attached_tabstrip_->button_v_offset());
1373    browser->window()->SetBounds(new_bounds);
1374  }
1375
1376  attached_tabstrip_->SetTabBoundsForDrag(drag_bounds);
1377
1378  WindowPositionManagedUpdater updater;
1379  dragged_browser_view->GetWidget()->AddObserver(&updater);
1380  browser->window()->Show();
1381  dragged_browser_view->GetWidget()->RemoveObserver(&updater);
1382
1383  browser->window()->Activate();
1384  dragged_browser_view->GetWidget()->SetVisibilityChangedAnimationsEnabled(
1385      true);
1386  RunMoveLoop(drag_offset);
1387}
1388
1389void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) {
1390  // If the user drags the whole window we'll assume they are going to attach to
1391  // another window and therefore want to reorder.
1392  move_behavior_ = REORDER;
1393
1394  move_loop_widget_ = GetAttachedBrowserWidget();
1395  DCHECK(move_loop_widget_);
1396  SetTrackedByWorkspace(move_loop_widget_->GetNativeView(), false);
1397  move_loop_widget_->AddObserver(this);
1398  is_dragging_window_ = true;
1399  bool destroyed = false;
1400  destroyed_ = &destroyed;
1401  // Running the move loop releases mouse capture on non-ash, which triggers
1402  // destroying the drag loop. Release mouse capture ourself before this while
1403  // the DragController isn't owned by the TabStrip.
1404  if (host_desktop_type_ != chrome::HOST_DESKTOP_TYPE_ASH) {
1405    attached_tabstrip_->ReleaseDragController();
1406    attached_tabstrip_->GetWidget()->ReleaseCapture();
1407    attached_tabstrip_->OwnDragController(this);
1408  }
1409  const views::Widget::MoveLoopSource move_loop_source =
1410      event_source_ == EVENT_SOURCE_MOUSE ?
1411      views::Widget::MOVE_LOOP_SOURCE_MOUSE :
1412      views::Widget::MOVE_LOOP_SOURCE_TOUCH;
1413  views::Widget::MoveLoopResult result =
1414      move_loop_widget_->RunMoveLoop(drag_offset, move_loop_source);
1415  content::NotificationService::current()->Notify(
1416      chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE,
1417      content::NotificationService::AllBrowserContextsAndSources(),
1418      content::NotificationService::NoDetails());
1419
1420  if (destroyed)
1421    return;
1422  destroyed_ = NULL;
1423  // Under chromeos we immediately set the |move_loop_widget_| to NULL.
1424  if (move_loop_widget_) {
1425    move_loop_widget_->RemoveObserver(this);
1426    move_loop_widget_ = NULL;
1427  }
1428  is_dragging_window_ = false;
1429  waiting_for_run_loop_to_exit_ = false;
1430  if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) {
1431    end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING;
1432    if (tab_strip_to_attach_to_after_exit_) {
1433      gfx::Point point_in_screen(GetCursorScreenPoint());
1434      Detach(DONT_RELEASE_CAPTURE);
1435      Attach(tab_strip_to_attach_to_after_exit_, point_in_screen);
1436      // Move the tabs into position.
1437      MoveAttached(point_in_screen);
1438      attached_tabstrip_->GetWidget()->Activate();
1439      tab_strip_to_attach_to_after_exit_ = NULL;
1440    }
1441    DCHECK(attached_tabstrip_);
1442    attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1443  } else if (active_) {
1444    EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ?
1445            END_DRAG_CANCEL : END_DRAG_COMPLETE);
1446  }
1447}
1448
1449int TabDragController::GetInsertionIndexFrom(const gfx::Rect& dragged_bounds,
1450                                             int start,
1451                                             int delta) const {
1452  for (int i = start, tab_count = attached_tabstrip_->tab_count();
1453       i >= 0 && i < tab_count; i += delta) {
1454    const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i);
1455    gfx::Rect left_half, right_half;
1456    ideal_bounds.SplitVertically(&left_half, &right_half);
1457    if (dragged_bounds.x() >= right_half.x() &&
1458        dragged_bounds.x() < right_half.right()) {
1459      return i + 1;
1460    } else if (dragged_bounds.x() >= left_half.x() &&
1461               dragged_bounds.x() < left_half.right()) {
1462      return i;
1463    }
1464  }
1465  return -1;
1466}
1467
1468int TabDragController::GetInsertionIndexForDraggedBounds(
1469    const gfx::Rect& dragged_bounds) const {
1470  int index = -1;
1471  if (attached_tabstrip_->touch_layout_.get()) {
1472    index = GetInsertionIndexForDraggedBoundsStacked(dragged_bounds);
1473    if (index != -1) {
1474      // Only move the tab to the left/right if the user actually moved the
1475      // mouse that way. This is necessary as tabs with stacked tabs
1476      // before/after them have multiple drag positions.
1477      int active_index = attached_tabstrip_->touch_layout_->active_index();
1478      if ((index < active_index &&
1479           (mouse_move_direction_ & kMovedMouseLeft) == 0) ||
1480          (index > active_index &&
1481           (mouse_move_direction_ & kMovedMouseRight) == 0)) {
1482        index = active_index;
1483      }
1484    }
1485  } else {
1486    index = GetInsertionIndexFrom(dragged_bounds, 0, 1);
1487  }
1488  if (index == -1) {
1489    int tab_count = attached_tabstrip_->tab_count();
1490    int right_tab_x = tab_count == 0 ? 0 :
1491        attached_tabstrip_->ideal_bounds(tab_count - 1).right();
1492    if (dragged_bounds.right() > right_tab_x) {
1493      index = GetModel(attached_tabstrip_)->count();
1494    } else {
1495      index = 0;
1496    }
1497  }
1498
1499  if (!drag_data_[0].attached_tab) {
1500    // If 'attached_tab' is NULL, it means we're in the process of attaching and
1501    // don't need to constrain the index.
1502    return index;
1503  }
1504
1505  int max_index = GetModel(attached_tabstrip_)->count() -
1506      static_cast<int>(drag_data_.size());
1507  return std::max(0, std::min(max_index, index));
1508}
1509
1510bool TabDragController::ShouldDragToNextStackedTab(
1511    const gfx::Rect& dragged_bounds,
1512    int index) const {
1513  if (index + 1 >= attached_tabstrip_->tab_count() ||
1514      !attached_tabstrip_->touch_layout_->IsStacked(index + 1) ||
1515      (mouse_move_direction_ & kMovedMouseRight) == 0)
1516    return false;
1517
1518  int active_x = attached_tabstrip_->ideal_bounds(index).x();
1519  int next_x = attached_tabstrip_->ideal_bounds(index + 1).x();
1520  int mid_x = std::min(next_x - kStackedDistance,
1521                       active_x + (next_x - active_x) / 4);
1522  return dragged_bounds.x() >= mid_x;
1523}
1524
1525bool TabDragController::ShouldDragToPreviousStackedTab(
1526    const gfx::Rect& dragged_bounds,
1527    int index) const {
1528  if (index - 1 < attached_tabstrip_->GetMiniTabCount() ||
1529      !attached_tabstrip_->touch_layout_->IsStacked(index - 1) ||
1530      (mouse_move_direction_ & kMovedMouseLeft) == 0)
1531    return false;
1532
1533  int active_x = attached_tabstrip_->ideal_bounds(index).x();
1534  int previous_x = attached_tabstrip_->ideal_bounds(index - 1).x();
1535  int mid_x = std::max(previous_x + kStackedDistance,
1536                       active_x - (active_x - previous_x) / 4);
1537  return dragged_bounds.x() <= mid_x;
1538}
1539
1540int TabDragController::GetInsertionIndexForDraggedBoundsStacked(
1541    const gfx::Rect& dragged_bounds) const {
1542  StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
1543  int active_index = touch_layout->active_index();
1544  // Search from the active index to the front of the tabstrip. Do this as tabs
1545  // overlap each other from the active index.
1546  int index = GetInsertionIndexFrom(dragged_bounds, active_index, -1);
1547  if (index != active_index)
1548    return index;
1549  if (index == -1)
1550    return GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1551
1552  // The position to drag to corresponds to the active tab. If the next/previous
1553  // tab is stacked, then shorten the distance used to determine insertion
1554  // bounds. We do this as GetInsertionIndexFrom() uses the bounds of the
1555  // tabs. When tabs are stacked the next/previous tab is on top of the tab.
1556  if (active_index + 1 < attached_tabstrip_->tab_count() &&
1557      touch_layout->IsStacked(active_index + 1)) {
1558    index = GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1559    if (index == -1 && ShouldDragToNextStackedTab(dragged_bounds, active_index))
1560      index = active_index + 1;
1561    else if (index == -1)
1562      index = active_index;
1563  } else if (ShouldDragToPreviousStackedTab(dragged_bounds, active_index)) {
1564    index = active_index - 1;
1565  }
1566  return index;
1567}
1568
1569gfx::Rect TabDragController::GetDraggedViewTabStripBounds(
1570    const gfx::Point& tab_strip_point) {
1571  // attached_tab is NULL when inserting into a new tabstrip.
1572  if (source_tab_drag_data()->attached_tab) {
1573    return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1574                     source_tab_drag_data()->attached_tab->width(),
1575                     source_tab_drag_data()->attached_tab->height());
1576  }
1577
1578  double sel_width, unselected_width;
1579  attached_tabstrip_->GetCurrentTabWidths(&sel_width, &unselected_width);
1580  return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1581                   static_cast<int>(sel_width),
1582                   Tab::GetStandardSize().height());
1583}
1584
1585gfx::Point TabDragController::GetAttachedDragPoint(
1586    const gfx::Point& point_in_screen) {
1587  DCHECK(attached_tabstrip_);  // The tab must be attached.
1588
1589  gfx::Point tab_loc(point_in_screen);
1590  views::View::ConvertPointToTarget(NULL, attached_tabstrip_, &tab_loc);
1591  int x =
1592      attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
1593
1594  // TODO: consider caching this.
1595  std::vector<Tab*> attached_tabs;
1596  for (size_t i = 0; i < drag_data_.size(); ++i)
1597    attached_tabs.push_back(drag_data_[i].attached_tab);
1598  int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs);
1599  int max_x = attached_tabstrip_->width() - size;
1600  return gfx::Point(std::min(std::max(x, 0), max_x), 0);
1601}
1602
1603std::vector<Tab*> TabDragController::GetTabsMatchingDraggedContents(
1604    TabStrip* tabstrip) {
1605  TabStripModel* model = GetModel(attached_tabstrip_);
1606  std::vector<Tab*> tabs;
1607  for (size_t i = 0; i < drag_data_.size(); ++i) {
1608    int model_index = model->GetIndexOfWebContents(drag_data_[i].contents);
1609    if (model_index == TabStripModel::kNoTab)
1610      return std::vector<Tab*>();
1611    tabs.push_back(tabstrip->tab_at(model_index));
1612  }
1613  return tabs;
1614}
1615
1616std::vector<gfx::Rect> TabDragController::CalculateBoundsForDraggedTabs(
1617    int x_offset) {
1618  std::vector<gfx::Rect> drag_bounds;
1619  std::vector<Tab*> attached_tabs;
1620  for (size_t i = 0; i < drag_data_.size(); ++i)
1621    attached_tabs.push_back(drag_data_[i].attached_tab);
1622  attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs,
1623                                                    &drag_bounds);
1624  if (x_offset != 0) {
1625    for (size_t i = 0; i < drag_bounds.size(); ++i)
1626      drag_bounds[i].set_x(drag_bounds[i].x() + x_offset);
1627  }
1628  return drag_bounds;
1629}
1630
1631void TabDragController::EndDragImpl(EndDragType type) {
1632  DCHECK(active_);
1633  active_ = false;
1634
1635  bring_to_front_timer_.Stop();
1636  move_stacked_timer_.Stop();
1637
1638  if (is_dragging_window_) {
1639    // SetTrackedByWorkspace() may call us back (by way of the window bounds
1640    // changing). Set |waiting_for_run_loop_to_exit_| here so that if that
1641    // happens we ignore it.
1642    waiting_for_run_loop_to_exit_ = true;
1643
1644    if (type == NORMAL || (type == TAB_DESTROYED && drag_data_.size() > 1)) {
1645      SetTrackedByWorkspace(GetAttachedBrowserWidget()->GetNativeView(), true);
1646      SetWindowPositionManaged(GetAttachedBrowserWidget()->GetNativeView(),
1647                               true);
1648    }
1649
1650    // End the nested drag loop.
1651    GetAttachedBrowserWidget()->EndMoveLoop();
1652  }
1653
1654  // Hide the current dock controllers.
1655  for (size_t i = 0; i < dock_controllers_.size(); ++i) {
1656    // Be sure and clear the controller first, that way if Hide ends up
1657    // deleting the controller it won't call us back.
1658    dock_controllers_[i]->clear_controller();
1659    dock_controllers_[i]->Hide();
1660  }
1661  dock_controllers_.clear();
1662  dock_windows_.clear();
1663
1664  if (type != TAB_DESTROYED) {
1665    // We only finish up the drag if we were actually dragging. If start_drag_
1666    // is false, the user just clicked and released and didn't move the mouse
1667    // enough to trigger a drag.
1668    if (started_drag_) {
1669      RestoreFocus();
1670      if (type == CANCELED)
1671        RevertDrag();
1672      else
1673        CompleteDrag();
1674    }
1675  } else if (drag_data_.size() > 1) {
1676    RevertDrag();
1677  }  // else case the only tab we were dragging was deleted. Nothing to do.
1678
1679  if (!detach_into_browser_)
1680    ResetDelegates();
1681
1682  // Clear out drag data so we don't attempt to do anything with it.
1683  drag_data_.clear();
1684
1685  TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
1686      attached_tabstrip_ : source_tabstrip_;
1687  owning_tabstrip->DestroyDragController();
1688}
1689
1690void TabDragController::RevertDrag() {
1691  std::vector<Tab*> tabs;
1692  for (size_t i = 0; i < drag_data_.size(); ++i) {
1693    if (drag_data_[i].contents) {
1694      // Contents is NULL if a tab was destroyed while the drag was under way.
1695      tabs.push_back(drag_data_[i].attached_tab);
1696      RevertDragAt(i);
1697    }
1698  }
1699
1700  bool restore_frame = !detach_into_browser_ &&
1701                       attached_tabstrip_ != source_tabstrip_;
1702  if (attached_tabstrip_) {
1703    if (attached_tabstrip_ == source_tabstrip_) {
1704      source_tabstrip_->StoppedDraggingTabs(
1705          tabs, initial_tab_positions_, move_behavior_ == MOVE_VISIBILE_TABS,
1706          false);
1707    } else {
1708      attached_tabstrip_->DraggedTabsDetached();
1709    }
1710  }
1711
1712  if (initial_selection_model_.empty())
1713    ResetSelection(GetModel(source_tabstrip_));
1714  else
1715    GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_);
1716
1717  // If we're not attached to any TabStrip, or attached to some other TabStrip,
1718  // we need to restore the bounds of the original TabStrip's frame, in case
1719  // it has been hidden.
1720  if (restore_frame && !restore_bounds_.IsEmpty())
1721    source_tabstrip_->GetWidget()->SetBounds(restore_bounds_);
1722
1723  if (detach_into_browser_ && source_tabstrip_)
1724    source_tabstrip_->GetWidget()->Activate();
1725
1726  // Return the WebContents to normalcy.  If the tab was attached to a
1727  // TabStrip before the revert, the decrement has already occurred.
1728  // If the tab was destroyed, don't attempt to dereference the
1729  // WebContents pointer.
1730  if (!detach_into_browser_ && !attached_tabstrip_ && source_dragged_contents())
1731    source_dragged_contents()->DecrementCapturerCount();
1732}
1733
1734void TabDragController::ResetSelection(TabStripModel* model) {
1735  DCHECK(model);
1736  ui::ListSelectionModel selection_model;
1737  bool has_one_valid_tab = false;
1738  for (size_t i = 0; i < drag_data_.size(); ++i) {
1739    // |contents| is NULL if a tab was deleted out from under us.
1740    if (drag_data_[i].contents) {
1741      int index = model->GetIndexOfWebContents(drag_data_[i].contents);
1742      DCHECK_NE(-1, index);
1743      selection_model.AddIndexToSelection(index);
1744      if (!has_one_valid_tab || i == source_tab_index_) {
1745        // Reset the active/lead to the first tab. If the source tab is still
1746        // valid we'll reset these again later on.
1747        selection_model.set_active(index);
1748        selection_model.set_anchor(index);
1749        has_one_valid_tab = true;
1750      }
1751    }
1752  }
1753  if (!has_one_valid_tab)
1754    return;
1755
1756  model->SetSelectionFromModel(selection_model);
1757}
1758
1759void TabDragController::RevertDragAt(size_t drag_index) {
1760  DCHECK(started_drag_);
1761  DCHECK(source_tabstrip_);
1762
1763  base::AutoReset<bool> setter(&is_mutating_, true);
1764  TabDragData* data = &(drag_data_[drag_index]);
1765  if (attached_tabstrip_) {
1766    int index =
1767        GetModel(attached_tabstrip_)->GetIndexOfWebContents(data->contents);
1768    if (attached_tabstrip_ != source_tabstrip_) {
1769      // The Tab was inserted into another TabStrip. We need to put it back
1770      // into the original one.
1771      GetModel(attached_tabstrip_)->DetachWebContentsAt(index);
1772      // TODO(beng): (Cleanup) seems like we should use Attach() for this
1773      //             somehow.
1774      GetModel(source_tabstrip_)->InsertWebContentsAt(
1775          data->source_model_index, data->contents,
1776          (data->pinned ? TabStripModel::ADD_PINNED : 0));
1777    } else {
1778      // The Tab was moved within the TabStrip where the drag was initiated.
1779      // Move it back to the starting location.
1780      GetModel(source_tabstrip_)->MoveWebContentsAt(
1781          index, data->source_model_index, false);
1782    }
1783  } else {
1784    // The Tab was detached from the TabStrip where the drag began, and has not
1785    // been attached to any other TabStrip. We need to put it back into the
1786    // source TabStrip.
1787    GetModel(source_tabstrip_)->InsertWebContentsAt(
1788        data->source_model_index, data->contents,
1789        (data->pinned ? TabStripModel::ADD_PINNED : 0));
1790  }
1791}
1792
1793void TabDragController::CompleteDrag() {
1794  DCHECK(started_drag_);
1795
1796  if (attached_tabstrip_) {
1797    attached_tabstrip_->StoppedDraggingTabs(
1798        GetTabsMatchingDraggedContents(attached_tabstrip_),
1799        initial_tab_positions_,
1800        move_behavior_ == MOVE_VISIBILE_TABS,
1801        true);
1802  } else {
1803    if (dock_info_.type() != DockInfo::NONE) {
1804      switch (dock_info_.type()) {
1805        case DockInfo::LEFT_OF_WINDOW:
1806          content::RecordAction(UserMetricsAction("DockingWindow_Left"));
1807          break;
1808
1809        case DockInfo::RIGHT_OF_WINDOW:
1810          content::RecordAction(UserMetricsAction("DockingWindow_Right"));
1811          break;
1812
1813        case DockInfo::BOTTOM_OF_WINDOW:
1814          content::RecordAction(UserMetricsAction("DockingWindow_Bottom"));
1815          break;
1816
1817        case DockInfo::TOP_OF_WINDOW:
1818          content::RecordAction(UserMetricsAction("DockingWindow_Top"));
1819          break;
1820
1821        case DockInfo::MAXIMIZE:
1822          content::RecordAction(
1823              UserMetricsAction("DockingWindow_Maximize"));
1824          break;
1825
1826        case DockInfo::LEFT_HALF:
1827          content::RecordAction(
1828              UserMetricsAction("DockingWindow_LeftHalf"));
1829          break;
1830
1831        case DockInfo::RIGHT_HALF:
1832          content::RecordAction(
1833              UserMetricsAction("DockingWindow_RightHalf"));
1834          break;
1835
1836        case DockInfo::BOTTOM_HALF:
1837          content::RecordAction(
1838              UserMetricsAction("DockingWindow_BottomHalf"));
1839          break;
1840
1841        default:
1842          NOTREACHED();
1843          break;
1844      }
1845    }
1846    // Compel the model to construct a new window for the detached
1847    // WebContentses.
1848    views::Widget* widget = source_tabstrip_->GetWidget();
1849    gfx::Rect window_bounds(widget->GetRestoredBounds());
1850    window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));
1851
1852    // When modifying the following if statement, please make sure not to
1853    // introduce issue listed in http://crbug.com/6223 comment #11.
1854    bool rtl_ui = base::i18n::IsRTL();
1855    bool has_dock_position = (dock_info_.type() != DockInfo::NONE);
1856    if (rtl_ui && has_dock_position) {
1857      // Mirror X axis so the docked tab is aligned using the mouse click as
1858      // the top-right corner.
1859      window_bounds.set_x(window_bounds.x() - window_bounds.width());
1860    }
1861    base::AutoReset<bool> setter(&is_mutating_, true);
1862
1863    std::vector<TabStripModelDelegate::NewStripContents> contentses;
1864    for (size_t i = 0; i < drag_data_.size(); ++i) {
1865      TabStripModelDelegate::NewStripContents item;
1866      item.web_contents = drag_data_[i].contents;
1867      item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED
1868                                            : TabStripModel::ADD_NONE;
1869      contentses.push_back(item);
1870    };
1871
1872    Browser* new_browser =
1873        GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents(
1874            contentses, window_bounds, dock_info_, widget->IsMaximized());
1875    ResetSelection(new_browser->tab_strip_model());
1876    new_browser->window()->Show();
1877
1878    // Return the WebContents to normalcy.
1879    if (!detach_into_browser_)
1880      source_dragged_contents()->DecrementCapturerCount();
1881  }
1882
1883  CleanUpHiddenFrame();
1884}
1885
1886void TabDragController::ResetDelegates() {
1887  DCHECK(!detach_into_browser_);
1888  for (size_t i = 0; i < drag_data_.size(); ++i) {
1889    if (drag_data_[i].contents &&
1890        drag_data_[i].contents->GetDelegate() == this) {
1891      drag_data_[i].contents->SetDelegate(
1892          drag_data_[i].original_delegate);
1893    }
1894  }
1895}
1896
1897void TabDragController::CreateDraggedView(
1898    const std::vector<TabRendererData>& data,
1899    const std::vector<gfx::Rect>& renderer_bounds) {
1900#if !defined(USE_AURA)
1901  DCHECK(!view_.get());
1902  DCHECK_EQ(data.size(), drag_data_.size());
1903
1904  // Set up the photo booth to start capturing the contents of the dragged
1905  // WebContents.
1906  NativeViewPhotobooth* photobooth = NativeViewPhotobooth::Create(
1907      source_dragged_contents()->GetView()->GetNativeView());
1908
1909  gfx::Rect content_bounds;
1910  source_dragged_contents()->GetView()->GetContainerBounds(&content_bounds);
1911
1912  std::vector<views::View*> renderers;
1913  for (size_t i = 0; i < drag_data_.size(); ++i) {
1914    Tab* renderer = source_tabstrip_->CreateTabForDragging();
1915    renderer->SetData(data[i]);
1916    renderers.push_back(renderer);
1917  }
1918  // DraggedTabView takes ownership of the renderers.
1919  view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_,
1920                                 content_bounds.size(), photobooth));
1921#else
1922  // Aura always hits the |detach_into_browser_| path.
1923  NOTREACHED();
1924#endif
1925}
1926
1927gfx::Rect TabDragController::GetViewScreenBounds(
1928    views::View* view) const {
1929  gfx::Point view_topleft;
1930  views::View::ConvertPointToScreen(view, &view_topleft);
1931  gfx::Rect view_screen_bounds = view->GetLocalBounds();
1932  view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());
1933  return view_screen_bounds;
1934}
1935
1936void TabDragController::HideFrame() {
1937#if defined(OS_WIN) && !defined(USE_AURA)
1938  // We don't actually hide the window, rather we just move it way off-screen.
1939  // If we actually hide it, we stop receiving drag events.
1940  //
1941  // Windows coordinates are 16 bit values. Additionally mouse events are
1942  // relative, this means if we move this window to the max position it is easy
1943  // to trigger overflow. To avoid this we don't move to the max position,
1944  // rather some where reasonably large. This should avoid common overflow
1945  // problems.
1946  // An alternative approach is to query the mouse pointer and ignore the
1947  // location on the mouse (early versions did this). This proves problematic as
1948  // if we happen to get behind in event processing it is all to easy to process
1949  // a release in the wrong location, triggering either an unexpected move or an
1950  // unexpected detach.
1951  HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView();
1952  RECT wr;
1953  GetWindowRect(frame_hwnd, &wr);
1954  MoveWindow(frame_hwnd, 0x3FFF, 0x3FFF, wr.right - wr.left,
1955             wr.bottom - wr.top, TRUE);
1956
1957  // We also save the bounds of the window prior to it being moved, so that if
1958  // the drag session is aborted we can restore them.
1959  restore_bounds_ = gfx::Rect(wr);
1960#else
1961  // Shouldn't hit as aura triggers the |detach_into_browser_| path.
1962  NOTREACHED();
1963#endif
1964}
1965
1966void TabDragController::CleanUpHiddenFrame() {
1967  // If the model we started dragging from is now empty, we must ask the
1968  // delegate to close the frame.
1969  if (!detach_into_browser_ && GetModel(source_tabstrip_)->empty())
1970    GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession();
1971}
1972
1973void TabDragController::DockDisplayerDestroyed(
1974    DockDisplayer* controller) {
1975  DockWindows::iterator dock_i =
1976      dock_windows_.find(controller->popup_view());
1977  if (dock_i != dock_windows_.end())
1978    dock_windows_.erase(dock_i);
1979  else
1980    NOTREACHED();
1981
1982  std::vector<DockDisplayer*>::iterator i =
1983      std::find(dock_controllers_.begin(), dock_controllers_.end(),
1984                controller);
1985  if (i != dock_controllers_.end())
1986    dock_controllers_.erase(i);
1987  else
1988    NOTREACHED();
1989}
1990
1991void TabDragController::BringWindowUnderPointToFront(
1992    const gfx::Point& point_in_screen) {
1993  // If we're going to dock to another window, bring it to the front.
1994  gfx::NativeWindow window = dock_info_.window();
1995  if (!window) {
1996    views::View* dragged_view;
1997    if (view_.get())
1998      dragged_view = view_.get();
1999    else
2000      dragged_view = attached_tabstrip_;
2001    gfx::NativeView dragged_native_view =
2002        dragged_view->GetWidget()->GetNativeView();
2003    dock_windows_.insert(dragged_native_view);
2004    window = DockInfo::GetLocalProcessWindowAtPoint(
2005        host_desktop_type_,
2006        point_in_screen,
2007        dock_windows_);
2008    dock_windows_.erase(dragged_native_view);
2009  }
2010  if (window) {
2011    views::Widget* widget_window = views::Widget::GetWidgetForNativeView(
2012        window);
2013    if (widget_window)
2014      widget_window->StackAtTop();
2015    else
2016      return;
2017
2018    // The previous call made the window appear on top of the dragged window,
2019    // move the dragged window to the front.
2020    if (view_.get())
2021      view_->GetWidget()->StackAtTop();
2022    else if (is_dragging_window_)
2023      attached_tabstrip_->GetWidget()->StackAtTop();
2024  }
2025}
2026
2027TabStripModel* TabDragController::GetModel(
2028    TabStrip* tabstrip) const {
2029  return static_cast<BrowserTabStripController*>(tabstrip->controller())->
2030      model();
2031}
2032
2033views::Widget* TabDragController::GetAttachedBrowserWidget() {
2034  return attached_tabstrip_->GetWidget();
2035}
2036
2037bool TabDragController::AreTabsConsecutive() {
2038  for (size_t i = 1; i < drag_data_.size(); ++i) {
2039    if (drag_data_[i - 1].source_model_index + 1 !=
2040        drag_data_[i].source_model_index) {
2041      return false;
2042    }
2043  }
2044  return true;
2045}
2046
2047Browser* TabDragController::CreateBrowserForDrag(
2048    TabStrip* source,
2049    const gfx::Point& point_in_screen,
2050    gfx::Vector2d* drag_offset,
2051    std::vector<gfx::Rect>* drag_bounds) {
2052  gfx::Point center(0, source->height() / 2);
2053  views::View::ConvertPointToWidget(source, &center);
2054  gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds());
2055  new_bounds.set_y(point_in_screen.y() - center.y());
2056  switch (GetDetachPosition(point_in_screen)) {
2057    case DETACH_BEFORE:
2058      new_bounds.set_x(point_in_screen.x() - center.x());
2059      new_bounds.Offset(-mouse_offset_.x(), 0);
2060      break;
2061    case DETACH_AFTER: {
2062      gfx::Point right_edge(source->width(), 0);
2063      views::View::ConvertPointToWidget(source, &right_edge);
2064      new_bounds.set_x(point_in_screen.x() - right_edge.x());
2065      new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0);
2066      int delta = (*drag_bounds)[0].x();
2067      for (size_t i = 0; i < drag_bounds->size(); ++i)
2068        (*drag_bounds)[i].Offset(-delta, 0);
2069      break;
2070    }
2071    default:
2072      break; // Nothing to do for DETACH_ABOVE_OR_BELOW.
2073  }
2074
2075  *drag_offset = point_in_screen - new_bounds.origin();
2076
2077  Profile* profile =
2078      Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext());
2079  Browser::CreateParams create_params(Browser::TYPE_TABBED,
2080                                      profile,
2081                                      host_desktop_type_);
2082  create_params.initial_bounds = new_bounds;
2083  Browser* browser = new Browser(create_params);
2084  SetTrackedByWorkspace(browser->window()->GetNativeWindow(), false);
2085  SetWindowPositionManaged(browser->window()->GetNativeWindow(), false);
2086  // If the window is created maximized then the bounds we supplied are ignored.
2087  // We need to reset them again so they are honored.
2088  browser->window()->SetBounds(new_bounds);
2089
2090  return browser;
2091}
2092
2093gfx::Point TabDragController::GetCursorScreenPoint() {
2094#if defined(USE_ASH)
2095  if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH &&
2096      event_source_ == EVENT_SOURCE_TOUCH &&
2097      aura::Env::GetInstance()->is_touch_down()) {
2098    views::Widget* widget = GetAttachedBrowserWidget();
2099    DCHECK(widget);
2100    aura::Window* widget_window = widget->GetNativeWindow();
2101    DCHECK(widget_window->GetRootWindow());
2102    gfx::Point touch_point;
2103    bool got_touch_point = widget_window->GetRootWindow()->
2104        gesture_recognizer()->GetLastTouchPointForTarget(widget_window,
2105                                                         &touch_point);
2106    DCHECK(got_touch_point);
2107    ash::wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point);
2108    return touch_point;
2109  }
2110#endif
2111  return screen_->GetCursorScreenPoint();
2112}
2113
2114gfx::Vector2d TabDragController::GetWindowOffset(
2115    const gfx::Point& point_in_screen) {
2116  TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
2117      attached_tabstrip_ : source_tabstrip_;
2118  views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView();
2119
2120  gfx::Point point = point_in_screen;
2121  views::View::ConvertPointFromScreen(toplevel_view, &point);
2122  return point.OffsetFromOrigin();
2123}
2124