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