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