1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2012, One Laptop Per Child. |
3 | * Copyright (C) 2014, Red Hat, Inc. |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Author(s): Carlos Garnacho <carlosg@gnome.org> |
19 | */ |
20 | |
21 | /** |
22 | * GtkGesture: |
23 | * |
24 | * `GtkGesture` is the base class for gesture recognition. |
25 | * |
26 | * Although `GtkGesture` is quite generalized to serve as a base for |
27 | * multi-touch gestures, it is suitable to implement single-touch and |
28 | * pointer-based gestures (using the special %NULL `GdkEventSequence` |
29 | * value for these). |
30 | * |
31 | * The number of touches that a `GtkGesture` need to be recognized is |
32 | * controlled by the [property@Gtk.Gesture:n-points] property, if a |
33 | * gesture is keeping track of less or more than that number of sequences, |
34 | * it won't check whether the gesture is recognized. |
35 | * |
36 | * As soon as the gesture has the expected number of touches, it will check |
37 | * regularly if it is recognized, the criteria to consider a gesture as |
38 | * "recognized" is left to `GtkGesture` subclasses. |
39 | * |
40 | * A recognized gesture will then emit the following signals: |
41 | * |
42 | * - [signal@Gtk.Gesture::begin] when the gesture is recognized. |
43 | * - [signal@Gtk.Gesture::update], whenever an input event is processed. |
44 | * - [signal@Gtk.Gesture::end] when the gesture is no longer recognized. |
45 | * |
46 | * ## Event propagation |
47 | * |
48 | * In order to receive events, a gesture needs to set a propagation phase |
49 | * through [method@Gtk.EventController.set_propagation_phase]. |
50 | * |
51 | * In the capture phase, events are propagated from the toplevel down |
52 | * to the target widget, and gestures that are attached to containers |
53 | * above the widget get a chance to interact with the event before it |
54 | * reaches the target. |
55 | * |
56 | * In the bubble phase, events are propagated up from the target widget |
57 | * to the toplevel, and gestures that are attached to containers above |
58 | * the widget get a chance to interact with events that have not been |
59 | * handled yet. |
60 | * |
61 | * ## States of a sequence |
62 | * |
63 | * Whenever input interaction happens, a single event may trigger a cascade |
64 | * of `GtkGesture`s, both across the parents of the widget receiving the |
65 | * event and in parallel within an individual widget. It is a responsibility |
66 | * of the widgets using those gestures to set the state of touch sequences |
67 | * accordingly in order to enable cooperation of gestures around the |
68 | * `GdkEventSequence`s triggering those. |
69 | * |
70 | * Within a widget, gestures can be grouped through [method@Gtk.Gesture.group]. |
71 | * Grouped gestures synchronize the state of sequences, so calling |
72 | * [method@Gtk.Gesture.set_sequence_state] on one will effectively propagate |
73 | * the state throughout the group. |
74 | * |
75 | * By default, all sequences start out in the %GTK_EVENT_SEQUENCE_NONE state, |
76 | * sequences in this state trigger the gesture event handler, but event |
77 | * propagation will continue unstopped by gestures. |
78 | * |
79 | * If a sequence enters into the %GTK_EVENT_SEQUENCE_DENIED state, the gesture |
80 | * group will effectively ignore the sequence, letting events go unstopped |
81 | * through the gesture, but the "slot" will still remain occupied while |
82 | * the touch is active. |
83 | * |
84 | * If a sequence enters in the %GTK_EVENT_SEQUENCE_CLAIMED state, the gesture |
85 | * group will grab all interaction on the sequence, by: |
86 | * |
87 | * - Setting the same sequence to %GTK_EVENT_SEQUENCE_DENIED on every other |
88 | * gesture group within the widget, and every gesture on parent widgets |
89 | * in the propagation chain. |
90 | * - Emitting [signal@Gtk.Gesture::cancel] on every gesture in widgets |
91 | * underneath in the propagation chain. |
92 | * - Stopping event propagation after the gesture group handles the event. |
93 | * |
94 | * Note: if a sequence is set early to %GTK_EVENT_SEQUENCE_CLAIMED on |
95 | * %GDK_TOUCH_BEGIN/%GDK_BUTTON_PRESS (so those events are captured before |
96 | * reaching the event widget, this implies %GTK_PHASE_CAPTURE), one similar |
97 | * event will be emulated if the sequence changes to %GTK_EVENT_SEQUENCE_DENIED. |
98 | * This way event coherence is preserved before event propagation is unstopped |
99 | * again. |
100 | * |
101 | * Sequence states can't be changed freely. |
102 | * See [method@Gtk.Gesture.set_sequence_state] to know about the possible |
103 | * lifetimes of a `GdkEventSequence`. |
104 | * |
105 | * ## Touchpad gestures |
106 | * |
107 | * On the platforms that support it, `GtkGesture` will handle transparently |
108 | * touchpad gesture events. The only precautions users of `GtkGesture` should |
109 | * do to enable this support are: |
110 | * |
111 | * - If the gesture has %GTK_PHASE_NONE, ensuring events of type |
112 | * %GDK_TOUCHPAD_SWIPE and %GDK_TOUCHPAD_PINCH are handled by the `GtkGesture` |
113 | */ |
114 | |
115 | #include "config.h" |
116 | #include "gtkgesture.h" |
117 | #include "gtkwidgetprivate.h" |
118 | #include "gtkeventcontrollerprivate.h" |
119 | #include "gtkgestureprivate.h" |
120 | #include "gtktypebuiltins.h" |
121 | #include "gtkprivate.h" |
122 | #include "gtkmain.h" |
123 | #include "gtkintl.h" |
124 | #include "gtkmarshalers.h" |
125 | #include "gtknative.h" |
126 | |
127 | typedef struct _GtkGesturePrivate GtkGesturePrivate; |
128 | typedef struct _PointData PointData; |
129 | |
130 | enum { |
131 | PROP_N_POINTS = 1, |
132 | }; |
133 | |
134 | enum { |
135 | BEGIN, |
136 | END, |
137 | UPDATE, |
138 | CANCEL, |
139 | SEQUENCE_STATE_CHANGED, |
140 | N_SIGNALS |
141 | }; |
142 | |
143 | struct _PointData |
144 | { |
145 | GdkEvent *event; |
146 | GtkWidget *target; |
147 | double widget_x; |
148 | double widget_y; |
149 | |
150 | /* Acummulators for touchpad events */ |
151 | double accum_dx; |
152 | double accum_dy; |
153 | |
154 | guint press_handled : 1; |
155 | guint state : 2; |
156 | }; |
157 | |
158 | struct _GtkGesturePrivate |
159 | { |
160 | GHashTable *points; |
161 | GdkEventSequence *last_sequence; |
162 | GdkDevice *device; |
163 | GList *group_link; |
164 | guint n_points; |
165 | guint hold_timeout_id; |
166 | guint recognized : 1; |
167 | guint touchpad : 1; |
168 | }; |
169 | |
170 | static guint signals[N_SIGNALS] = { 0 }; |
171 | |
172 | #define BUTTONS_MASK (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK) |
173 | |
174 | #define EVENT_IS_TOUCHPAD_GESTURE(e) (gdk_event_get_event_type (e) == GDK_TOUCHPAD_SWIPE || \ |
175 | gdk_event_get_event_type (e) == GDK_TOUCHPAD_PINCH || \ |
176 | gdk_event_get_event_type (e) == GDK_TOUCHPAD_HOLD) |
177 | |
178 | #define HOLD_TIMEOUT_MS 50 |
179 | |
180 | GList * _gtk_gesture_get_group_link (GtkGesture *gesture); |
181 | |
182 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkGesture, gtk_gesture, GTK_TYPE_EVENT_CONTROLLER) |
183 | |
184 | static void |
185 | gtk_gesture_get_property (GObject *object, |
186 | guint prop_id, |
187 | GValue *value, |
188 | GParamSpec *pspec) |
189 | { |
190 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (GTK_GESTURE (object)); |
191 | |
192 | switch (prop_id) |
193 | { |
194 | case PROP_N_POINTS: |
195 | g_value_set_uint (value, v_uint: priv->n_points); |
196 | break; |
197 | default: |
198 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
199 | } |
200 | } |
201 | |
202 | static void |
203 | gtk_gesture_set_property (GObject *object, |
204 | guint prop_id, |
205 | const GValue *value, |
206 | GParamSpec *pspec) |
207 | { |
208 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (GTK_GESTURE (object)); |
209 | |
210 | switch (prop_id) |
211 | { |
212 | case PROP_N_POINTS: |
213 | priv->n_points = g_value_get_uint (value); |
214 | break; |
215 | default: |
216 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
217 | } |
218 | } |
219 | |
220 | static void |
221 | gtk_gesture_finalize (GObject *object) |
222 | { |
223 | GtkGesture *gesture = GTK_GESTURE (object); |
224 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (self: gesture); |
225 | |
226 | gtk_gesture_ungroup (gesture); |
227 | g_list_free (list: priv->group_link); |
228 | g_clear_handle_id (&priv->hold_timeout_id, g_source_remove); |
229 | |
230 | g_hash_table_destroy (hash_table: priv->points); |
231 | |
232 | G_OBJECT_CLASS (gtk_gesture_parent_class)->finalize (object); |
233 | } |
234 | |
235 | static guint |
236 | _gtk_gesture_get_n_touchpad_points (GtkGesture *gesture, |
237 | gboolean only_active) |
238 | { |
239 | GtkGesturePrivate *priv; |
240 | PointData *data; |
241 | GdkEventType event_type; |
242 | GdkTouchpadGesturePhase phase = 0; |
243 | guint n_fingers = 0; |
244 | |
245 | priv = gtk_gesture_get_instance_private (self: gesture); |
246 | |
247 | if (!priv->touchpad) |
248 | return 0; |
249 | |
250 | data = g_hash_table_lookup (hash_table: priv->points, key: priv->last_sequence); |
251 | |
252 | if (!data) |
253 | return 0; |
254 | |
255 | event_type = gdk_event_get_event_type (event: data->event); |
256 | |
257 | if (EVENT_IS_TOUCHPAD_GESTURE (data->event)) |
258 | { |
259 | phase = gdk_touchpad_event_get_gesture_phase (event: data->event); |
260 | n_fingers = gdk_touchpad_event_get_n_fingers (event: data->event); |
261 | } |
262 | |
263 | if (only_active && |
264 | (data->state == GTK_EVENT_SEQUENCE_DENIED || |
265 | (event_type == GDK_TOUCHPAD_SWIPE && phase == GDK_TOUCHPAD_GESTURE_PHASE_END) || |
266 | (event_type == GDK_TOUCHPAD_PINCH && phase == GDK_TOUCHPAD_GESTURE_PHASE_END) || |
267 | (event_type == GDK_TOUCHPAD_HOLD && phase == GDK_TOUCHPAD_GESTURE_PHASE_END))) |
268 | return 0; |
269 | |
270 | return n_fingers; |
271 | } |
272 | |
273 | static guint |
274 | _gtk_gesture_get_n_touch_points (GtkGesture *gesture, |
275 | gboolean only_active) |
276 | { |
277 | GtkGesturePrivate *priv; |
278 | GHashTableIter iter; |
279 | guint n_points = 0; |
280 | PointData *data; |
281 | GdkEventType event_type; |
282 | |
283 | priv = gtk_gesture_get_instance_private (self: gesture); |
284 | g_hash_table_iter_init (iter: &iter, hash_table: priv->points); |
285 | |
286 | while (g_hash_table_iter_next (iter: &iter, NULL, value: (gpointer *) &data)) |
287 | { |
288 | event_type = gdk_event_get_event_type (event: data->event); |
289 | |
290 | if (only_active && |
291 | (data->state == GTK_EVENT_SEQUENCE_DENIED || |
292 | event_type == GDK_TOUCH_END || |
293 | event_type == GDK_BUTTON_RELEASE)) |
294 | continue; |
295 | |
296 | n_points++; |
297 | } |
298 | |
299 | return n_points; |
300 | } |
301 | |
302 | static guint |
303 | _gtk_gesture_get_n_physical_points (GtkGesture *gesture, |
304 | gboolean only_active) |
305 | { |
306 | GtkGesturePrivate *priv; |
307 | |
308 | priv = gtk_gesture_get_instance_private (self: gesture); |
309 | |
310 | if (priv->touchpad) |
311 | return _gtk_gesture_get_n_touchpad_points (gesture, only_active); |
312 | else |
313 | return _gtk_gesture_get_n_touch_points (gesture, only_active); |
314 | } |
315 | |
316 | static gboolean |
317 | gtk_gesture_check_impl (GtkGesture *gesture) |
318 | { |
319 | GtkGesturePrivate *priv; |
320 | guint n_points; |
321 | |
322 | priv = gtk_gesture_get_instance_private (self: gesture); |
323 | n_points = _gtk_gesture_get_n_physical_points (gesture, TRUE); |
324 | |
325 | return n_points == priv->n_points; |
326 | } |
327 | |
328 | static void |
329 | _gtk_gesture_set_recognized (GtkGesture *gesture, |
330 | gboolean recognized, |
331 | GdkEventSequence *sequence) |
332 | { |
333 | GtkGesturePrivate *priv; |
334 | |
335 | priv = gtk_gesture_get_instance_private (self: gesture); |
336 | |
337 | if (priv->recognized == recognized) |
338 | return; |
339 | |
340 | priv->recognized = recognized; |
341 | |
342 | if (recognized) |
343 | g_signal_emit (instance: gesture, signal_id: signals[BEGIN], detail: 0, sequence); |
344 | else |
345 | g_signal_emit (instance: gesture, signal_id: signals[END], detail: 0, sequence); |
346 | } |
347 | |
348 | static gboolean |
349 | _gtk_gesture_do_check (GtkGesture *gesture) |
350 | { |
351 | GtkGestureClass *gesture_class; |
352 | gboolean retval = FALSE; |
353 | |
354 | gesture_class = GTK_GESTURE_GET_CLASS (gesture); |
355 | |
356 | if (!gesture_class->check) |
357 | return retval; |
358 | |
359 | retval = gesture_class->check (gesture); |
360 | return retval; |
361 | } |
362 | |
363 | static gboolean |
364 | _gtk_gesture_has_matching_touchpoints (GtkGesture *gesture) |
365 | { |
366 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (self: gesture); |
367 | guint active_n_points, current_n_points; |
368 | |
369 | current_n_points = _gtk_gesture_get_n_physical_points (gesture, FALSE); |
370 | active_n_points = _gtk_gesture_get_n_physical_points (gesture, TRUE); |
371 | |
372 | return (active_n_points == priv->n_points && |
373 | current_n_points == priv->n_points); |
374 | } |
375 | |
376 | static gboolean |
377 | _gtk_gesture_check_recognized (GtkGesture *gesture, |
378 | GdkEventSequence *sequence) |
379 | { |
380 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (self: gesture); |
381 | gboolean has_matching_touchpoints; |
382 | |
383 | has_matching_touchpoints = _gtk_gesture_has_matching_touchpoints (gesture); |
384 | |
385 | if (priv->recognized && !has_matching_touchpoints) |
386 | _gtk_gesture_set_recognized (gesture, FALSE, sequence); |
387 | else if (!priv->recognized && has_matching_touchpoints && |
388 | _gtk_gesture_do_check (gesture)) |
389 | _gtk_gesture_set_recognized (gesture, TRUE, sequence); |
390 | |
391 | return priv->recognized; |
392 | } |
393 | |
394 | static void |
395 | _update_touchpad_deltas (PointData *data) |
396 | { |
397 | GdkEvent *event = data->event; |
398 | GdkTouchpadGesturePhase phase; |
399 | double dx = 0; |
400 | double dy = 0; |
401 | |
402 | if (!event) |
403 | return; |
404 | |
405 | if (EVENT_IS_TOUCHPAD_GESTURE (event)) |
406 | { |
407 | phase = gdk_touchpad_event_get_gesture_phase (event); |
408 | |
409 | if (gdk_event_get_event_type (event) != GDK_TOUCHPAD_HOLD) |
410 | gdk_touchpad_event_get_deltas (event, dx: &dx, dy: &dy); |
411 | |
412 | if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN) |
413 | data->accum_dx = data->accum_dy = 0; |
414 | else if (phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) |
415 | { |
416 | data->accum_dx += dx; |
417 | data->accum_dy += dy; |
418 | } |
419 | } |
420 | } |
421 | |
422 | static GtkEventSequenceState |
423 | gtk_gesture_get_group_state (GtkGesture *gesture, |
424 | GdkEventSequence *sequence) |
425 | { |
426 | GtkEventSequenceState state = GTK_EVENT_SEQUENCE_NONE; |
427 | GList *group_elem; |
428 | |
429 | group_elem = g_list_first (list: _gtk_gesture_get_group_link (gesture)); |
430 | |
431 | for (; group_elem; group_elem = group_elem->next) |
432 | { |
433 | if (group_elem->data == gesture) |
434 | continue; |
435 | if (!gtk_gesture_handles_sequence (gesture: group_elem->data, sequence)) |
436 | continue; |
437 | |
438 | state = gtk_gesture_get_sequence_state (gesture: group_elem->data, sequence); |
439 | break; |
440 | } |
441 | |
442 | return state; |
443 | } |
444 | |
445 | static gboolean |
446 | _gtk_gesture_update_point (GtkGesture *gesture, |
447 | GdkEvent *event, |
448 | GtkWidget *target, |
449 | double x, |
450 | double y, |
451 | gboolean add) |
452 | { |
453 | GdkEventSequence *sequence; |
454 | GtkGesturePrivate *priv; |
455 | GdkDevice *device; |
456 | gboolean existed, touchpad; |
457 | PointData *data; |
458 | |
459 | device = gdk_event_get_device (event); |
460 | |
461 | if (!device) |
462 | return FALSE; |
463 | |
464 | priv = gtk_gesture_get_instance_private (self: gesture); |
465 | touchpad = EVENT_IS_TOUCHPAD_GESTURE (event); |
466 | |
467 | if (add) |
468 | { |
469 | /* If the event happens with the wrong device, or |
470 | * on the wrong window, ignore. |
471 | */ |
472 | if (priv->device && priv->device != device) |
473 | return FALSE; |
474 | |
475 | /* Make touchpad and touchscreen gestures mutually exclusive */ |
476 | if (touchpad && g_hash_table_size (hash_table: priv->points) > 0) |
477 | return FALSE; |
478 | else if (!touchpad && priv->touchpad) |
479 | return FALSE; |
480 | } |
481 | else if (!priv->device) |
482 | return FALSE; |
483 | |
484 | sequence = gdk_event_get_event_sequence (event); |
485 | existed = g_hash_table_lookup_extended (hash_table: priv->points, lookup_key: sequence, |
486 | NULL, value: (gpointer *) &data); |
487 | if (!existed) |
488 | { |
489 | if (!add) |
490 | return FALSE; |
491 | |
492 | if (g_hash_table_size (hash_table: priv->points) == 0) |
493 | { |
494 | priv->device = device; |
495 | priv->touchpad = touchpad; |
496 | } |
497 | |
498 | data = g_new0 (PointData, 1); |
499 | g_hash_table_insert (hash_table: priv->points, key: sequence, value: data); |
500 | } |
501 | |
502 | if (data->event) |
503 | gdk_event_unref (event: data->event); |
504 | |
505 | data->event = gdk_event_ref (event: (GdkEvent *)event); |
506 | g_set_object (&data->target, target); |
507 | _update_touchpad_deltas (data); |
508 | data->widget_x = x + data->accum_dx; |
509 | data->widget_y = y + data->accum_dy; |
510 | |
511 | if (!existed) |
512 | { |
513 | GtkEventSequenceState state; |
514 | |
515 | /* Deny the sequence right away if the expected |
516 | * number of points is exceeded, so this sequence |
517 | * can be tracked with gtk_gesture_handles_sequence(). |
518 | * |
519 | * Otherwise, make the sequence inherit the same state |
520 | * from other gestures in the same group. |
521 | */ |
522 | if (_gtk_gesture_get_n_physical_points (gesture, FALSE) > priv->n_points) |
523 | state = GTK_EVENT_SEQUENCE_DENIED; |
524 | else |
525 | state = gtk_gesture_get_group_state (gesture, sequence); |
526 | |
527 | gtk_gesture_set_sequence_state (gesture, sequence, state); |
528 | } |
529 | |
530 | return TRUE; |
531 | } |
532 | |
533 | static void |
534 | _gtk_gesture_check_empty (GtkGesture *gesture) |
535 | { |
536 | GtkGesturePrivate *priv; |
537 | |
538 | priv = gtk_gesture_get_instance_private (self: gesture); |
539 | |
540 | if (g_hash_table_size (hash_table: priv->points) == 0) |
541 | { |
542 | priv->device = NULL; |
543 | priv->touchpad = FALSE; |
544 | } |
545 | } |
546 | |
547 | static void |
548 | _gtk_gesture_remove_point (GtkGesture *gesture, |
549 | GdkEvent *event) |
550 | { |
551 | GdkEventSequence *sequence; |
552 | GtkGesturePrivate *priv; |
553 | GdkDevice *device; |
554 | |
555 | sequence = gdk_event_get_event_sequence (event); |
556 | device = gdk_event_get_device (event); |
557 | priv = gtk_gesture_get_instance_private (self: gesture); |
558 | |
559 | if (priv->device != device) |
560 | return; |
561 | |
562 | g_hash_table_remove (hash_table: priv->points, key: sequence); |
563 | _gtk_gesture_check_empty (gesture); |
564 | } |
565 | |
566 | static void |
567 | _gtk_gesture_cancel_all (GtkGesture *gesture) |
568 | { |
569 | GdkEventSequence *sequence; |
570 | GtkGesturePrivate *priv; |
571 | GHashTableIter iter; |
572 | |
573 | priv = gtk_gesture_get_instance_private (self: gesture); |
574 | g_hash_table_iter_init (iter: &iter, hash_table: priv->points); |
575 | |
576 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer*) &sequence, NULL)) |
577 | { |
578 | g_signal_emit (instance: gesture, signal_id: signals[CANCEL], detail: 0, sequence); |
579 | g_hash_table_iter_remove (iter: &iter); |
580 | _gtk_gesture_check_recognized (gesture, sequence); |
581 | } |
582 | |
583 | _gtk_gesture_check_empty (gesture); |
584 | } |
585 | |
586 | static gboolean |
587 | gtk_gesture_hold_timeout (gpointer user_data) |
588 | { |
589 | GtkGesture *gesture; |
590 | GtkGesturePrivate *priv; |
591 | |
592 | gesture = user_data; |
593 | priv = gtk_gesture_get_instance_private (self: gesture); |
594 | |
595 | if (priv->touchpad) |
596 | _gtk_gesture_cancel_sequence (gesture, sequence: priv->last_sequence); |
597 | |
598 | priv->hold_timeout_id = 0; |
599 | return G_SOURCE_REMOVE; |
600 | } |
601 | |
602 | static gboolean |
603 | gesture_within_surface (GtkGesture *gesture, |
604 | GdkSurface *surface) |
605 | { |
606 | GtkWidget *widget; |
607 | |
608 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
609 | return surface == gtk_native_get_surface (self: gtk_widget_get_native (widget)); |
610 | } |
611 | |
612 | static gboolean |
613 | gtk_gesture_filter_event (GtkEventController *controller, |
614 | GdkEvent *event) |
615 | { |
616 | /* Even though GtkGesture handles these events, we want |
617 | * touchpad gestures disabled by default, it will be |
618 | * subclasses which punch the holes in for the events |
619 | * they can possibly handle. |
620 | */ |
621 | if (EVENT_IS_TOUCHPAD_GESTURE (event)) |
622 | return FALSE; |
623 | |
624 | return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_parent_class)->filter_event (controller, event); |
625 | } |
626 | |
627 | static gboolean |
628 | gtk_gesture_handle_event (GtkEventController *controller, |
629 | GdkEvent *event, |
630 | double x, |
631 | double y) |
632 | { |
633 | GtkGesture *gesture = GTK_GESTURE (controller); |
634 | GdkEventSequence *sequence; |
635 | GtkGesturePrivate *priv; |
636 | GdkDevice *source_device; |
637 | gboolean was_recognized; |
638 | GdkEventType event_type; |
639 | GdkTouchpadGesturePhase phase = 0; |
640 | GdkModifierType state; |
641 | GtkWidget *target; |
642 | |
643 | source_device = gdk_event_get_device (event); |
644 | |
645 | if (!source_device) |
646 | return FALSE; |
647 | |
648 | priv = gtk_gesture_get_instance_private (self: gesture); |
649 | sequence = gdk_event_get_event_sequence (event); |
650 | was_recognized = gtk_gesture_is_recognized (gesture); |
651 | event_type = gdk_event_get_event_type (event); |
652 | state = gdk_event_get_modifier_state (event); |
653 | if (EVENT_IS_TOUCHPAD_GESTURE (event)) |
654 | phase = gdk_touchpad_event_get_gesture_phase (event); |
655 | |
656 | target = gtk_event_controller_get_target (controller); |
657 | |
658 | if (gtk_gesture_get_sequence_state (gesture, sequence) != GTK_EVENT_SEQUENCE_DENIED) |
659 | priv->last_sequence = sequence; |
660 | |
661 | if (event_type == GDK_BUTTON_PRESS || |
662 | event_type == GDK_TOUCH_BEGIN || |
663 | (event_type == GDK_TOUCHPAD_SWIPE && phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN) || |
664 | (event_type == GDK_TOUCHPAD_PINCH && phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN) || |
665 | (event_type == GDK_TOUCHPAD_HOLD && phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN)) |
666 | { |
667 | if ((event_type == GDK_TOUCHPAD_PINCH || event_type == GDK_TOUCHPAD_SWIPE) && |
668 | _gtk_gesture_has_matching_touchpoints (gesture)) |
669 | g_clear_handle_id (&priv->hold_timeout_id, g_source_remove); |
670 | |
671 | if (_gtk_gesture_update_point (gesture, event, target, x, y, TRUE)) |
672 | { |
673 | gboolean triggered_recognition; |
674 | |
675 | triggered_recognition = |
676 | !was_recognized && _gtk_gesture_has_matching_touchpoints (gesture); |
677 | |
678 | if (_gtk_gesture_check_recognized (gesture, sequence)) |
679 | { |
680 | PointData *data; |
681 | |
682 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
683 | |
684 | /* If the sequence was claimed early, the press event will be consumed */ |
685 | if (gtk_gesture_get_sequence_state (gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED) |
686 | data->press_handled = TRUE; |
687 | } |
688 | else if (triggered_recognition && g_hash_table_size (hash_table: priv->points) == 0) |
689 | { |
690 | /* Recognition was triggered, but the gesture reset during |
691 | * ::begin emission. Still, recognition was strictly triggered, |
692 | * so the event should be consumed. |
693 | */ |
694 | return TRUE; |
695 | } |
696 | } |
697 | } |
698 | else if (event_type == GDK_BUTTON_RELEASE || |
699 | event_type == GDK_TOUCH_END || |
700 | (event_type == GDK_TOUCHPAD_SWIPE && phase == GDK_TOUCHPAD_GESTURE_PHASE_END) || |
701 | (event_type == GDK_TOUCHPAD_PINCH && phase == GDK_TOUCHPAD_GESTURE_PHASE_END) || |
702 | (event_type == GDK_TOUCHPAD_HOLD && phase == GDK_TOUCHPAD_GESTURE_PHASE_END)) |
703 | { |
704 | gboolean was_claimed = FALSE; |
705 | |
706 | if (_gtk_gesture_update_point (gesture, event, target, x, y, FALSE)) |
707 | { |
708 | if (was_recognized && |
709 | _gtk_gesture_check_recognized (gesture, sequence)) |
710 | g_signal_emit (instance: gesture, signal_id: signals[UPDATE], detail: 0, sequence); |
711 | |
712 | was_claimed = |
713 | gtk_gesture_get_sequence_state (gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED; |
714 | |
715 | _gtk_gesture_remove_point (gesture, event); |
716 | } |
717 | |
718 | return was_claimed && was_recognized; |
719 | } |
720 | else if (event_type == GDK_MOTION_NOTIFY || |
721 | event_type == GDK_TOUCH_UPDATE || |
722 | (event_type == GDK_TOUCHPAD_SWIPE && phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE) || |
723 | (event_type == GDK_TOUCHPAD_PINCH && phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)) |
724 | { |
725 | if (event_type == GDK_MOTION_NOTIFY) |
726 | { |
727 | if ((state & BUTTONS_MASK) == 0) |
728 | return FALSE; |
729 | } |
730 | |
731 | if (_gtk_gesture_update_point (gesture, event, target, x, y, FALSE) && |
732 | _gtk_gesture_check_recognized (gesture, sequence)) |
733 | g_signal_emit (instance: gesture, signal_id: signals[UPDATE], detail: 0, sequence); |
734 | } |
735 | else if (event_type == GDK_TOUCH_CANCEL) |
736 | { |
737 | if (!priv->touchpad) |
738 | _gtk_gesture_cancel_sequence (gesture, sequence); |
739 | } |
740 | else if ((event_type == GDK_TOUCHPAD_SWIPE && phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL) || |
741 | (event_type == GDK_TOUCHPAD_PINCH && phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL)) |
742 | { |
743 | if (priv->touchpad) |
744 | _gtk_gesture_cancel_sequence (gesture, sequence); |
745 | } |
746 | else if (event_type == GDK_TOUCHPAD_HOLD && phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL) |
747 | { |
748 | if (priv->hold_timeout_id == 0) |
749 | { |
750 | priv->hold_timeout_id = g_timeout_add (HOLD_TIMEOUT_MS, |
751 | function: gtk_gesture_hold_timeout, |
752 | data: gesture); |
753 | } |
754 | } |
755 | else if (event_type == GDK_GRAB_BROKEN) |
756 | { |
757 | GdkSurface *surface; |
758 | |
759 | surface = gdk_grab_broken_event_get_grab_surface (event); |
760 | if (!surface || !gesture_within_surface (gesture, surface)) |
761 | _gtk_gesture_cancel_all (gesture); |
762 | |
763 | return FALSE; |
764 | } |
765 | else |
766 | { |
767 | /* Unhandled event */ |
768 | return FALSE; |
769 | } |
770 | |
771 | if (gtk_gesture_get_sequence_state (gesture, sequence) != GTK_EVENT_SEQUENCE_CLAIMED) |
772 | return FALSE; |
773 | |
774 | return priv->recognized; |
775 | } |
776 | |
777 | static void |
778 | gtk_gesture_reset (GtkEventController *controller) |
779 | { |
780 | GtkGesture *gesture = GTK_GESTURE (controller); |
781 | GtkGesturePrivate *priv = gtk_gesture_get_instance_private (self: gesture); |
782 | |
783 | g_clear_handle_id (&priv->hold_timeout_id, g_source_remove); |
784 | _gtk_gesture_cancel_all (GTK_GESTURE (controller)); |
785 | } |
786 | |
787 | static void |
788 | gtk_gesture_class_init (GtkGestureClass *klass) |
789 | { |
790 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
791 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
792 | |
793 | object_class->get_property = gtk_gesture_get_property; |
794 | object_class->set_property = gtk_gesture_set_property; |
795 | object_class->finalize = gtk_gesture_finalize; |
796 | |
797 | controller_class->filter_event = gtk_gesture_filter_event; |
798 | controller_class->handle_event = gtk_gesture_handle_event; |
799 | controller_class->reset = gtk_gesture_reset; |
800 | |
801 | klass->check = gtk_gesture_check_impl; |
802 | |
803 | /** |
804 | * GtkGesture:n-points: |
805 | * |
806 | * The number of touch points that trigger |
807 | * recognition on this gesture. |
808 | */ |
809 | g_object_class_install_property (oclass: object_class, |
810 | property_id: PROP_N_POINTS, |
811 | pspec: g_param_spec_uint (name: "n-points" , |
812 | P_("Number of points" ), |
813 | P_("Number of points needed " |
814 | "to trigger the gesture" ), |
815 | minimum: 1, G_MAXUINT, default_value: 1, |
816 | GTK_PARAM_READWRITE | |
817 | G_PARAM_CONSTRUCT_ONLY)); |
818 | /** |
819 | * GtkGesture::begin: |
820 | * @gesture: the object which received the signal |
821 | * @sequence: (nullable): the `GdkEventSequence` that made the gesture |
822 | * to be recognized |
823 | * |
824 | * Emitted when the gesture is recognized. |
825 | * |
826 | * This means the number of touch sequences matches |
827 | * [property@Gtk.Gesture:n-points]. |
828 | * |
829 | * Note: These conditions may also happen when an extra touch |
830 | * (eg. a third touch on a 2-touches gesture) is lifted, in that |
831 | * situation @sequence won't pertain to the current set of active |
832 | * touches, so don't rely on this being true. |
833 | */ |
834 | signals[BEGIN] = |
835 | g_signal_new (I_("begin" ), |
836 | G_TYPE_FROM_CLASS (klass), |
837 | signal_flags: G_SIGNAL_RUN_LAST, |
838 | G_STRUCT_OFFSET (GtkGestureClass, begin), |
839 | NULL, NULL, NULL, |
840 | G_TYPE_NONE, n_params: 1, GDK_TYPE_EVENT_SEQUENCE); |
841 | |
842 | /** |
843 | * GtkGesture::end: |
844 | * @gesture: the object which received the signal |
845 | * @sequence: (nullable): the `GdkEventSequence` that made gesture |
846 | * recognition to finish |
847 | * |
848 | * Emitted when @gesture either stopped recognizing the event |
849 | * sequences as something to be handled, or the number of touch |
850 | * sequences became higher or lower than [property@Gtk.Gesture:n-points]. |
851 | * |
852 | * Note: @sequence might not pertain to the group of sequences that |
853 | * were previously triggering recognition on @gesture (ie. a just |
854 | * pressed touch sequence that exceeds [property@Gtk.Gesture:n-points]). |
855 | * This situation may be detected by checking through |
856 | * [method@Gtk.Gesture.handles_sequence]. |
857 | */ |
858 | signals[END] = |
859 | g_signal_new (I_("end" ), |
860 | G_TYPE_FROM_CLASS (klass), |
861 | signal_flags: G_SIGNAL_RUN_LAST, |
862 | G_STRUCT_OFFSET (GtkGestureClass, end), |
863 | NULL, NULL, NULL, |
864 | G_TYPE_NONE, n_params: 1, GDK_TYPE_EVENT_SEQUENCE); |
865 | |
866 | /** |
867 | * GtkGesture::update: |
868 | * @gesture: the object which received the signal |
869 | * @sequence: (nullable): the `GdkEventSequence` that was updated |
870 | * |
871 | * Emitted whenever an event is handled while the gesture is recognized. |
872 | * |
873 | * @sequence is guaranteed to pertain to the set of active touches. |
874 | */ |
875 | signals[UPDATE] = |
876 | g_signal_new (I_("update" ), |
877 | G_TYPE_FROM_CLASS (klass), |
878 | signal_flags: G_SIGNAL_RUN_LAST, |
879 | G_STRUCT_OFFSET (GtkGestureClass, update), |
880 | NULL, NULL, NULL, |
881 | G_TYPE_NONE, n_params: 1, GDK_TYPE_EVENT_SEQUENCE); |
882 | |
883 | /** |
884 | * GtkGesture::cancel: |
885 | * @gesture: the object which received the signal |
886 | * @sequence: (nullable): the `GdkEventSequence` that was cancelled |
887 | * |
888 | * Emitted whenever a sequence is cancelled. |
889 | * |
890 | * This usually happens on active touches when |
891 | * [method@Gtk.EventController.reset] is called on @gesture |
892 | * (manually, due to grabs...), or the individual @sequence |
893 | * was claimed by parent widgets' controllers (see |
894 | * [method@Gtk.Gesture.set_sequence_state]). |
895 | * |
896 | * @gesture must forget everything about @sequence as in |
897 | * response to this signal. |
898 | */ |
899 | signals[CANCEL] = |
900 | g_signal_new (I_("cancel" ), |
901 | G_TYPE_FROM_CLASS (klass), |
902 | signal_flags: G_SIGNAL_RUN_LAST, |
903 | G_STRUCT_OFFSET (GtkGestureClass, cancel), |
904 | NULL, NULL, NULL, |
905 | G_TYPE_NONE, n_params: 1, GDK_TYPE_EVENT_SEQUENCE); |
906 | |
907 | /** |
908 | * GtkGesture::sequence-state-changed: |
909 | * @gesture: the object which received the signal |
910 | * @sequence: (nullable): the `GdkEventSequence` that was cancelled |
911 | * @state: the new sequence state |
912 | * |
913 | * Emitted whenever a sequence state changes. |
914 | * |
915 | * See [method@Gtk.Gesture.set_sequence_state] to know |
916 | * more about the expectable sequence lifetimes. |
917 | */ |
918 | signals[SEQUENCE_STATE_CHANGED] = |
919 | g_signal_new (I_("sequence-state-changed" ), |
920 | G_TYPE_FROM_CLASS (klass), |
921 | signal_flags: G_SIGNAL_RUN_LAST, |
922 | G_STRUCT_OFFSET (GtkGestureClass, sequence_state_changed), |
923 | NULL, NULL, |
924 | c_marshaller: _gtk_marshal_VOID__BOXED_ENUM, |
925 | G_TYPE_NONE, n_params: 2, GDK_TYPE_EVENT_SEQUENCE, |
926 | GTK_TYPE_EVENT_SEQUENCE_STATE); |
927 | g_signal_set_va_marshaller (signal_id: signals[SEQUENCE_STATE_CHANGED], |
928 | G_TYPE_FROM_CLASS (klass), |
929 | va_marshaller: _gtk_marshal_VOID__BOXED_ENUMv); |
930 | } |
931 | |
932 | static void |
933 | free_point_data (gpointer data) |
934 | { |
935 | PointData *point = data; |
936 | |
937 | if (point->event) |
938 | gdk_event_unref (event: point->event); |
939 | |
940 | if (point->target) |
941 | g_object_unref (object: point->target); |
942 | |
943 | g_free (mem: point); |
944 | } |
945 | |
946 | static void |
947 | gtk_gesture_init (GtkGesture *gesture) |
948 | { |
949 | GtkGesturePrivate *priv; |
950 | |
951 | priv = gtk_gesture_get_instance_private (self: gesture); |
952 | priv->points = g_hash_table_new_full (NULL, NULL, NULL, |
953 | value_destroy_func: (GDestroyNotify) free_point_data); |
954 | priv->group_link = g_list_prepend (NULL, data: gesture); |
955 | priv->hold_timeout_id = 0; |
956 | } |
957 | |
958 | /** |
959 | * gtk_gesture_get_device: |
960 | * @gesture: a `GtkGesture` |
961 | * |
962 | * Returns the logical `GdkDevice` that is currently operating |
963 | * on @gesture. |
964 | * |
965 | * This returns %NULL if the gesture is not being interacted. |
966 | * |
967 | * Returns: (nullable) (transfer none): a `GdkDevice` |
968 | */ |
969 | GdkDevice * |
970 | gtk_gesture_get_device (GtkGesture *gesture) |
971 | { |
972 | GtkGesturePrivate *priv; |
973 | |
974 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
975 | |
976 | priv = gtk_gesture_get_instance_private (self: gesture); |
977 | |
978 | return priv->device; |
979 | } |
980 | |
981 | /** |
982 | * gtk_gesture_get_sequence_state: |
983 | * @gesture: a `GtkGesture` |
984 | * @sequence: a `GdkEventSequence` |
985 | * |
986 | * Returns the @sequence state, as seen by @gesture. |
987 | * |
988 | * Returns: The sequence state in @gesture |
989 | */ |
990 | GtkEventSequenceState |
991 | gtk_gesture_get_sequence_state (GtkGesture *gesture, |
992 | GdkEventSequence *sequence) |
993 | { |
994 | GtkGesturePrivate *priv; |
995 | PointData *data; |
996 | |
997 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), |
998 | GTK_EVENT_SEQUENCE_NONE); |
999 | |
1000 | priv = gtk_gesture_get_instance_private (self: gesture); |
1001 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1002 | |
1003 | if (!data) |
1004 | return GTK_EVENT_SEQUENCE_NONE; |
1005 | |
1006 | return data->state; |
1007 | } |
1008 | |
1009 | /** |
1010 | * gtk_gesture_set_sequence_state: |
1011 | * @gesture: a `GtkGesture` |
1012 | * @sequence: a `GdkEventSequence` |
1013 | * @state: the sequence state |
1014 | * |
1015 | * Sets the state of @sequence in @gesture. |
1016 | * |
1017 | * Sequences start in state %GTK_EVENT_SEQUENCE_NONE, and whenever |
1018 | * they change state, they can never go back to that state. Likewise, |
1019 | * sequences in state %GTK_EVENT_SEQUENCE_DENIED cannot turn back to |
1020 | * a not denied state. With these rules, the lifetime of an event |
1021 | * sequence is constrained to the next four: |
1022 | * |
1023 | * * None |
1024 | * * None → Denied |
1025 | * * None → Claimed |
1026 | * * None → Claimed → Denied |
1027 | * |
1028 | * Note: Due to event handling ordering, it may be unsafe to set the |
1029 | * state on another gesture within a [signal@Gtk.Gesture::begin] signal |
1030 | * handler, as the callback might be executed before the other gesture |
1031 | * knows about the sequence. A safe way to perform this could be: |
1032 | * |
1033 | * ```c |
1034 | * static void |
1035 | * first_gesture_begin_cb (GtkGesture *first_gesture, |
1036 | * GdkEventSequence *sequence, |
1037 | * gpointer user_data) |
1038 | * { |
1039 | * gtk_gesture_set_sequence_state (first_gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED); |
1040 | * gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED); |
1041 | * } |
1042 | * |
1043 | * static void |
1044 | * second_gesture_begin_cb (GtkGesture *second_gesture, |
1045 | * GdkEventSequence *sequence, |
1046 | * gpointer user_data) |
1047 | * { |
1048 | * if (gtk_gesture_get_sequence_state (first_gesture, sequence) == GTK_EVENT_SEQUENCE_CLAIMED) |
1049 | * gtk_gesture_set_sequence_state (second_gesture, sequence, GTK_EVENT_SEQUENCE_DENIED); |
1050 | * } |
1051 | * ``` |
1052 | * |
1053 | * If both gestures are in the same group, just set the state on |
1054 | * the gesture emitting the event, the sequence will be already |
1055 | * be initialized to the group's global state when the second |
1056 | * gesture processes the event. |
1057 | * |
1058 | * Returns: %TRUE if @sequence is handled by @gesture, |
1059 | * and the state is changed successfully |
1060 | */ |
1061 | gboolean |
1062 | gtk_gesture_set_sequence_state (GtkGesture *gesture, |
1063 | GdkEventSequence *sequence, |
1064 | GtkEventSequenceState state) |
1065 | { |
1066 | GtkGesturePrivate *priv; |
1067 | PointData *data; |
1068 | |
1069 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1070 | g_return_val_if_fail (state >= GTK_EVENT_SEQUENCE_NONE && |
1071 | state <= GTK_EVENT_SEQUENCE_DENIED, FALSE); |
1072 | |
1073 | priv = gtk_gesture_get_instance_private (self: gesture); |
1074 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1075 | |
1076 | if (!data) |
1077 | return FALSE; |
1078 | |
1079 | if (data->state == state) |
1080 | return FALSE; |
1081 | |
1082 | /* denied sequences remain denied */ |
1083 | if (data->state == GTK_EVENT_SEQUENCE_DENIED) |
1084 | return FALSE; |
1085 | |
1086 | /* Sequences can't go from claimed/denied to none */ |
1087 | if (state == GTK_EVENT_SEQUENCE_NONE && |
1088 | data->state != GTK_EVENT_SEQUENCE_NONE) |
1089 | return FALSE; |
1090 | |
1091 | data->state = state; |
1092 | |
1093 | gtk_widget_cancel_event_sequence (widget: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)), |
1094 | gesture, sequence, state); |
1095 | g_signal_emit (instance: gesture, signal_id: signals[SEQUENCE_STATE_CHANGED], detail: 0, |
1096 | sequence, state); |
1097 | |
1098 | if (state == GTK_EVENT_SEQUENCE_DENIED) |
1099 | _gtk_gesture_check_recognized (gesture, sequence); |
1100 | |
1101 | return TRUE; |
1102 | } |
1103 | |
1104 | /** |
1105 | * gtk_gesture_set_state: |
1106 | * @gesture: a `GtkGesture` |
1107 | * @state: the sequence state |
1108 | * |
1109 | * Sets the state of all sequences that @gesture is currently |
1110 | * interacting with. |
1111 | * |
1112 | * See [method@Gtk.Gesture.set_sequence_state] for more details |
1113 | * on sequence states. |
1114 | * |
1115 | * Returns: %TRUE if the state of at least one sequence |
1116 | * was changed successfully |
1117 | */ |
1118 | gboolean |
1119 | gtk_gesture_set_state (GtkGesture *gesture, |
1120 | GtkEventSequenceState state) |
1121 | { |
1122 | gboolean handled = FALSE; |
1123 | GtkGesturePrivate *priv; |
1124 | GList *sequences, *l; |
1125 | |
1126 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1127 | g_return_val_if_fail (state >= GTK_EVENT_SEQUENCE_NONE && |
1128 | state <= GTK_EVENT_SEQUENCE_DENIED, FALSE); |
1129 | |
1130 | priv = gtk_gesture_get_instance_private (self: gesture); |
1131 | sequences = g_hash_table_get_keys (hash_table: priv->points); |
1132 | |
1133 | for (l = sequences; l; l = l->next) |
1134 | handled |= gtk_gesture_set_sequence_state (gesture, sequence: l->data, state); |
1135 | |
1136 | g_list_free (list: sequences); |
1137 | |
1138 | return handled; |
1139 | } |
1140 | |
1141 | /** |
1142 | * gtk_gesture_get_sequences: |
1143 | * @gesture: a `GtkGesture` |
1144 | * |
1145 | * Returns the list of `GdkEventSequences` currently being interpreted |
1146 | * by @gesture. |
1147 | * |
1148 | * Returns: (transfer container) (element-type GdkEventSequence): A list |
1149 | * of `GdkEventSequence`, the list elements are owned by GTK and must |
1150 | * not be freed or modified, the list itself must be deleted |
1151 | * through g_list_free() |
1152 | */ |
1153 | GList * |
1154 | gtk_gesture_get_sequences (GtkGesture *gesture) |
1155 | { |
1156 | GdkEventSequence *sequence; |
1157 | GtkGesturePrivate *priv; |
1158 | GList *sequences = NULL; |
1159 | GHashTableIter iter; |
1160 | PointData *data; |
1161 | GdkEventType event_type; |
1162 | |
1163 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
1164 | |
1165 | priv = gtk_gesture_get_instance_private (self: gesture); |
1166 | g_hash_table_iter_init (iter: &iter, hash_table: priv->points); |
1167 | |
1168 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer *) &sequence, value: (gpointer *) &data)) |
1169 | { |
1170 | if (data->state == GTK_EVENT_SEQUENCE_DENIED) |
1171 | continue; |
1172 | |
1173 | event_type = gdk_event_get_event_type (event: data->event); |
1174 | |
1175 | if (event_type == GDK_TOUCH_END || |
1176 | event_type == GDK_BUTTON_RELEASE) |
1177 | continue; |
1178 | |
1179 | sequences = g_list_prepend (list: sequences, data: sequence); |
1180 | } |
1181 | |
1182 | return sequences; |
1183 | } |
1184 | |
1185 | /** |
1186 | * gtk_gesture_get_last_updated_sequence: |
1187 | * @gesture: a `GtkGesture` |
1188 | * |
1189 | * Returns the `GdkEventSequence` that was last updated on @gesture. |
1190 | * |
1191 | * Returns: (transfer none) (nullable): The last updated sequence |
1192 | */ |
1193 | GdkEventSequence * |
1194 | gtk_gesture_get_last_updated_sequence (GtkGesture *gesture) |
1195 | { |
1196 | GtkGesturePrivate *priv; |
1197 | |
1198 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
1199 | |
1200 | priv = gtk_gesture_get_instance_private (self: gesture); |
1201 | |
1202 | return priv->last_sequence; |
1203 | } |
1204 | |
1205 | /** |
1206 | * gtk_gesture_get_last_event: |
1207 | * @gesture: a `GtkGesture` |
1208 | * @sequence: (nullable): a `GdkEventSequence` |
1209 | * |
1210 | * Returns the last event that was processed for @sequence. |
1211 | * |
1212 | * Note that the returned pointer is only valid as long as the |
1213 | * @sequence is still interpreted by the @gesture. If in doubt, |
1214 | * you should make a copy of the event. |
1215 | * |
1216 | * Returns: (transfer none) (nullable): The last event from @sequence |
1217 | */ |
1218 | GdkEvent * |
1219 | gtk_gesture_get_last_event (GtkGesture *gesture, |
1220 | GdkEventSequence *sequence) |
1221 | { |
1222 | GtkGesturePrivate *priv; |
1223 | PointData *data; |
1224 | |
1225 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
1226 | |
1227 | priv = gtk_gesture_get_instance_private (self: gesture); |
1228 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1229 | |
1230 | if (!data) |
1231 | return NULL; |
1232 | |
1233 | return data->event; |
1234 | } |
1235 | |
1236 | /* |
1237 | * gtk_gesture_get_last_target: |
1238 | * @gesture: a `GtkGesture` |
1239 | * @sequence: event sequence |
1240 | * |
1241 | * Returns the widget that the last event was targeted at. |
1242 | * |
1243 | * See [method@Gtk.Gesture.get_last_event]. |
1244 | * |
1245 | * Returns: (transfer none) (nullable): The target of the last event |
1246 | */ |
1247 | GtkWidget * |
1248 | gtk_gesture_get_last_target (GtkGesture *gesture, |
1249 | GdkEventSequence *sequence) |
1250 | { |
1251 | GtkGesturePrivate *priv; |
1252 | PointData *data; |
1253 | |
1254 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
1255 | |
1256 | priv = gtk_gesture_get_instance_private (self: gesture); |
1257 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1258 | |
1259 | if (!data) |
1260 | return NULL; |
1261 | |
1262 | return data->target; |
1263 | } |
1264 | |
1265 | /** |
1266 | * gtk_gesture_get_point: |
1267 | * @gesture: a `GtkGesture` |
1268 | * @sequence: (nullable): a `GdkEventSequence`, or %NULL for pointer events |
1269 | * @x: (out) (optional): return location for X axis of the sequence coordinates |
1270 | * @y: (out) (optional): return location for Y axis of the sequence coordinates |
1271 | * |
1272 | * If @sequence is currently being interpreted by @gesture, |
1273 | * returns %TRUE and fills in @x and @y with the last coordinates |
1274 | * stored for that event sequence. |
1275 | * |
1276 | * The coordinates are always relative to the widget allocation. |
1277 | * |
1278 | * Returns: %TRUE if @sequence is currently interpreted |
1279 | */ |
1280 | gboolean |
1281 | gtk_gesture_get_point (GtkGesture *gesture, |
1282 | GdkEventSequence *sequence, |
1283 | double *x, |
1284 | double *y) |
1285 | { |
1286 | GtkGesturePrivate *priv; |
1287 | PointData *data; |
1288 | |
1289 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1290 | |
1291 | priv = gtk_gesture_get_instance_private (self: gesture); |
1292 | |
1293 | if (!g_hash_table_lookup_extended (hash_table: priv->points, lookup_key: sequence, |
1294 | NULL, value: (gpointer *) &data)) |
1295 | return FALSE; |
1296 | |
1297 | if (x) |
1298 | *x = data->widget_x; |
1299 | if (y) |
1300 | *y = data->widget_y; |
1301 | |
1302 | return TRUE; |
1303 | } |
1304 | |
1305 | gboolean |
1306 | _gtk_gesture_get_last_update_time (GtkGesture *gesture, |
1307 | GdkEventSequence *sequence, |
1308 | guint32 *evtime) |
1309 | { |
1310 | GtkGesturePrivate *priv; |
1311 | PointData *data; |
1312 | |
1313 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1314 | |
1315 | priv = gtk_gesture_get_instance_private (self: gesture); |
1316 | |
1317 | if (!g_hash_table_lookup_extended (hash_table: priv->points, lookup_key: sequence, |
1318 | NULL, value: (gpointer *) &data)) |
1319 | return FALSE; |
1320 | |
1321 | if (evtime) |
1322 | *evtime = gdk_event_get_time (event: data->event); |
1323 | |
1324 | return TRUE; |
1325 | }; |
1326 | |
1327 | /** |
1328 | * gtk_gesture_get_bounding_box: |
1329 | * @gesture: a `GtkGesture` |
1330 | * @rect: (out): bounding box containing all active touches. |
1331 | * |
1332 | * If there are touch sequences being currently handled by @gesture, |
1333 | * returns %TRUE and fills in @rect with the bounding box containing |
1334 | * all active touches. |
1335 | * |
1336 | * Otherwise, %FALSE will be returned. |
1337 | * |
1338 | * Note: This function will yield unexpected results on touchpad |
1339 | * gestures. Since there is no correlation between physical and |
1340 | * pixel distances, these will look as if constrained in an |
1341 | * infinitely small area, @rect width and height will thus be 0 |
1342 | * regardless of the number of touchpoints. |
1343 | * |
1344 | * Returns: %TRUE if there are active touches, %FALSE otherwise |
1345 | */ |
1346 | gboolean |
1347 | gtk_gesture_get_bounding_box (GtkGesture *gesture, |
1348 | GdkRectangle *rect) |
1349 | { |
1350 | GtkGesturePrivate *priv; |
1351 | double x1, y1, x2, y2; |
1352 | GHashTableIter iter; |
1353 | guint n_points = 0; |
1354 | PointData *data; |
1355 | GdkEventType event_type; |
1356 | |
1357 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1358 | g_return_val_if_fail (rect != NULL, FALSE); |
1359 | |
1360 | priv = gtk_gesture_get_instance_private (self: gesture); |
1361 | x1 = y1 = G_MAXDOUBLE; |
1362 | x2 = y2 = -G_MAXDOUBLE; |
1363 | |
1364 | g_hash_table_iter_init (iter: &iter, hash_table: priv->points); |
1365 | |
1366 | while (g_hash_table_iter_next (iter: &iter, NULL, value: (gpointer *) &data)) |
1367 | { |
1368 | if (data->state == GTK_EVENT_SEQUENCE_DENIED) |
1369 | continue; |
1370 | |
1371 | event_type = gdk_event_get_event_type (event: data->event); |
1372 | |
1373 | if (event_type == GDK_TOUCH_END || |
1374 | event_type == GDK_BUTTON_RELEASE) |
1375 | continue; |
1376 | |
1377 | n_points++; |
1378 | x1 = MIN (x1, data->widget_x); |
1379 | y1 = MIN (y1, data->widget_y); |
1380 | x2 = MAX (x2, data->widget_x); |
1381 | y2 = MAX (y2, data->widget_y); |
1382 | } |
1383 | |
1384 | if (n_points == 0) |
1385 | return FALSE; |
1386 | |
1387 | rect->x = x1; |
1388 | rect->y = y1; |
1389 | rect->width = x2 - x1; |
1390 | rect->height = y2 - y1; |
1391 | |
1392 | return TRUE; |
1393 | } |
1394 | |
1395 | |
1396 | /** |
1397 | * gtk_gesture_get_bounding_box_center: |
1398 | * @gesture: a `GtkGesture` |
1399 | * @x: (out): X coordinate for the bounding box center |
1400 | * @y: (out): Y coordinate for the bounding box center |
1401 | * |
1402 | * If there are touch sequences being currently handled by @gesture, |
1403 | * returns %TRUE and fills in @x and @y with the center of the bounding |
1404 | * box containing all active touches. |
1405 | * |
1406 | * Otherwise, %FALSE will be returned. |
1407 | * |
1408 | * Returns: %FALSE if no active touches are present, %TRUE otherwise |
1409 | */ |
1410 | gboolean |
1411 | gtk_gesture_get_bounding_box_center (GtkGesture *gesture, |
1412 | double *x, |
1413 | double *y) |
1414 | { |
1415 | GdkEvent *last_event; |
1416 | GdkRectangle rect; |
1417 | GdkEventSequence *sequence; |
1418 | |
1419 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1420 | g_return_val_if_fail (x != NULL && y != NULL, FALSE); |
1421 | |
1422 | sequence = gtk_gesture_get_last_updated_sequence (gesture); |
1423 | last_event = gtk_gesture_get_last_event (gesture, sequence); |
1424 | |
1425 | if (EVENT_IS_TOUCHPAD_GESTURE (last_event)) |
1426 | return gtk_gesture_get_point (gesture, sequence, x, y); |
1427 | else if (!gtk_gesture_get_bounding_box (gesture, rect: &rect)) |
1428 | return FALSE; |
1429 | |
1430 | *x = rect.x + rect.width / 2; |
1431 | *y = rect.y + rect.height / 2; |
1432 | return TRUE; |
1433 | } |
1434 | |
1435 | /** |
1436 | * gtk_gesture_is_active: |
1437 | * @gesture: a `GtkGesture` |
1438 | * |
1439 | * Returns %TRUE if the gesture is currently active. |
1440 | * |
1441 | * A gesture is active while there are touch sequences |
1442 | * interacting with it. |
1443 | * |
1444 | * Returns: %TRUE if gesture is active |
1445 | */ |
1446 | gboolean |
1447 | gtk_gesture_is_active (GtkGesture *gesture) |
1448 | { |
1449 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1450 | |
1451 | return _gtk_gesture_get_n_physical_points (gesture, TRUE) != 0; |
1452 | } |
1453 | |
1454 | /** |
1455 | * gtk_gesture_is_recognized: |
1456 | * @gesture: a `GtkGesture` |
1457 | * |
1458 | * Returns %TRUE if the gesture is currently recognized. |
1459 | * |
1460 | * A gesture is recognized if there are as many interacting |
1461 | * touch sequences as required by @gesture. |
1462 | * |
1463 | * Returns: %TRUE if gesture is recognized |
1464 | */ |
1465 | gboolean |
1466 | gtk_gesture_is_recognized (GtkGesture *gesture) |
1467 | { |
1468 | GtkGesturePrivate *priv; |
1469 | |
1470 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1471 | |
1472 | priv = gtk_gesture_get_instance_private (self: gesture); |
1473 | |
1474 | return priv->recognized; |
1475 | } |
1476 | |
1477 | gboolean |
1478 | _gtk_gesture_check (GtkGesture *gesture) |
1479 | { |
1480 | GtkGesturePrivate *priv; |
1481 | |
1482 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1483 | |
1484 | priv = gtk_gesture_get_instance_private (self: gesture); |
1485 | |
1486 | return _gtk_gesture_check_recognized (gesture, sequence: priv->last_sequence); |
1487 | } |
1488 | |
1489 | /** |
1490 | * gtk_gesture_handles_sequence: |
1491 | * @gesture: a `GtkGesture` |
1492 | * @sequence: (nullable): a `GdkEventSequence` |
1493 | * |
1494 | * Returns %TRUE if @gesture is currently handling events |
1495 | * corresponding to @sequence. |
1496 | * |
1497 | * Returns: %TRUE if @gesture is handling @sequence, %FALSE otherwise |
1498 | */ |
1499 | gboolean |
1500 | gtk_gesture_handles_sequence (GtkGesture *gesture, |
1501 | GdkEventSequence *sequence) |
1502 | { |
1503 | GtkGesturePrivate *priv; |
1504 | PointData *data; |
1505 | |
1506 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1507 | |
1508 | priv = gtk_gesture_get_instance_private (self: gesture); |
1509 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1510 | |
1511 | if (!data) |
1512 | return FALSE; |
1513 | |
1514 | if (data->state == GTK_EVENT_SEQUENCE_DENIED) |
1515 | return FALSE; |
1516 | |
1517 | return TRUE; |
1518 | } |
1519 | |
1520 | gboolean |
1521 | _gtk_gesture_cancel_sequence (GtkGesture *gesture, |
1522 | GdkEventSequence *sequence) |
1523 | { |
1524 | GtkGesturePrivate *priv; |
1525 | PointData *data; |
1526 | |
1527 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1528 | |
1529 | priv = gtk_gesture_get_instance_private (self: gesture); |
1530 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1531 | |
1532 | if (!data) |
1533 | return FALSE; |
1534 | |
1535 | g_signal_emit (instance: gesture, signal_id: signals[CANCEL], detail: 0, sequence); |
1536 | _gtk_gesture_remove_point (gesture, event: data->event); |
1537 | _gtk_gesture_check_recognized (gesture, sequence); |
1538 | |
1539 | return TRUE; |
1540 | } |
1541 | |
1542 | GList * |
1543 | _gtk_gesture_get_group_link (GtkGesture *gesture) |
1544 | { |
1545 | GtkGesturePrivate *priv; |
1546 | |
1547 | priv = gtk_gesture_get_instance_private (self: gesture); |
1548 | |
1549 | return priv->group_link; |
1550 | } |
1551 | |
1552 | /** |
1553 | * gtk_gesture_group: |
1554 | * @gesture: a `GtkGesture` |
1555 | * @group_gesture: `GtkGesture` to group @gesture with |
1556 | * |
1557 | * Adds @gesture to the same group than @group_gesture. |
1558 | * |
1559 | * Gestures are by default isolated in their own groups. |
1560 | * |
1561 | * Both gestures must have been added to the same widget before |
1562 | * they can be grouped. |
1563 | * |
1564 | * When gestures are grouped, the state of `GdkEventSequences` |
1565 | * is kept in sync for all of those, so calling |
1566 | * [method@Gtk.Gesture.set_sequence_state], on one will transfer |
1567 | * the same value to the others. |
1568 | * |
1569 | * Groups also perform an "implicit grabbing" of sequences, if a |
1570 | * `GdkEventSequence` state is set to %GTK_EVENT_SEQUENCE_CLAIMED |
1571 | * on one group, every other gesture group attached to the same |
1572 | * `GtkWidget` will switch the state for that sequence to |
1573 | * %GTK_EVENT_SEQUENCE_DENIED. |
1574 | */ |
1575 | void |
1576 | gtk_gesture_group (GtkGesture *gesture, |
1577 | GtkGesture *group_gesture) |
1578 | { |
1579 | GList *link, *group_link, *next; |
1580 | |
1581 | g_return_if_fail (GTK_IS_GESTURE (gesture)); |
1582 | g_return_if_fail (GTK_IS_GESTURE (group_gesture)); |
1583 | g_return_if_fail (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (group_gesture)) == |
1584 | gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture))); |
1585 | |
1586 | link = _gtk_gesture_get_group_link (gesture); |
1587 | |
1588 | if (link->prev || link->next) |
1589 | { |
1590 | if (gtk_gesture_is_grouped_with (gesture, other: group_gesture)) |
1591 | return; |
1592 | gtk_gesture_ungroup (gesture); |
1593 | } |
1594 | |
1595 | group_link = _gtk_gesture_get_group_link (gesture: group_gesture); |
1596 | next = group_link->next; |
1597 | |
1598 | /* Rewire link so it's inserted after the group_gesture elem */ |
1599 | link->prev = group_link; |
1600 | link->next = next; |
1601 | group_link->next = link; |
1602 | if (next) |
1603 | next->prev = link; |
1604 | } |
1605 | |
1606 | /** |
1607 | * gtk_gesture_ungroup: |
1608 | * @gesture: a `GtkGesture` |
1609 | * |
1610 | * Separates @gesture into an isolated group. |
1611 | */ |
1612 | void |
1613 | gtk_gesture_ungroup (GtkGesture *gesture) |
1614 | { |
1615 | GList *link, *prev, *next; |
1616 | |
1617 | g_return_if_fail (GTK_IS_GESTURE (gesture)); |
1618 | |
1619 | link = _gtk_gesture_get_group_link (gesture); |
1620 | prev = link->prev; |
1621 | next = link->next; |
1622 | |
1623 | /* Detach link from the group chain */ |
1624 | if (prev) |
1625 | prev->next = next; |
1626 | if (next) |
1627 | next->prev = prev; |
1628 | |
1629 | link->next = link->prev = NULL; |
1630 | } |
1631 | |
1632 | /** |
1633 | * gtk_gesture_get_group: |
1634 | * @gesture: a `GtkGesture` |
1635 | * |
1636 | * Returns all gestures in the group of @gesture |
1637 | * |
1638 | * Returns: (element-type GtkGesture) (transfer container): The list |
1639 | * of `GtkGesture`s, free with g_list_free() |
1640 | */ |
1641 | GList * |
1642 | gtk_gesture_get_group (GtkGesture *gesture) |
1643 | { |
1644 | GList *link; |
1645 | |
1646 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), NULL); |
1647 | |
1648 | link = _gtk_gesture_get_group_link (gesture); |
1649 | |
1650 | return g_list_copy (list: g_list_first (list: link)); |
1651 | } |
1652 | |
1653 | /** |
1654 | * gtk_gesture_is_grouped_with: |
1655 | * @gesture: a `GtkGesture` |
1656 | * @other: another `GtkGesture` |
1657 | * |
1658 | * Returns %TRUE if both gestures pertain to the same group. |
1659 | * |
1660 | * Returns: whether the gestures are grouped |
1661 | */ |
1662 | gboolean |
1663 | gtk_gesture_is_grouped_with (GtkGesture *gesture, |
1664 | GtkGesture *other) |
1665 | { |
1666 | GList *link; |
1667 | |
1668 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1669 | g_return_val_if_fail (GTK_IS_GESTURE (other), FALSE); |
1670 | |
1671 | link = _gtk_gesture_get_group_link (gesture); |
1672 | link = g_list_first (list: link); |
1673 | |
1674 | return g_list_find (list: link, data: other) != NULL; |
1675 | } |
1676 | |
1677 | gboolean |
1678 | _gtk_gesture_handled_sequence_press (GtkGesture *gesture, |
1679 | GdkEventSequence *sequence) |
1680 | { |
1681 | GtkGesturePrivate *priv; |
1682 | PointData *data; |
1683 | |
1684 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1685 | |
1686 | priv = gtk_gesture_get_instance_private (self: gesture); |
1687 | data = g_hash_table_lookup (hash_table: priv->points, key: sequence); |
1688 | |
1689 | if (!data) |
1690 | return FALSE; |
1691 | |
1692 | return data->press_handled; |
1693 | } |
1694 | |
1695 | gboolean |
1696 | _gtk_gesture_get_pointer_emulating_sequence (GtkGesture *gesture, |
1697 | GdkEventSequence **sequence) |
1698 | { |
1699 | GtkGesturePrivate *priv; |
1700 | GdkEventSequence *seq; |
1701 | GHashTableIter iter; |
1702 | PointData *data; |
1703 | |
1704 | g_return_val_if_fail (GTK_IS_GESTURE (gesture), FALSE); |
1705 | |
1706 | priv = gtk_gesture_get_instance_private (self: gesture); |
1707 | g_hash_table_iter_init (iter: &iter, hash_table: priv->points); |
1708 | |
1709 | while (g_hash_table_iter_next (iter: &iter, key: (gpointer*) &seq, value: (gpointer*) &data)) |
1710 | { |
1711 | switch ((guint) gdk_event_get_event_type (event: data->event)) |
1712 | { |
1713 | case GDK_TOUCH_BEGIN: |
1714 | case GDK_TOUCH_UPDATE: |
1715 | case GDK_TOUCH_END: |
1716 | if (!gdk_touch_event_get_emulating_pointer (event: data->event)) |
1717 | continue; |
1718 | G_GNUC_FALLTHROUGH; |
1719 | case GDK_BUTTON_PRESS: |
1720 | case GDK_BUTTON_RELEASE: |
1721 | case GDK_MOTION_NOTIFY: |
1722 | *sequence = seq; |
1723 | return TRUE; |
1724 | default: |
1725 | break; |
1726 | } |
1727 | } |
1728 | |
1729 | return FALSE; |
1730 | } |
1731 | |