1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2014 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | * |
17 | * Author(s): Carlos Garnacho <carlosg@gnome.org> |
18 | */ |
19 | |
20 | /** |
21 | * GtkGestureClick: |
22 | * |
23 | * `GtkGestureClick` is a `GtkGesture` implementation for clicks. |
24 | * |
25 | * It is able to recognize multiple clicks on a nearby zone, which |
26 | * can be listened for through the [signal@Gtk.GestureClick::pressed] |
27 | * signal. Whenever time or distance between clicks exceed the GTK |
28 | * defaults, [signal@Gtk.GestureClick::stopped] is emitted, and the |
29 | * click counter is reset. |
30 | */ |
31 | |
32 | #include "config.h" |
33 | #include "gtkgestureprivate.h" |
34 | #include "gtkgestureclick.h" |
35 | #include "gtkgestureclickprivate.h" |
36 | #include "gtkprivate.h" |
37 | #include "gtkintl.h" |
38 | #include "gtkmarshalers.h" |
39 | |
40 | typedef struct _GtkGestureClickPrivate GtkGestureClickPrivate; |
41 | |
42 | struct _GtkGestureClickPrivate |
43 | { |
44 | GdkDevice *current_device; |
45 | double initial_press_x; |
46 | double initial_press_y; |
47 | guint double_click_timeout_id; |
48 | guint n_presses; |
49 | guint n_release; |
50 | guint current_button; |
51 | }; |
52 | |
53 | enum { |
54 | PRESSED, |
55 | RELEASED, |
56 | STOPPED, |
57 | UNPAIRED_RELEASE, |
58 | LAST_SIGNAL |
59 | }; |
60 | |
61 | static guint signals[LAST_SIGNAL] = { 0 }; |
62 | |
63 | G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureClick, gtk_gesture_click, GTK_TYPE_GESTURE_SINGLE) |
64 | |
65 | static void |
66 | gtk_gesture_click_finalize (GObject *object) |
67 | { |
68 | GtkGestureClickPrivate *priv; |
69 | GtkGestureClick *gesture; |
70 | |
71 | gesture = GTK_GESTURE_CLICK (object); |
72 | priv = gtk_gesture_click_get_instance_private (self: gesture); |
73 | |
74 | if (priv->double_click_timeout_id) |
75 | { |
76 | g_source_remove (tag: priv->double_click_timeout_id); |
77 | priv->double_click_timeout_id = 0; |
78 | } |
79 | |
80 | G_OBJECT_CLASS (gtk_gesture_click_parent_class)->finalize (object); |
81 | } |
82 | |
83 | static gboolean |
84 | gtk_gesture_click_check (GtkGesture *gesture) |
85 | { |
86 | GtkGestureClick *click; |
87 | GtkGestureClickPrivate *priv; |
88 | GList *sequences; |
89 | gboolean active; |
90 | |
91 | click = GTK_GESTURE_CLICK (gesture); |
92 | priv = gtk_gesture_click_get_instance_private (self: click); |
93 | sequences = gtk_gesture_get_sequences (gesture); |
94 | |
95 | active = g_list_length (list: sequences) == 1 || priv->double_click_timeout_id; |
96 | g_list_free (list: sequences); |
97 | |
98 | return active; |
99 | } |
100 | |
101 | static void |
102 | _gtk_gesture_click_stop (GtkGestureClick *gesture) |
103 | { |
104 | GtkGestureClickPrivate *priv; |
105 | |
106 | priv = gtk_gesture_click_get_instance_private (self: gesture); |
107 | |
108 | if (priv->n_presses == 0) |
109 | return; |
110 | |
111 | priv->current_device = NULL; |
112 | priv->current_button = 0; |
113 | priv->n_presses = 0; |
114 | g_signal_emit (instance: gesture, signal_id: signals[STOPPED], detail: 0); |
115 | _gtk_gesture_check (GTK_GESTURE (gesture)); |
116 | } |
117 | |
118 | static gboolean |
119 | _double_click_timeout_cb (gpointer user_data) |
120 | { |
121 | GtkGestureClick *gesture = user_data; |
122 | GtkGestureClickPrivate *priv; |
123 | |
124 | priv = gtk_gesture_click_get_instance_private (self: gesture); |
125 | priv->double_click_timeout_id = 0; |
126 | _gtk_gesture_click_stop (gesture); |
127 | |
128 | return FALSE; |
129 | } |
130 | |
131 | static void |
132 | _gtk_gesture_click_update_timeout (GtkGestureClick *gesture) |
133 | { |
134 | GtkGestureClickPrivate *priv; |
135 | guint double_click_time; |
136 | GtkSettings *settings; |
137 | GtkWidget *widget; |
138 | |
139 | priv = gtk_gesture_click_get_instance_private (self: gesture); |
140 | |
141 | if (priv->double_click_timeout_id) |
142 | g_source_remove (tag: priv->double_click_timeout_id); |
143 | |
144 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
145 | settings = gtk_widget_get_settings (widget); |
146 | g_object_get (object: settings, first_property_name: "gtk-double-click-time" , &double_click_time, NULL); |
147 | |
148 | priv->double_click_timeout_id = g_timeout_add (interval: double_click_time, function: _double_click_timeout_cb, data: gesture); |
149 | gdk_source_set_static_name_by_id (tag: priv->double_click_timeout_id, name: "[gtk] _double_click_timeout_cb" ); |
150 | } |
151 | |
152 | static gboolean |
153 | _gtk_gesture_click_check_within_threshold (GtkGestureClick *gesture, |
154 | double x, |
155 | double y) |
156 | { |
157 | GtkGestureClickPrivate *priv; |
158 | guint double_click_distance; |
159 | GtkSettings *settings; |
160 | GtkWidget *widget; |
161 | |
162 | priv = gtk_gesture_click_get_instance_private (self: gesture); |
163 | |
164 | if (priv->n_presses == 0) |
165 | return TRUE; |
166 | |
167 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
168 | settings = gtk_widget_get_settings (widget); |
169 | g_object_get (object: settings, |
170 | first_property_name: "gtk-double-click-distance" , &double_click_distance, |
171 | NULL); |
172 | |
173 | if (ABS (priv->initial_press_x - x) < double_click_distance && |
174 | ABS (priv->initial_press_y - y) < double_click_distance) |
175 | return TRUE; |
176 | |
177 | return FALSE; |
178 | } |
179 | |
180 | static void |
181 | gtk_gesture_click_begin (GtkGesture *gesture, |
182 | GdkEventSequence *sequence) |
183 | { |
184 | GtkGestureClick *click; |
185 | GtkGestureClickPrivate *priv; |
186 | guint n_presses, button = 1; |
187 | GdkEventSequence *current; |
188 | GdkEvent *event; |
189 | GdkEventType event_type; |
190 | GdkDevice *device; |
191 | double x, y; |
192 | |
193 | if (!gtk_gesture_handles_sequence (gesture, sequence)) |
194 | return; |
195 | |
196 | click = GTK_GESTURE_CLICK (gesture); |
197 | priv = gtk_gesture_click_get_instance_private (self: click); |
198 | event = gtk_gesture_get_last_event (gesture, sequence); |
199 | current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
200 | device = gdk_event_get_device (event); |
201 | event_type = gdk_event_get_event_type (event); |
202 | |
203 | if (event_type == GDK_BUTTON_PRESS) |
204 | button = gdk_button_event_get_button (event); |
205 | else if (event_type == GDK_TOUCH_BEGIN) |
206 | button = 1; |
207 | else |
208 | return; |
209 | |
210 | /* Reset the gesture if the button number changes mid-recognition */ |
211 | if (priv->n_presses > 0 && |
212 | priv->current_button != button) |
213 | _gtk_gesture_click_stop (gesture: click); |
214 | |
215 | /* Reset also if the device changed */ |
216 | if (priv->current_device && priv->current_device != device) |
217 | _gtk_gesture_click_stop (gesture: click); |
218 | |
219 | priv->current_device = device; |
220 | priv->current_button = button; |
221 | _gtk_gesture_click_update_timeout (gesture: click); |
222 | gtk_gesture_get_point (gesture, sequence: current, x: &x, y: &y); |
223 | |
224 | if (!_gtk_gesture_click_check_within_threshold (gesture: click, x, y)) |
225 | _gtk_gesture_click_stop (gesture: click); |
226 | |
227 | /* Increment later the real counter, just if the gesture is |
228 | * reset on the pressed handler */ |
229 | n_presses = priv->n_release = priv->n_presses + 1; |
230 | |
231 | g_signal_emit (instance: gesture, signal_id: signals[PRESSED], detail: 0, n_presses, x, y); |
232 | |
233 | if (priv->n_presses == 0) |
234 | { |
235 | priv->initial_press_x = x; |
236 | priv->initial_press_y = y; |
237 | } |
238 | |
239 | priv->n_presses++; |
240 | } |
241 | |
242 | static void |
243 | gtk_gesture_click_update (GtkGesture *gesture, |
244 | GdkEventSequence *sequence) |
245 | { |
246 | GtkGestureClick *click; |
247 | GdkEventSequence *current; |
248 | double x, y; |
249 | |
250 | click = GTK_GESTURE_CLICK (gesture); |
251 | current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
252 | gtk_gesture_get_point (gesture, sequence: current, x: &x, y: &y); |
253 | |
254 | if (!_gtk_gesture_click_check_within_threshold (gesture: click, x, y)) |
255 | _gtk_gesture_click_stop (gesture: click); |
256 | } |
257 | |
258 | static void |
259 | gtk_gesture_click_end (GtkGesture *gesture, |
260 | GdkEventSequence *sequence) |
261 | { |
262 | GtkGestureClick *click; |
263 | GtkGestureClickPrivate *priv; |
264 | GdkEventSequence *current; |
265 | double x, y; |
266 | gboolean interpreted; |
267 | GtkEventSequenceState state; |
268 | |
269 | click = GTK_GESTURE_CLICK (gesture); |
270 | priv = gtk_gesture_click_get_instance_private (self: click); |
271 | current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
272 | interpreted = gtk_gesture_get_point (gesture, sequence: current, x: &x, y: &y); |
273 | state = gtk_gesture_get_sequence_state (gesture, sequence: current); |
274 | |
275 | if (current == sequence && state != GTK_EVENT_SEQUENCE_DENIED && interpreted) |
276 | g_signal_emit (instance: gesture, signal_id: signals[RELEASED], detail: 0, priv->n_release, x, y); |
277 | |
278 | priv->n_release = 0; |
279 | } |
280 | |
281 | static void |
282 | gtk_gesture_click_cancel (GtkGesture *gesture, |
283 | GdkEventSequence *sequence) |
284 | { |
285 | _gtk_gesture_click_stop (GTK_GESTURE_CLICK (gesture)); |
286 | GTK_GESTURE_CLASS (gtk_gesture_click_parent_class)->cancel (gesture, sequence); |
287 | } |
288 | |
289 | static void |
290 | gtk_gesture_click_reset (GtkEventController *controller) |
291 | { |
292 | _gtk_gesture_click_stop (GTK_GESTURE_CLICK (controller)); |
293 | GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_click_parent_class)->reset (controller); |
294 | } |
295 | |
296 | static gboolean |
297 | gtk_gesture_click_handle_event (GtkEventController *controller, |
298 | GdkEvent *event, |
299 | double x, |
300 | double y) |
301 | { |
302 | GtkEventControllerClass *parent_controller; |
303 | GtkGestureClickPrivate *priv; |
304 | GdkEventSequence *sequence; |
305 | guint button; |
306 | |
307 | priv = gtk_gesture_click_get_instance_private (GTK_GESTURE_CLICK (controller)); |
308 | parent_controller = GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_click_parent_class); |
309 | sequence = gdk_event_get_event_sequence (event); |
310 | |
311 | if (priv->n_presses == 0 && |
312 | !gtk_gesture_handles_sequence (GTK_GESTURE (controller), sequence) && |
313 | (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE || |
314 | gdk_event_get_event_type (event) == GDK_TOUCH_END)) |
315 | { |
316 | if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE) |
317 | button = gdk_button_event_get_button (event); |
318 | else |
319 | button = 0; |
320 | g_signal_emit (instance: controller, signal_id: signals[UNPAIRED_RELEASE], detail: 0, |
321 | x, y, button, sequence); |
322 | } |
323 | |
324 | return parent_controller->handle_event (controller, event, x, y); |
325 | } |
326 | |
327 | static void |
328 | gtk_gesture_click_class_init (GtkGestureClickClass *klass) |
329 | { |
330 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
331 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
332 | GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass); |
333 | |
334 | object_class->finalize = gtk_gesture_click_finalize; |
335 | |
336 | gesture_class->check = gtk_gesture_click_check; |
337 | gesture_class->begin = gtk_gesture_click_begin; |
338 | gesture_class->update = gtk_gesture_click_update; |
339 | gesture_class->end = gtk_gesture_click_end; |
340 | gesture_class->cancel = gtk_gesture_click_cancel; |
341 | |
342 | controller_class->reset = gtk_gesture_click_reset; |
343 | controller_class->handle_event = gtk_gesture_click_handle_event; |
344 | |
345 | /** |
346 | * GtkGestureClick::pressed: |
347 | * @gesture: the object which received the signal |
348 | * @n_press: how many touch/button presses happened with this one |
349 | * @x: The X coordinate, in widget allocation coordinates |
350 | * @y: The Y coordinate, in widget allocation coordinates |
351 | * |
352 | * Emitted whenever a button or touch press happens. |
353 | */ |
354 | signals[PRESSED] = |
355 | g_signal_new (I_("pressed" ), |
356 | G_TYPE_FROM_CLASS (klass), |
357 | signal_flags: G_SIGNAL_RUN_LAST, |
358 | G_STRUCT_OFFSET (GtkGestureClickClass, pressed), |
359 | NULL, NULL, |
360 | c_marshaller: _gtk_marshal_VOID__INT_DOUBLE_DOUBLE, |
361 | G_TYPE_NONE, n_params: 3, G_TYPE_INT, |
362 | G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
363 | g_signal_set_va_marshaller (signal_id: signals[PRESSED], |
364 | G_TYPE_FROM_CLASS (klass), |
365 | va_marshaller: _gtk_marshal_VOID__INT_DOUBLE_DOUBLEv); |
366 | |
367 | /** |
368 | * GtkGestureClick::released: |
369 | * @gesture: the object which received the signal |
370 | * @n_press: number of press that is paired with this release |
371 | * @x: The X coordinate, in widget allocation coordinates |
372 | * @y: The Y coordinate, in widget allocation coordinates |
373 | * |
374 | * Emitted when a button or touch is released. |
375 | * |
376 | * @n_press will report the number of press that is paired to |
377 | * this event, note that [signal@Gtk.GestureClick::stopped] may |
378 | * have been emitted between the press and its release, @n_press |
379 | * will only start over at the next press. |
380 | */ |
381 | signals[RELEASED] = |
382 | g_signal_new (I_("released" ), |
383 | G_TYPE_FROM_CLASS (klass), |
384 | signal_flags: G_SIGNAL_RUN_LAST, |
385 | G_STRUCT_OFFSET (GtkGestureClickClass, released), |
386 | NULL, NULL, |
387 | c_marshaller: _gtk_marshal_VOID__INT_DOUBLE_DOUBLE, |
388 | G_TYPE_NONE, n_params: 3, G_TYPE_INT, |
389 | G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
390 | g_signal_set_va_marshaller (signal_id: signals[RELEASED], |
391 | G_TYPE_FROM_CLASS (klass), |
392 | va_marshaller: _gtk_marshal_VOID__INT_DOUBLE_DOUBLEv); |
393 | |
394 | /** |
395 | * GtkGestureClick::stopped: |
396 | * @gesture: the object which received the signal |
397 | * |
398 | * Emitted whenever any time/distance threshold has been exceeded. |
399 | */ |
400 | signals[STOPPED] = |
401 | g_signal_new (I_("stopped" ), |
402 | G_TYPE_FROM_CLASS (klass), |
403 | signal_flags: G_SIGNAL_RUN_LAST, |
404 | G_STRUCT_OFFSET (GtkGestureClickClass, stopped), |
405 | NULL, NULL, NULL, |
406 | G_TYPE_NONE, n_params: 0); |
407 | |
408 | /** |
409 | * GtkGestureClick::unpaired-release |
410 | * @gesture: the object which received the signal |
411 | * @x: X coordinate of the event |
412 | * @y: Y coordinate of the event |
413 | * @button: Button being released |
414 | * @sequence: Sequence being released |
415 | * |
416 | * Emitted whenever the gesture receives a release |
417 | * event that had no previous corresponding press. |
418 | * |
419 | * Due to implicit grabs, this can only happen on situations |
420 | * where input is grabbed elsewhere mid-press or the pressed |
421 | * widget voluntarily relinquishes its implicit grab. |
422 | */ |
423 | signals[UNPAIRED_RELEASE] = |
424 | g_signal_new (I_("unpaired-release" ), |
425 | G_TYPE_FROM_CLASS (klass), |
426 | signal_flags: G_SIGNAL_RUN_LAST, |
427 | class_offset: 0, NULL, NULL, |
428 | c_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE_UINT_BOXED, |
429 | G_TYPE_NONE, n_params: 4, |
430 | G_TYPE_DOUBLE, G_TYPE_DOUBLE, |
431 | G_TYPE_UINT, GDK_TYPE_EVENT_SEQUENCE); |
432 | g_signal_set_va_marshaller (signal_id: signals[UNPAIRED_RELEASE], |
433 | G_TYPE_FROM_CLASS (klass), |
434 | va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE_UINT_BOXEDv); |
435 | } |
436 | |
437 | static void |
438 | gtk_gesture_click_init (GtkGestureClick *gesture) |
439 | { |
440 | } |
441 | |
442 | /** |
443 | * gtk_gesture_click_new: |
444 | * |
445 | * Returns a newly created `GtkGesture` that recognizes |
446 | * single and multiple presses. |
447 | * |
448 | * Returns: a newly created `GtkGestureClick` |
449 | **/ |
450 | GtkGesture * |
451 | gtk_gesture_click_new (void) |
452 | { |
453 | return g_object_new (GTK_TYPE_GESTURE_CLICK, NULL); |
454 | } |
455 | |