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