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