OLD | NEW |
| (Empty) |
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 | |
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: | |
44 explicit PanelDragDelegate(Panel* panel) : panel_(panel) {} | |
45 virtual ~PanelDragDelegate() {} | |
46 | |
47 Panel* panel() const { return panel_; } | |
48 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: | |
66 explicit MoveDragDelegate(Panel* panel) | |
67 : PanelDragDelegate(panel) {} | |
68 virtual ~MoveDragDelegate() {} | |
69 | |
70 virtual void DragStarted(gfx::Point point) OVERRIDE { | |
71 panel_manager()->StartDragging(panel(), point); | |
72 } | |
73 virtual void Dragged(gfx::Point point) OVERRIDE { | |
74 panel_manager()->Drag(point); | |
75 } | |
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: | |
86 ResizeDragDelegate(Panel* panel, GdkWindowEdge edge) | |
87 : PanelDragDelegate(panel), | |
88 resizing_side_(GdkWindowEdgeToResizingSide(edge)) {} | |
89 virtual ~ResizeDragDelegate() {} | |
90 | |
91 virtual void DragStarted(gfx::Point point) OVERRIDE { | |
92 panel_manager()->StartResizingByMouse(panel(), point, resizing_side_); | |
93 } | |
94 virtual void Dragged(gfx::Point point) OVERRIDE { | |
95 panel_manager()->ResizeByMouse(point); | |
96 } | |
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. | |
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 | |
134 PanelDragGtk::~PanelDragGtk() { | |
135 EndDrag(true); // Clean up drag state. | |
136 ReleasePointerAndKeyboardGrab(); | |
137 } | |
138 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
280 gboolean PanelDragGtk::OnButtonPressEvent(GtkWidget* widget, | |
281 GdkEventButton* event) { | |
282 DCHECK(drag_state_ != NOT_DRAGGING); | |
283 return TRUE; | |
284 } | |
285 | |
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 | |
304 gboolean PanelDragGtk::OnKeyPressEvent(GtkWidget* widget, | |
305 GdkEventKey* event) { | |
306 DCHECK(drag_state_ != NOT_DRAGGING); | |
307 return TRUE; | |
308 } | |
309 | |
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 | |
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 } | |
OLD | NEW |