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/gtk/tabs/dragged_tab_controller_gtk.h"
6
7 #include <algorithm>
8
9 #include "base/callback.h"
10 #include "base/i18n/rtl.h"
11 #include "chrome/browser/platform_util.h"
12 #include "chrome/browser/tabs/tab_strip_model.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
15 #include "chrome/browser/ui/gtk/gtk_util.h"
16 #include "chrome/browser/ui/gtk/tabs/dragged_tab_gtk.h"
17 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
19 #include "content/browser/tab_contents/tab_contents.h"
20 #include "content/common/notification_source.h"
21
22 namespace {
23
24 // Delay, in ms, during dragging before we bring a window to front.
25 const int kBringToFrontDelay = 750;
26
27 // Used to determine how far a tab must obscure another tab in order to swap
28 // their indexes.
29 const int kHorizontalMoveThreshold = 16; // pixels
30
31 // How far a drag must pull a tab out of the tabstrip in order to detach it.
32 const int kVerticalDetachMagnetism = 15; // pixels
33
34 } // namespace
35
DraggedTabControllerGtk(TabGtk * source_tab,TabStripGtk * source_tabstrip)36 DraggedTabControllerGtk::DraggedTabControllerGtk(TabGtk* source_tab,
37 TabStripGtk* source_tabstrip)
38 : dragged_contents_(NULL),
39 original_delegate_(NULL),
40 source_tab_(source_tab),
41 source_tabstrip_(source_tabstrip),
42 source_model_index_(source_tabstrip->GetIndexOfTab(source_tab)),
43 attached_tabstrip_(source_tabstrip),
44 in_destructor_(false),
45 last_move_screen_x_(0),
46 mini_(source_tabstrip->model()->IsMiniTab(source_model_index_)),
47 pinned_(source_tabstrip->model()->IsTabPinned(source_model_index_)) {
48 SetDraggedContents(
49 source_tabstrip_->model()->GetTabContentsAt(source_model_index_));
50 }
51
~DraggedTabControllerGtk()52 DraggedTabControllerGtk::~DraggedTabControllerGtk() {
53 in_destructor_ = true;
54 CleanUpSourceTab();
55 // Need to delete the dragged tab here manually _before_ we reset the dragged
56 // contents to NULL, otherwise if the view is animating to its destination
57 // bounds, it won't be able to clean up properly since its cleanup routine
58 // uses GetIndexForDraggedContents, which will be invalid.
59 dragged_tab_.reset();
60 SetDraggedContents(NULL);
61 }
62
CaptureDragInfo(const gfx::Point & mouse_offset)63 void DraggedTabControllerGtk::CaptureDragInfo(const gfx::Point& mouse_offset) {
64 start_screen_point_ = GetCursorScreenPoint();
65 mouse_offset_ = mouse_offset;
66 }
67
Drag()68 void DraggedTabControllerGtk::Drag() {
69 if (!source_tab_ || !dragged_contents_)
70 return;
71
72 bring_to_front_timer_.Stop();
73
74 EnsureDraggedTab();
75
76 // Before we get to dragging anywhere, ensure that we consider ourselves
77 // attached to the source tabstrip.
78 if (source_tab_->IsVisible()) {
79 Attach(source_tabstrip_, gfx::Point());
80 }
81
82 if (!source_tab_->IsVisible()) {
83 // TODO(jhawkins): Save focus.
84 ContinueDragging();
85 }
86 }
87
EndDrag(bool canceled)88 bool DraggedTabControllerGtk::EndDrag(bool canceled) {
89 return EndDragImpl(canceled ? CANCELED : NORMAL);
90 }
91
GetDragSourceTabForContents(TabContents * contents) const92 TabGtk* DraggedTabControllerGtk::GetDragSourceTabForContents(
93 TabContents* contents) const {
94 if (attached_tabstrip_ == source_tabstrip_)
95 return contents == dragged_contents_->tab_contents() ? source_tab_ : NULL;
96 return NULL;
97 }
98
IsDragSourceTab(const TabGtk * tab) const99 bool DraggedTabControllerGtk::IsDragSourceTab(const TabGtk* tab) const {
100 return source_tab_ == tab;
101 }
102
IsTabDetached(const TabGtk * tab) const103 bool DraggedTabControllerGtk::IsTabDetached(const TabGtk* tab) const {
104 if (!IsDragSourceTab(tab))
105 return false;
106 return (attached_tabstrip_ == NULL);
107 }
108
109 ////////////////////////////////////////////////////////////////////////////////
110 // DraggedTabControllerGtk, TabContentsDelegate implementation:
111
OpenURLFromTab(TabContents * source,const GURL & url,const GURL & referrer,WindowOpenDisposition disposition,PageTransition::Type transition)112 void DraggedTabControllerGtk::OpenURLFromTab(TabContents* source,
113 const GURL& url,
114 const GURL& referrer,
115 WindowOpenDisposition disposition,
116 PageTransition::Type transition) {
117 if (original_delegate_) {
118 if (disposition == CURRENT_TAB)
119 disposition = NEW_WINDOW;
120
121 original_delegate_->OpenURLFromTab(source, url, referrer,
122 disposition, transition);
123 }
124 }
125
NavigationStateChanged(const TabContents * source,unsigned changed_flags)126 void DraggedTabControllerGtk::NavigationStateChanged(const TabContents* source,
127 unsigned changed_flags) {
128 if (dragged_tab_.get())
129 dragged_tab_->Update();
130 }
131
AddNewContents(TabContents * source,TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)132 void DraggedTabControllerGtk::AddNewContents(TabContents* source,
133 TabContents* new_contents,
134 WindowOpenDisposition disposition,
135 const gfx::Rect& initial_pos,
136 bool user_gesture) {
137 DCHECK(disposition != CURRENT_TAB);
138
139 // Theoretically could be called while dragging if the page tries to
140 // spawn a window. Route this message back to the browser in most cases.
141 if (original_delegate_) {
142 original_delegate_->AddNewContents(source, new_contents, disposition,
143 initial_pos, user_gesture);
144 }
145 }
146
ActivateContents(TabContents * contents)147 void DraggedTabControllerGtk::ActivateContents(TabContents* contents) {
148 // Ignored.
149 }
150
DeactivateContents(TabContents * contents)151 void DraggedTabControllerGtk::DeactivateContents(TabContents* contents) {
152 // Ignored.
153 }
154
LoadingStateChanged(TabContents * source)155 void DraggedTabControllerGtk::LoadingStateChanged(TabContents* source) {
156 // TODO(jhawkins): It would be nice to respond to this message by changing the
157 // screen shot in the dragged tab.
158 if (dragged_tab_.get())
159 dragged_tab_->Update();
160 }
161
CloseContents(TabContents * source)162 void DraggedTabControllerGtk::CloseContents(TabContents* source) {
163 // Theoretically could be called by a window. Should be ignored
164 // because window.close() is ignored (usually, even though this
165 // method gets called.)
166 }
167
MoveContents(TabContents * source,const gfx::Rect & pos)168 void DraggedTabControllerGtk::MoveContents(TabContents* source,
169 const gfx::Rect& pos) {
170 // Theoretically could be called by a web page trying to move its
171 // own window. Should be ignored since we're moving the window...
172 }
173
IsPopup(const TabContents * source) const174 bool DraggedTabControllerGtk::IsPopup(const TabContents* source) const {
175 return false;
176 }
177
UpdateTargetURL(TabContents * source,const GURL & url)178 void DraggedTabControllerGtk::UpdateTargetURL(TabContents* source,
179 const GURL& url) {
180 // Ignored.
181 }
182
183 ////////////////////////////////////////////////////////////////////////////////
184 // DraggedTabControllerGtk, NotificationObserver implementation:
185
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)186 void DraggedTabControllerGtk::Observe(NotificationType type,
187 const NotificationSource& source,
188 const NotificationDetails& details) {
189 DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
190 DCHECK(Source<TabContentsWrapper>(source).ptr() == dragged_contents_);
191 EndDragImpl(TAB_DESTROYED);
192 }
193
InitWindowCreatePoint()194 void DraggedTabControllerGtk::InitWindowCreatePoint() {
195 window_create_point_.SetPoint(mouse_offset_.x(), mouse_offset_.y());
196 }
197
GetWindowCreatePoint() const198 gfx::Point DraggedTabControllerGtk::GetWindowCreatePoint() const {
199 gfx::Point cursor_point = GetCursorScreenPoint();
200 return gfx::Point(cursor_point.x() - window_create_point_.x(),
201 cursor_point.y() - window_create_point_.y());
202 }
203
SetDraggedContents(TabContentsWrapper * new_contents)204 void DraggedTabControllerGtk::SetDraggedContents(
205 TabContentsWrapper* new_contents) {
206 if (dragged_contents_) {
207 registrar_.Remove(this,
208 NotificationType::TAB_CONTENTS_DESTROYED,
209 Source<TabContentsWrapper>(dragged_contents_));
210 if (original_delegate_)
211 dragged_contents_->tab_contents()->set_delegate(original_delegate_);
212 }
213 original_delegate_ = NULL;
214 dragged_contents_ = new_contents;
215 if (dragged_contents_) {
216 registrar_.Add(this,
217 NotificationType::TAB_CONTENTS_DESTROYED,
218 Source<TabContentsWrapper>(dragged_contents_));
219
220 // We need to be the delegate so we receive messages about stuff,
221 // otherwise our dragged_contents() may be replaced and subsequently
222 // collected/destroyed while the drag is in process, leading to
223 // nasty crashes.
224 original_delegate_ = dragged_contents_->tab_contents()->delegate();
225 dragged_contents_->tab_contents()->set_delegate(this);
226 }
227 }
228
ContinueDragging()229 void DraggedTabControllerGtk::ContinueDragging() {
230 // TODO(jhawkins): We don't handle the situation where the last tab is dragged
231 // out of a window, so we'll just go with the way Windows handles dragging for
232 // now.
233 gfx::Point screen_point = GetCursorScreenPoint();
234
235 // Determine whether or not we have dragged over a compatible TabStrip in
236 // another browser window. If we have, we should attach to it and start
237 // dragging within it.
238 #if defined(OS_CHROMEOS)
239 // We don't allow detaching on chrome os.
240 TabStripGtk* target_tabstrip = source_tabstrip_;
241 #else
242 TabStripGtk* target_tabstrip = GetTabStripForPoint(screen_point);
243 #endif
244 if (target_tabstrip != attached_tabstrip_) {
245 // Make sure we're fully detached from whatever TabStrip we're attached to
246 // (if any).
247 if (attached_tabstrip_)
248 Detach();
249
250 if (target_tabstrip)
251 Attach(target_tabstrip, screen_point);
252 }
253
254 if (!target_tabstrip) {
255 bring_to_front_timer_.Start(
256 base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this,
257 &DraggedTabControllerGtk::BringWindowUnderMouseToFront);
258 }
259
260 MoveTab(screen_point);
261 }
262
MoveTab(const gfx::Point & screen_point)263 void DraggedTabControllerGtk::MoveTab(const gfx::Point& screen_point) {
264 gfx::Point dragged_tab_point = GetDraggedTabPoint(screen_point);
265
266 if (attached_tabstrip_) {
267 TabStripModel* attached_model = attached_tabstrip_->model();
268 int from_index = attached_model->GetIndexOfTabContents(dragged_contents_);
269
270 // Determine the horizontal move threshold. This is dependent on the width
271 // of tabs. The smaller the tabs compared to the standard size, the smaller
272 // the threshold.
273 double unselected, selected;
274 attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
275 double ratio = unselected / TabGtk::GetStandardSize().width();
276 int threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
277
278 // Update the model, moving the TabContents from one index to another. Do
279 // this only if we have moved a minimum distance since the last reorder (to
280 // prevent jitter).
281 if (abs(screen_point.x() - last_move_screen_x_) > threshold) {
282 gfx::Rect bounds = GetDraggedTabTabStripBounds(dragged_tab_point);
283 int to_index = GetInsertionIndexForDraggedBounds(bounds, true);
284 to_index = NormalizeIndexToAttachedTabStrip(to_index);
285 if (from_index != to_index) {
286 last_move_screen_x_ = screen_point.x();
287 attached_model->MoveTabContentsAt(from_index, to_index, true);
288 }
289 }
290 }
291
292 // Move the dragged tab. There are no changes to the model if we're detached.
293 dragged_tab_->MoveTo(dragged_tab_point);
294 }
295
GetTabStripForPoint(const gfx::Point & screen_point)296 TabStripGtk* DraggedTabControllerGtk::GetTabStripForPoint(
297 const gfx::Point& screen_point) {
298 GtkWidget* dragged_window = dragged_tab_->widget();
299 dock_windows_.insert(dragged_window);
300 gfx::NativeWindow local_window =
301 DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_);
302 dock_windows_.erase(dragged_window);
303 if (!local_window)
304 return NULL;
305
306 BrowserWindowGtk* browser =
307 BrowserWindowGtk::GetBrowserWindowForNativeWindow(local_window);
308 if (!browser)
309 return NULL;
310
311 TabStripGtk* other_tabstrip = browser->tabstrip();
312 if (!other_tabstrip->IsCompatibleWith(source_tabstrip_))
313 return NULL;
314
315 return GetTabStripIfItContains(other_tabstrip, screen_point);
316 }
317
GetTabStripIfItContains(TabStripGtk * tabstrip,const gfx::Point & screen_point) const318 TabStripGtk* DraggedTabControllerGtk::GetTabStripIfItContains(
319 TabStripGtk* tabstrip, const gfx::Point& screen_point) const {
320 // Make sure the specified screen point is actually within the bounds of the
321 // specified tabstrip...
322 gfx::Rect tabstrip_bounds =
323 gtk_util::GetWidgetScreenBounds(tabstrip->tabstrip_.get());
324 if (screen_point.x() < tabstrip_bounds.right() &&
325 screen_point.x() >= tabstrip_bounds.x()) {
326 // TODO(beng): make this be relative to the start position of the mouse for
327 // the source TabStrip.
328 int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism;
329 int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism;
330 if (screen_point.y() >= lower_threshold &&
331 screen_point.y() <= upper_threshold) {
332 return tabstrip;
333 }
334 }
335
336 return NULL;
337 }
338
Attach(TabStripGtk * attached_tabstrip,const gfx::Point & screen_point)339 void DraggedTabControllerGtk::Attach(TabStripGtk* attached_tabstrip,
340 const gfx::Point& screen_point) {
341 attached_tabstrip_ = attached_tabstrip;
342 InitWindowCreatePoint();
343 attached_tabstrip_->GenerateIdealBounds();
344
345 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
346
347 // Update the tab first, so we can ask it for its bounds and determine
348 // where to insert the hidden tab.
349
350 // If this is the first time Attach is called for this drag, we're attaching
351 // to the source tabstrip, and we should assume the tab count already
352 // includes this tab since we haven't been detached yet. If we don't do this,
353 // the dragged representation will be a different size to others in the
354 // tabstrip.
355 int tab_count = attached_tabstrip_->GetTabCount();
356 int mini_tab_count = attached_tabstrip_->GetMiniTabCount();
357 if (!tab)
358 ++tab_count;
359 double unselected_width = 0, selected_width = 0;
360 attached_tabstrip_->GetDesiredTabWidths(tab_count, mini_tab_count,
361 &unselected_width, &selected_width);
362 int dragged_tab_width =
363 mini_ ? TabGtk::GetMiniWidth() : static_cast<int>(selected_width);
364 dragged_tab_->Attach(dragged_tab_width);
365
366 if (!tab) {
367 // There is no tab in |attached_tabstrip| that corresponds to the dragged
368 // TabContents. We must now create one.
369
370 // Remove ourselves as the delegate now that the dragged TabContents is
371 // being inserted back into a Browser.
372 dragged_contents_->tab_contents()->set_delegate(NULL);
373 original_delegate_ = NULL;
374
375 // Return the TabContents' to normalcy.
376 dragged_contents_->tab_contents()->set_capturing_contents(false);
377
378 // We need to ask the tabstrip we're attached to ensure that the ideal
379 // bounds for all its tabs are correctly generated, because the calculation
380 // in GetInsertionIndexForDraggedBounds needs them to be to figure out the
381 // appropriate insertion index.
382 attached_tabstrip_->GenerateIdealBounds();
383
384 // Inserting counts as a move. We don't want the tabs to jitter when the
385 // user moves the tab immediately after attaching it.
386 last_move_screen_x_ = screen_point.x();
387
388 // Figure out where to insert the tab based on the bounds of the dragged
389 // representation and the ideal bounds of the other tabs already in the
390 // strip. ("ideal bounds" are stable even if the tabs' actual bounds are
391 // changing due to animation).
392 gfx::Rect bounds = GetDraggedTabTabStripBounds(screen_point);
393 int index = GetInsertionIndexForDraggedBounds(bounds, false);
394 attached_tabstrip_->model()->InsertTabContentsAt(
395 index, dragged_contents_,
396 TabStripModel::ADD_ACTIVE |
397 (pinned_ ? TabStripModel::ADD_PINNED : 0));
398
399 tab = GetTabMatchingDraggedContents(attached_tabstrip_);
400 }
401 DCHECK(tab); // We should now have a tab.
402 tab->SetVisible(false);
403 tab->set_dragging(true);
404
405 // TODO(jhawkins): Move the corresponding window to the front.
406 }
407
Detach()408 void DraggedTabControllerGtk::Detach() {
409 // Update the Model.
410 TabStripModel* attached_model = attached_tabstrip_->model();
411 int index = attached_model->GetIndexOfTabContents(dragged_contents_);
412 if (index >= 0 && index < attached_model->count()) {
413 // Sometimes, DetachTabContentsAt has consequences that result in
414 // attached_tabstrip_ being set to NULL, so we need to save it first.
415 TabStripGtk* attached_tabstrip = attached_tabstrip_;
416 attached_model->DetachTabContentsAt(index);
417 attached_tabstrip->SchedulePaint();
418 }
419
420 // If we've removed the last tab from the tabstrip, hide the frame now.
421 if (attached_model->empty())
422 HideWindow();
423
424 // Update the dragged tab. This NULL check is necessary apparently in some
425 // conditions during automation where the view_ is destroyed inside a
426 // function call preceding this point but after it is created.
427 if (dragged_tab_.get()) {
428 dragged_tab_->Detach();
429 }
430
431 // Detaching resets the delegate, but we still want to be the delegate.
432 dragged_contents_->tab_contents()->set_delegate(this);
433
434 attached_tabstrip_ = NULL;
435 }
436
ConvertScreenPointToTabStripPoint(TabStripGtk * tabstrip,const gfx::Point & screen_point)437 gfx::Point DraggedTabControllerGtk::ConvertScreenPointToTabStripPoint(
438 TabStripGtk* tabstrip, const gfx::Point& screen_point) {
439 gfx::Point tabstrip_screen_point =
440 gtk_util::GetWidgetScreenPosition(tabstrip->tabstrip_.get());
441 return screen_point.Subtract(tabstrip_screen_point);
442 }
443
GetDraggedTabTabStripBounds(const gfx::Point & screen_point)444 gfx::Rect DraggedTabControllerGtk::GetDraggedTabTabStripBounds(
445 const gfx::Point& screen_point) {
446 gfx::Point client_point =
447 ConvertScreenPointToTabStripPoint(attached_tabstrip_, screen_point);
448 gfx::Size tab_size = dragged_tab_->attached_tab_size();
449 return gfx::Rect(client_point.x(), client_point.y(),
450 tab_size.width(), tab_size.height());
451 }
452
GetInsertionIndexForDraggedBounds(const gfx::Rect & dragged_bounds,bool is_tab_attached) const453 int DraggedTabControllerGtk::GetInsertionIndexForDraggedBounds(
454 const gfx::Rect& dragged_bounds,
455 bool is_tab_attached) const {
456 int right_tab_x = 0;
457 int dragged_bounds_x = base::i18n::IsRTL() ? dragged_bounds.right() :
458 dragged_bounds.x();
459 dragged_bounds_x =
460 gtk_util::MirroredXCoordinate(attached_tabstrip_->widget(),
461 dragged_bounds_x);
462
463 // Divides each tab into two halves to see if the dragged tab has crossed
464 // the halfway boundary necessary to move past the next tab.
465 int index = -1;
466 for (int i = 0; i < attached_tabstrip_->GetTabCount(); i++) {
467 gfx::Rect ideal_bounds = attached_tabstrip_->GetIdealBounds(i);
468
469 gfx::Rect left_half = ideal_bounds;
470 left_half.set_width(left_half.width() / 2);
471
472 gfx::Rect right_half = ideal_bounds;
473 right_half.set_width(ideal_bounds.width() - left_half.width());
474 right_half.set_x(left_half.right());
475
476 right_tab_x = right_half.right();
477
478 if (dragged_bounds_x >= right_half.x() &&
479 dragged_bounds_x < right_half.right()) {
480 index = i + 1;
481 break;
482 } else if (dragged_bounds_x >= left_half.x() &&
483 dragged_bounds_x < left_half.right()) {
484 index = i;
485 break;
486 }
487 }
488
489 if (index == -1) {
490 bool at_the_end = base::i18n::IsRTL() ?
491 dragged_bounds.x() < right_tab_x :
492 dragged_bounds.right() > right_tab_x;
493 index = at_the_end ? attached_tabstrip_->model()->count() : 0;
494 }
495
496 index = attached_tabstrip_->model()->ConstrainInsertionIndex(index, mini_);
497 if (is_tab_attached && mini_ &&
498 index == attached_tabstrip_->model()->IndexOfFirstNonMiniTab()) {
499 index--;
500 }
501
502 return index;
503 }
504
GetDraggedTabPoint(const gfx::Point & screen_point)505 gfx::Point DraggedTabControllerGtk::GetDraggedTabPoint(
506 const gfx::Point& screen_point) {
507 int x = screen_point.x() - mouse_offset_.x();
508 int y = screen_point.y() - mouse_offset_.y();
509
510 // If we're not attached, we just use x and y from above.
511 if (attached_tabstrip_) {
512 gfx::Rect tabstrip_bounds =
513 gtk_util::GetWidgetScreenBounds(attached_tabstrip_->tabstrip_.get());
514 // Snap the dragged tab to the tabstrip if we are attached, detaching
515 // only when the mouse position (screen_point) exceeds the screen bounds
516 // of the tabstrip.
517 if (x < tabstrip_bounds.x() && screen_point.x() >= tabstrip_bounds.x())
518 x = tabstrip_bounds.x();
519
520 gfx::Size tab_size = dragged_tab_->attached_tab_size();
521 int vertical_drag_magnetism = tab_size.height() * 2;
522 int vertical_detach_point = tabstrip_bounds.y() - vertical_drag_magnetism;
523 if (y < tabstrip_bounds.y() && screen_point.y() >= vertical_detach_point)
524 y = tabstrip_bounds.y();
525
526 // Make sure the tab can't be dragged off the right side of the tabstrip
527 // unless the mouse pointer passes outside the bounds of the strip by
528 // clamping the position of the dragged window to the tabstrip width less
529 // the width of one tab until the mouse pointer (screen_point) exceeds the
530 // screen bounds of the tabstrip.
531 int max_x = tabstrip_bounds.right() - tab_size.width();
532 int max_y = tabstrip_bounds.bottom() - tab_size.height();
533 if (x > max_x && screen_point.x() <= tabstrip_bounds.right())
534 x = max_x;
535 if (y > max_y && screen_point.y() <=
536 (tabstrip_bounds.bottom() + vertical_drag_magnetism)) {
537 y = max_y;
538 }
539 #if defined(OS_CHROMEOS)
540 // We don't allow detaching on chromeos. This restricts dragging to the
541 // source window.
542 x = std::min(std::max(x, tabstrip_bounds.x()), max_x);
543 y = tabstrip_bounds.y();
544 #endif
545 }
546 return gfx::Point(x, y);
547 }
548
NormalizeIndexToAttachedTabStrip(int index) const549 int DraggedTabControllerGtk::NormalizeIndexToAttachedTabStrip(int index) const {
550 if (index >= attached_tabstrip_->model_->count())
551 return attached_tabstrip_->model_->count() - 1;
552 if (index == TabStripModel::kNoTab)
553 return 0;
554 return index;
555 }
556
GetTabMatchingDraggedContents(TabStripGtk * tabstrip) const557 TabGtk* DraggedTabControllerGtk::GetTabMatchingDraggedContents(
558 TabStripGtk* tabstrip) const {
559 int index = tabstrip->model()->GetIndexOfTabContents(dragged_contents_);
560 return index == TabStripModel::kNoTab ? NULL : tabstrip->GetTabAt(index);
561 }
562
EndDragImpl(EndDragType type)563 bool DraggedTabControllerGtk::EndDragImpl(EndDragType type) {
564 bring_to_front_timer_.Stop();
565
566 // WARNING: this may be invoked multiple times. In particular, if deletion
567 // occurs after a delay (as it does when the tab is released in the original
568 // tab strip) and the navigation controller/tab contents is deleted before
569 // the animation finishes, this is invoked twice. The second time through
570 // type == TAB_DESTROYED.
571
572 bool destroy_now = true;
573 if (type == TAB_DESTROYED) {
574 // If we get here it means the NavigationController is going down. Don't
575 // attempt to do any cleanup other than resetting the delegate (if we're
576 // still the delegate).
577 if (dragged_contents_ &&
578 dragged_contents_->tab_contents()->delegate() == this)
579 dragged_contents_->tab_contents()->set_delegate(NULL);
580 dragged_contents_ = NULL;
581 } else {
582 // If we never received a drag-motion event, the drag will never have
583 // started in the sense that |dragged_tab_| will be NULL. We don't need to
584 // revert or complete the drag in that case.
585 if (dragged_tab_.get()) {
586 if (type == CANCELED) {
587 RevertDrag();
588 } else {
589 destroy_now = CompleteDrag();
590 }
591 }
592
593 if (dragged_contents_ &&
594 dragged_contents_->tab_contents()->delegate() == this)
595 dragged_contents_->tab_contents()->set_delegate(original_delegate_);
596 }
597
598 // The delegate of the dragged contents should have been reset. Unset the
599 // original delegate so that we don't attempt to reset the delegate when
600 // deleted.
601 DCHECK(!dragged_contents_ ||
602 dragged_contents_->tab_contents()->delegate() != this);
603 original_delegate_ = NULL;
604
605 // If we're not destroyed now, we'll be destroyed asynchronously later.
606 if (destroy_now)
607 source_tabstrip_->DestroyDragController();
608
609 return destroy_now;
610 }
611
RevertDrag()612 void DraggedTabControllerGtk::RevertDrag() {
613 // We save this here because code below will modify |attached_tabstrip_|.
614 bool restore_window = attached_tabstrip_ != source_tabstrip_;
615 if (attached_tabstrip_) {
616 int index = attached_tabstrip_->model()->GetIndexOfTabContents(
617 dragged_contents_);
618 if (attached_tabstrip_ != source_tabstrip_) {
619 // The tab was inserted into another tabstrip. We need to put it back
620 // into the original one.
621 attached_tabstrip_->model()->DetachTabContentsAt(index);
622 // TODO(beng): (Cleanup) seems like we should use Attach() for this
623 // somehow.
624 attached_tabstrip_ = source_tabstrip_;
625 source_tabstrip_->model()->InsertTabContentsAt(
626 source_model_index_, dragged_contents_,
627 TabStripModel::ADD_ACTIVE |
628 (pinned_ ? TabStripModel::ADD_PINNED : 0));
629 } else {
630 // The tab was moved within the tabstrip where the drag was initiated.
631 // Move it back to the starting location.
632 source_tabstrip_->model()->MoveTabContentsAt(index, source_model_index_,
633 true);
634 }
635 } else {
636 // TODO(beng): (Cleanup) seems like we should use Attach() for this
637 // somehow.
638 attached_tabstrip_ = source_tabstrip_;
639 // The tab was detached from the tabstrip where the drag began, and has not
640 // been attached to any other tabstrip. We need to put it back into the
641 // source tabstrip.
642 source_tabstrip_->model()->InsertTabContentsAt(
643 source_model_index_, dragged_contents_,
644 TabStripModel::ADD_ACTIVE |
645 (pinned_ ? TabStripModel::ADD_PINNED : 0));
646 }
647
648 // If we're not attached to any tab strip, or attached to some other tab
649 // strip, we need to restore the bounds of the original tab strip's frame, in
650 // case it has been hidden.
651 if (restore_window)
652 ShowWindow();
653
654 source_tab_->SetVisible(true);
655 source_tab_->set_dragging(false);
656 }
657
CompleteDrag()658 bool DraggedTabControllerGtk::CompleteDrag() {
659 bool destroy_immediately = true;
660 if (attached_tabstrip_) {
661 // We don't need to do anything other than make the tab visible again,
662 // since the dragged tab is going away.
663 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
664 gfx::Rect rect = GetTabScreenBounds(tab);
665 dragged_tab_->AnimateToBounds(GetTabScreenBounds(tab),
666 NewCallback(this, &DraggedTabControllerGtk::OnAnimateToBoundsComplete));
667 destroy_immediately = false;
668 } else {
669 // Compel the model to construct a new window for the detached TabContents.
670 BrowserWindowGtk* window = source_tabstrip_->window();
671 gfx::Rect window_bounds = window->GetRestoredBounds();
672 window_bounds.set_origin(GetWindowCreatePoint());
673 Browser* new_browser =
674 source_tabstrip_->model()->delegate()->CreateNewStripWithContents(
675 dragged_contents_, window_bounds, dock_info_, window->IsMaximized());
676 TabStripModel* new_model = new_browser->tabstrip_model();
677 new_model->SetTabPinned(new_model->GetIndexOfTabContents(dragged_contents_),
678 pinned_);
679 new_browser->window()->Show();
680 CleanUpHiddenFrame();
681 }
682
683 return destroy_immediately;
684 }
685
EnsureDraggedTab()686 void DraggedTabControllerGtk::EnsureDraggedTab() {
687 if (!dragged_tab_.get()) {
688 gfx::Rect rect;
689 dragged_contents_->tab_contents()->GetContainerBounds(&rect);
690
691 dragged_tab_.reset(new DraggedTabGtk(dragged_contents_->tab_contents(),
692 mouse_offset_, rect.size(), mini_));
693 }
694 }
695
GetCursorScreenPoint() const696 gfx::Point DraggedTabControllerGtk::GetCursorScreenPoint() const {
697 // Get default display and screen.
698 GdkDisplay* display = gdk_display_get_default();
699
700 // Get cursor position.
701 int x, y;
702 gdk_display_get_pointer(display, NULL, &x, &y, NULL);
703
704 return gfx::Point(x, y);
705 }
706
707 // static
GetTabScreenBounds(TabGtk * tab)708 gfx::Rect DraggedTabControllerGtk::GetTabScreenBounds(TabGtk* tab) {
709 // A hidden widget moved with gtk_fixed_move in a GtkFixed container doesn't
710 // update its allocation until after the widget is shown, so we have to use
711 // the tab bounds we keep track of.
712 //
713 // We use the requested bounds instead of the allocation because the
714 // allocation is relative to the first windowed widget ancestor of the tab.
715 // Because of this, we can't use the tabs allocation to get the screen bounds.
716 gfx::Rect bounds = tab->GetRequisition();
717 GtkWidget* widget = tab->widget();
718 GtkWidget* parent = gtk_widget_get_parent(widget);
719 gfx::Point point = gtk_util::GetWidgetScreenPosition(parent);
720 bounds.Offset(point);
721
722 return gfx::Rect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
723 }
724
HideWindow()725 void DraggedTabControllerGtk::HideWindow() {
726 GtkWidget* tabstrip = source_tabstrip_->widget();
727 GtkWindow* window = platform_util::GetTopLevel(tabstrip);
728 gtk_widget_hide(GTK_WIDGET(window));
729 }
730
ShowWindow()731 void DraggedTabControllerGtk::ShowWindow() {
732 GtkWidget* tabstrip = source_tabstrip_->widget();
733 GtkWindow* window = platform_util::GetTopLevel(tabstrip);
734 gtk_window_present(window);
735 }
736
CleanUpHiddenFrame()737 void DraggedTabControllerGtk::CleanUpHiddenFrame() {
738 // If the model we started dragging from is now empty, we must ask the
739 // delegate to close the frame.
740 if (source_tabstrip_->model()->empty())
741 source_tabstrip_->model()->delegate()->CloseFrameAfterDragSession();
742 }
743
CleanUpSourceTab()744 void DraggedTabControllerGtk::CleanUpSourceTab() {
745 // If we were attached to the source tabstrip, source tab will be in use
746 // as the tab. If we were detached or attached to another tabstrip, we can
747 // safely remove this item and delete it now.
748 if (attached_tabstrip_ != source_tabstrip_) {
749 source_tabstrip_->DestroyDraggedSourceTab(source_tab_);
750 source_tab_ = NULL;
751 }
752 }
753
OnAnimateToBoundsComplete()754 void DraggedTabControllerGtk::OnAnimateToBoundsComplete() {
755 // Sometimes, for some reason, in automation we can be called back on a
756 // detach even though we aren't attached to a tabstrip. Guard against that.
757 if (attached_tabstrip_) {
758 TabGtk* tab = GetTabMatchingDraggedContents(attached_tabstrip_);
759 if (tab) {
760 tab->SetVisible(true);
761 tab->set_dragging(false);
762 // Paint the tab now, otherwise there may be slight flicker between the
763 // time the dragged tab window is destroyed and we paint.
764 tab->SchedulePaint();
765 }
766 }
767
768 CleanUpHiddenFrame();
769
770 if (!in_destructor_)
771 source_tabstrip_->DestroyDragController();
772 }
773
BringWindowUnderMouseToFront()774 void DraggedTabControllerGtk::BringWindowUnderMouseToFront() {
775 // If we're going to dock to another window, bring it to the front.
776 gfx::NativeWindow window = dock_info_.window();
777 if (!window) {
778 gfx::NativeView dragged_tab = dragged_tab_->widget();
779 dock_windows_.insert(dragged_tab);
780 window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(),
781 dock_windows_);
782 dock_windows_.erase(dragged_tab);
783 }
784
785 if (window)
786 gtk_window_present(GTK_WINDOW(window));
787 }
788