• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/gtk/panels/panel_drag_gtk.h"
6 
7 #include <gdk/gdkkeysyms.h>
8 
9 #include "chrome/browser/ui/panels/panel.h"
10 #include "chrome/browser/ui/panels/panel_constants.h"
11 #include "chrome/browser/ui/panels/panel_manager.h"
12 #include "ui/gfx/gtk_util.h"
13 
14 namespace {
15 
GdkWindowEdgeToResizingSide(GdkWindowEdge edge)16 panel::ResizingSides GdkWindowEdgeToResizingSide(GdkWindowEdge edge) {
17   switch (edge) {
18   case GDK_WINDOW_EDGE_NORTH_WEST:
19     return panel::RESIZE_TOP_LEFT;
20   case GDK_WINDOW_EDGE_NORTH:
21     return panel::RESIZE_TOP;
22   case GDK_WINDOW_EDGE_NORTH_EAST:
23     return panel::RESIZE_TOP_RIGHT;
24   case GDK_WINDOW_EDGE_WEST:
25     return panel::RESIZE_LEFT;
26   case GDK_WINDOW_EDGE_EAST:
27     return panel::RESIZE_RIGHT;
28   case GDK_WINDOW_EDGE_SOUTH_WEST:
29     return panel::RESIZE_BOTTOM_LEFT;
30   case GDK_WINDOW_EDGE_SOUTH:
31     return panel::RESIZE_BOTTOM;
32   case GDK_WINDOW_EDGE_SOUTH_EAST:
33     return panel::RESIZE_BOTTOM_RIGHT;
34   default:
35     return panel::RESIZE_NONE;
36   }
37 }
38 
39 }  // namespace
40 
41 // Virtual base class to abstract move vs resize drag logic.
42 class PanelDragDelegate {
43  public:
PanelDragDelegate(Panel * panel)44   explicit PanelDragDelegate(Panel* panel) : panel_(panel) {}
~PanelDragDelegate()45   virtual ~PanelDragDelegate() {}
46 
panel() const47   Panel* panel() const { return panel_; }
panel_manager() const48   PanelManager* panel_manager() const { return panel_->manager(); }
49 
50   // |point| is the mouse location in screen coordinates.
51   virtual void DragStarted(gfx::Point point) = 0;
52   virtual void Dragged(gfx::Point point) = 0;
53 
54   // |canceled| is true to abort the drag.
55   virtual void DragEnded(bool canceled) = 0;
56 
57  private:
58   Panel* panel_;  // Weak pointer to the panel being dragged.
59 
60   DISALLOW_COPY_AND_ASSIGN(PanelDragDelegate);
61 };
62 
63 // Delegate for moving a panel by dragging the mouse.
64 class MoveDragDelegate : public PanelDragDelegate {
65  public:
MoveDragDelegate(Panel * panel)66   explicit MoveDragDelegate(Panel* panel)
67       : PanelDragDelegate(panel) {}
~MoveDragDelegate()68   virtual ~MoveDragDelegate() {}
69 
DragStarted(gfx::Point point)70   virtual void DragStarted(gfx::Point point) OVERRIDE {
71     panel_manager()->StartDragging(panel(), point);
72   }
Dragged(gfx::Point point)73   virtual void Dragged(gfx::Point point) OVERRIDE {
74     panel_manager()->Drag(point);
75   }
DragEnded(bool canceled)76   virtual void DragEnded(bool canceled) OVERRIDE {
77     panel_manager()->EndDragging(canceled);
78   }
79 
80   DISALLOW_COPY_AND_ASSIGN(MoveDragDelegate);
81 };
82 
83 // Delegate for resizing a panel by dragging the mouse.
84 class ResizeDragDelegate : public PanelDragDelegate {
85  public:
ResizeDragDelegate(Panel * panel,GdkWindowEdge edge)86   ResizeDragDelegate(Panel* panel, GdkWindowEdge edge)
87       : PanelDragDelegate(panel),
88         resizing_side_(GdkWindowEdgeToResizingSide(edge)) {}
~ResizeDragDelegate()89   virtual ~ResizeDragDelegate() {}
90 
DragStarted(gfx::Point point)91   virtual void DragStarted(gfx::Point point) OVERRIDE {
92     panel_manager()->StartResizingByMouse(panel(), point, resizing_side_);
93   }
Dragged(gfx::Point point)94   virtual void Dragged(gfx::Point point) OVERRIDE {
95     panel_manager()->ResizeByMouse(point);
96   }
DragEnded(bool canceled)97   virtual void DragEnded(bool canceled) OVERRIDE {
98     panel_manager()->EndResizingByMouse(canceled);
99   }
100  private:
101   // The edge from which the panel is being resized.
102   panel::ResizingSides resizing_side_;
103 
104   DISALLOW_COPY_AND_ASSIGN(ResizeDragDelegate);
105 };
106 
107 // Panel drag helper for processing mouse and keyboard events while
108 // the left mouse button is pressed.
PanelDragGtk(Panel * panel)109 PanelDragGtk::PanelDragGtk(Panel* panel)
110     : panel_(panel),
111       drag_state_(NOT_DRAGGING),
112       initial_mouse_down_(NULL),
113       click_handler_(NULL),
114       drag_delegate_(NULL) {
115   // Create an invisible event box to receive mouse and key events.
116   drag_widget_ = gtk_event_box_new();
117   gtk_event_box_set_visible_window(GTK_EVENT_BOX(drag_widget_), FALSE);
118 
119   // Connect signals for events during a drag.
120   g_signal_connect(drag_widget_, "motion-notify-event",
121                    G_CALLBACK(OnMouseMoveEventThunk), this);
122   g_signal_connect(drag_widget_, "key-press-event",
123                    G_CALLBACK(OnKeyPressEventThunk), this);
124   g_signal_connect(drag_widget_, "key-release-event",
125                    G_CALLBACK(OnKeyReleaseEventThunk), this);
126   g_signal_connect(drag_widget_, "button-press-event",
127                    G_CALLBACK(OnButtonPressEventThunk), this);
128   g_signal_connect(drag_widget_, "button-release-event",
129                    G_CALLBACK(OnButtonReleaseEventThunk), this);
130   g_signal_connect(drag_widget_, "grab-broken-event",
131                    G_CALLBACK(OnGrabBrokenEventThunk), this);
132 }
133 
~PanelDragGtk()134 PanelDragGtk::~PanelDragGtk() {
135   EndDrag(true);  // Clean up drag state.
136   ReleasePointerAndKeyboardGrab();
137 }
138 
AssertCleanState()139 void PanelDragGtk::AssertCleanState() {
140   DCHECK_EQ(NOT_DRAGGING, drag_state_);
141   DCHECK(!drag_delegate_);
142   DCHECK(!initial_mouse_down_);
143   DCHECK(!click_handler_);
144 }
145 
InitialWindowEdgeMousePress(GdkEventButton * event,GdkCursor * cursor,GdkWindowEdge & edge)146 void PanelDragGtk::InitialWindowEdgeMousePress(GdkEventButton* event,
147                                                GdkCursor* cursor,
148                                                GdkWindowEdge& edge) {
149   AssertCleanState();
150   drag_delegate_ = new ResizeDragDelegate(panel_, edge);
151   drag_state_ = DRAG_CAN_START;
152   GrabPointerAndKeyboard(event, cursor);
153 }
154 
InitialTitlebarMousePress(GdkEventButton * event,GtkWidget * titlebar_widget)155 void PanelDragGtk::InitialTitlebarMousePress(GdkEventButton* event,
156                                              GtkWidget* titlebar_widget) {
157   AssertCleanState();
158   click_handler_ = titlebar_widget;
159   drag_delegate_ = new MoveDragDelegate(panel_);
160   drag_state_ = DRAG_CAN_START;
161   GrabPointerAndKeyboard(event, gfx::GetCursor(GDK_FLEUR));  // Drag cursor.
162 }
163 
GrabPointerAndKeyboard(GdkEventButton * event,GdkCursor * cursor)164 void PanelDragGtk::GrabPointerAndKeyboard(GdkEventButton* event,
165                                           GdkCursor* cursor) {
166   // Remember initial mouse event for use in determining when drag
167   // threshold has been exceeded.
168   initial_mouse_down_ = gdk_event_copy(reinterpret_cast<GdkEvent*>(event));
169 
170   // Grab pointer and keyboard to make sure we have the focus and get
171   // all mouse and keyboard events during the drag.
172   GdkWindow* gdk_window = gtk_widget_get_window(drag_widget_);
173   DCHECK(gdk_window);
174   GdkGrabStatus pointer_grab_status =
175       gdk_pointer_grab(gdk_window,
176                        TRUE,
177                        GdkEventMask(GDK_BUTTON_PRESS_MASK |
178                                     GDK_BUTTON_RELEASE_MASK |
179                                     GDK_POINTER_MOTION_MASK),
180                        NULL,
181                        cursor,
182                        event->time);
183   GdkGrabStatus keyboard_grab_status =
184       gdk_keyboard_grab(gdk_window, TRUE, event->time);
185   if (pointer_grab_status != GDK_GRAB_SUCCESS ||
186       keyboard_grab_status != GDK_GRAB_SUCCESS) {
187     // Grab could fail if someone else already has the pointer/keyboard
188     // grabbed. Cancel the drag.
189     DLOG(ERROR) << "Unable to grab pointer or keyboard (pointer_status="
190                 << pointer_grab_status << ", keyboard_status="
191                 << keyboard_grab_status << ")";
192     EndDrag(true);
193     ReleasePointerAndKeyboardGrab();
194     return;
195   }
196 
197   gtk_grab_add(drag_widget_);
198 }
199 
ReleasePointerAndKeyboardGrab()200 void PanelDragGtk::ReleasePointerAndKeyboardGrab() {
201   DCHECK(!drag_delegate_);
202   if (drag_state_ == NOT_DRAGGING)
203     return;
204 
205   DCHECK_EQ(DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE, drag_state_);
206   gdk_pointer_ungrab(GDK_CURRENT_TIME);
207   gdk_keyboard_ungrab(GDK_CURRENT_TIME);
208   gtk_grab_remove(drag_widget_);
209   drag_state_ = NOT_DRAGGING;  // Drag is truly over now.
210 }
211 
EndDrag(bool canceled)212 void PanelDragGtk::EndDrag(bool canceled) {
213   if (drag_state_ == NOT_DRAGGING ||
214       drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
215     DCHECK(!drag_delegate_);
216     return;
217   }
218 
219   DCHECK(drag_delegate_);
220 
221   if (initial_mouse_down_) {
222     gdk_event_free(initial_mouse_down_);
223     initial_mouse_down_ = NULL;
224   }
225 
226   if (drag_state_ == DRAG_IN_PROGRESS) {
227     drag_delegate_->DragEnded(canceled);
228   }
229   drag_state_ = DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE;
230 
231   delete drag_delegate_;
232   drag_delegate_ = NULL;
233 
234   click_handler_ = NULL;
235 }
236 
OnMouseMoveEvent(GtkWidget * widget,GdkEventMotion * event)237 gboolean PanelDragGtk::OnMouseMoveEvent(GtkWidget* widget,
238                                         GdkEventMotion* event) {
239   DCHECK(drag_state_ != NOT_DRAGGING);
240 
241   if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
242     DCHECK(!drag_delegate_);
243     return TRUE;
244   }
245 
246   DCHECK(drag_delegate_);
247 
248   gdouble new_x_double;
249   gdouble new_y_double;
250   gdk_event_get_root_coords(reinterpret_cast<GdkEvent*>(event),
251                             &new_x_double, &new_y_double);
252   gint new_x = static_cast<gint>(new_x_double);
253   gint new_y = static_cast<gint>(new_y_double);
254 
255   // Begin dragging only after mouse has moved beyond the drag threshold.
256   if (drag_state_ == DRAG_CAN_START) {
257     DCHECK(initial_mouse_down_);
258     gdouble old_x_double;
259     gdouble old_y_double;
260     gdk_event_get_root_coords(initial_mouse_down_,
261                               &old_x_double, &old_y_double);
262     gint old_x = static_cast<gint>(old_x_double);
263     gint old_y = static_cast<gint>(old_y_double);
264 
265     if (gtk_drag_check_threshold(drag_widget_, old_x, old_y,
266                                  new_x, new_y)) {
267       drag_state_ = DRAG_IN_PROGRESS;
268       drag_delegate_->DragStarted(gfx::Point(old_x, old_y));
269       gdk_event_free(initial_mouse_down_);
270       initial_mouse_down_ = NULL;
271     }
272   }
273 
274   if (drag_state_ == DRAG_IN_PROGRESS)
275     drag_delegate_->Dragged(gfx::Point(new_x, new_y));
276 
277   return TRUE;
278 }
279 
OnButtonPressEvent(GtkWidget * widget,GdkEventButton * event)280 gboolean PanelDragGtk::OnButtonPressEvent(GtkWidget* widget,
281                                           GdkEventButton* event) {
282   DCHECK(drag_state_ != NOT_DRAGGING);
283   return TRUE;
284 }
285 
OnButtonReleaseEvent(GtkWidget * widget,GdkEventButton * event)286 gboolean PanelDragGtk::OnButtonReleaseEvent(GtkWidget* widget,
287                                             GdkEventButton* event) {
288   DCHECK(drag_state_ != NOT_DRAGGING);
289 
290   if (event->button == 1) {
291     // Treat release as a mouse click if drag was never started.
292     if (drag_state_ == DRAG_CAN_START && click_handler_) {
293       gtk_propagate_event(click_handler_,
294                           reinterpret_cast<GdkEvent*>(event));
295     }
296     // Cleanup state regardless.
297     EndDrag(false);
298     ReleasePointerAndKeyboardGrab();
299   }
300 
301   return TRUE;
302 }
303 
OnKeyPressEvent(GtkWidget * widget,GdkEventKey * event)304 gboolean PanelDragGtk::OnKeyPressEvent(GtkWidget* widget,
305                                        GdkEventKey* event) {
306   DCHECK(drag_state_ != NOT_DRAGGING);
307   return TRUE;
308 }
309 
OnKeyReleaseEvent(GtkWidget * widget,GdkEventKey * event)310 gboolean PanelDragGtk::OnKeyReleaseEvent(GtkWidget* widget,
311                                          GdkEventKey* event) {
312   DCHECK(drag_state_ != NOT_DRAGGING);
313 
314   if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) {
315     DCHECK(!drag_delegate_);
316     return TRUE;
317   }
318 
319   DCHECK(drag_delegate_);
320 
321   switch (event->keyval) {
322   case GDK_Escape:
323     EndDrag(true);  // Cancel drag.
324     break;
325   case GDK_Return:
326   case GDK_KP_Enter:
327   case GDK_ISO_Enter:
328   case GDK_space:
329     EndDrag(false);  // Normal end.
330     break;
331   }
332   return TRUE;
333 }
334 
OnGrabBrokenEvent(GtkWidget * widget,GdkEventGrabBroken * event)335 gboolean PanelDragGtk::OnGrabBrokenEvent(GtkWidget* widget,
336                                          GdkEventGrabBroken* event) {
337   DCHECK(drag_state_ != NOT_DRAGGING);
338   EndDrag(true);  // Cancel drag.
339   ReleasePointerAndKeyboardGrab();
340   return TRUE;
341 }
342