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 | * GtkGestureLongPress: |
23 | * |
24 | * `GtkGestureLongPress` is a `GtkGesture` for long presses. |
25 | * |
26 | * This gesture is also known as “Press and Hold”. |
27 | * |
28 | * When the timeout is exceeded, the gesture is triggering the |
29 | * [signal@Gtk.GestureLongPress::pressed] signal. |
30 | * |
31 | * If the touchpoint is lifted before the timeout passes, or if |
32 | * it drifts too far of the initial press point, the |
33 | * [signal@Gtk.GestureLongPress::cancelled] signal will be emitted. |
34 | * |
35 | * How long the timeout is before the ::pressed signal gets emitted is |
36 | * determined by the [property@Gtk.Settings:gtk-long-press-time] setting. |
37 | * It can be modified by the [property@Gtk.GestureLongPress:delay-factor] |
38 | * property. |
39 | */ |
40 | |
41 | #include "config.h" |
42 | #include "gtkgesturelongpress.h" |
43 | #include "gtkgesturelongpressprivate.h" |
44 | #include "gtkgestureprivate.h" |
45 | #include "gtkmarshalers.h" |
46 | #include "gtkdragsourceprivate.h" |
47 | #include "gtkprivate.h" |
48 | #include "gtkintl.h" |
49 | #include "gtkmarshalers.h" |
50 | |
51 | typedef struct _GtkGestureLongPressPrivate GtkGestureLongPressPrivate; |
52 | |
53 | enum { |
54 | PRESSED, |
55 | CANCELLED, |
56 | N_SIGNALS |
57 | }; |
58 | |
59 | enum { |
60 | PROP_DELAY_FACTOR = 1, |
61 | LAST_PROP |
62 | }; |
63 | |
64 | struct _GtkGestureLongPressPrivate |
65 | { |
66 | double initial_x; |
67 | double initial_y; |
68 | |
69 | double delay_factor; |
70 | guint timeout_id; |
71 | guint delay; |
72 | guint cancelled : 1; |
73 | guint triggered : 1; |
74 | }; |
75 | |
76 | static guint signals[N_SIGNALS] = { 0 }; |
77 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
78 | |
79 | G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureLongPress, gtk_gesture_long_press, GTK_TYPE_GESTURE_SINGLE) |
80 | |
81 | static void |
82 | gtk_gesture_long_press_init (GtkGestureLongPress *gesture) |
83 | { |
84 | GtkGestureLongPressPrivate *priv = gtk_gesture_long_press_get_instance_private (self: gesture); |
85 | priv->delay_factor = 1.0; |
86 | } |
87 | |
88 | static gboolean |
89 | gtk_gesture_long_press_check (GtkGesture *gesture) |
90 | { |
91 | GtkGestureLongPressPrivate *priv; |
92 | |
93 | priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture)); |
94 | |
95 | if (priv->cancelled) |
96 | return FALSE; |
97 | |
98 | return GTK_GESTURE_CLASS (gtk_gesture_long_press_parent_class)->check (gesture); |
99 | } |
100 | |
101 | static gboolean |
102 | _gtk_gesture_long_press_timeout (gpointer user_data) |
103 | { |
104 | GtkGestureLongPress *gesture = user_data; |
105 | GtkGestureLongPressPrivate *priv; |
106 | GdkEventSequence *sequence; |
107 | double x, y; |
108 | |
109 | priv = gtk_gesture_long_press_get_instance_private (self: gesture); |
110 | sequence = gtk_gesture_get_last_updated_sequence (GTK_GESTURE (gesture)); |
111 | gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, x: &x, y: &y); |
112 | |
113 | priv->timeout_id = 0; |
114 | priv->triggered = TRUE; |
115 | g_signal_emit (instance: gesture, signal_id: signals[PRESSED], detail: 0, x, y); |
116 | |
117 | return G_SOURCE_REMOVE; |
118 | } |
119 | |
120 | static void |
121 | gtk_gesture_long_press_begin (GtkGesture *gesture, |
122 | GdkEventSequence *sequence) |
123 | { |
124 | GtkGestureLongPressPrivate *priv; |
125 | GdkEvent *event; |
126 | GdkEventType event_type; |
127 | GtkWidget *widget; |
128 | int delay; |
129 | |
130 | priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture)); |
131 | sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
132 | event = gtk_gesture_get_last_event (gesture, sequence); |
133 | |
134 | if (!event) |
135 | return; |
136 | |
137 | event_type = gdk_event_get_event_type (event); |
138 | |
139 | if (event_type != GDK_BUTTON_PRESS && |
140 | event_type != GDK_TOUCH_BEGIN) |
141 | return; |
142 | |
143 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
144 | g_object_get (object: gtk_widget_get_settings (widget), |
145 | first_property_name: "gtk-long-press-time" , &delay, |
146 | NULL); |
147 | |
148 | delay = (int)(priv->delay_factor * delay); |
149 | |
150 | gtk_gesture_get_point (gesture, sequence, |
151 | x: &priv->initial_x, y: &priv->initial_y); |
152 | priv->timeout_id = g_timeout_add (interval: delay, function: _gtk_gesture_long_press_timeout, data: gesture); |
153 | gdk_source_set_static_name_by_id (tag: priv->timeout_id, name: "[gtk] _gtk_gesture_long_press_timeout" ); |
154 | } |
155 | |
156 | static void |
157 | gtk_gesture_long_press_update (GtkGesture *gesture, |
158 | GdkEventSequence *sequence) |
159 | { |
160 | GtkGestureLongPressPrivate *priv; |
161 | GtkWidget *widget; |
162 | double x, y; |
163 | |
164 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); |
165 | priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture)); |
166 | gtk_gesture_get_point (gesture, sequence, x: &x, y: &y); |
167 | |
168 | if (gtk_drag_check_threshold_double (widget, start_x: priv->initial_x, start_y: priv->initial_y, current_x: x, current_y: y)) |
169 | { |
170 | if (priv->timeout_id) |
171 | { |
172 | g_source_remove (tag: priv->timeout_id); |
173 | priv->timeout_id = 0; |
174 | g_signal_emit (instance: gesture, signal_id: signals[CANCELLED], detail: 0); |
175 | } |
176 | |
177 | priv->cancelled = TRUE; |
178 | _gtk_gesture_check (gesture); |
179 | } |
180 | } |
181 | |
182 | static void |
183 | gtk_gesture_long_press_end (GtkGesture *gesture, |
184 | GdkEventSequence *sequence) |
185 | { |
186 | GtkGestureLongPressPrivate *priv; |
187 | |
188 | priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (gesture)); |
189 | |
190 | if (priv->timeout_id) |
191 | { |
192 | g_source_remove (tag: priv->timeout_id); |
193 | priv->timeout_id = 0; |
194 | g_signal_emit (instance: gesture, signal_id: signals[CANCELLED], detail: 0); |
195 | } |
196 | |
197 | priv->cancelled = priv->triggered = FALSE; |
198 | } |
199 | |
200 | static void |
201 | gtk_gesture_long_press_cancel (GtkGesture *gesture, |
202 | GdkEventSequence *sequence) |
203 | { |
204 | gtk_gesture_long_press_end (gesture, sequence); |
205 | GTK_GESTURE_CLASS (gtk_gesture_long_press_parent_class)->cancel (gesture, sequence); |
206 | } |
207 | |
208 | static void |
209 | gtk_gesture_long_press_sequence_state_changed (GtkGesture *gesture, |
210 | GdkEventSequence *sequence, |
211 | GtkEventSequenceState state) |
212 | { |
213 | if (state == GTK_EVENT_SEQUENCE_DENIED) |
214 | gtk_gesture_long_press_end (gesture, sequence); |
215 | } |
216 | |
217 | static void |
218 | gtk_gesture_long_press_finalize (GObject *object) |
219 | { |
220 | GtkGestureLongPressPrivate *priv; |
221 | |
222 | priv = gtk_gesture_long_press_get_instance_private (GTK_GESTURE_LONG_PRESS (object)); |
223 | |
224 | if (priv->timeout_id) |
225 | g_source_remove (tag: priv->timeout_id); |
226 | |
227 | G_OBJECT_CLASS (gtk_gesture_long_press_parent_class)->finalize (object); |
228 | } |
229 | |
230 | static void |
231 | gtk_gesture_long_press_get_property (GObject *object, |
232 | guint property_id, |
233 | GValue *value, |
234 | GParamSpec *pspec) |
235 | { |
236 | switch (property_id) |
237 | { |
238 | case PROP_DELAY_FACTOR: |
239 | g_value_set_double (value, v_double: gtk_gesture_long_press_get_delay_factor (GTK_GESTURE_LONG_PRESS (object))); |
240 | break; |
241 | |
242 | default: |
243 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
244 | break; |
245 | } |
246 | } |
247 | |
248 | static void |
249 | gtk_gesture_long_press_set_property (GObject *object, |
250 | guint property_id, |
251 | const GValue *value, |
252 | GParamSpec *pspec) |
253 | { |
254 | switch (property_id) |
255 | { |
256 | case PROP_DELAY_FACTOR: |
257 | gtk_gesture_long_press_set_delay_factor (GTK_GESTURE_LONG_PRESS (object), |
258 | delay_factor: g_value_get_double (value)); |
259 | break; |
260 | |
261 | default: |
262 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
263 | break; |
264 | } |
265 | } |
266 | |
267 | static void |
268 | gtk_gesture_long_press_class_init (GtkGestureLongPressClass *klass) |
269 | { |
270 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
271 | GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass); |
272 | |
273 | object_class->finalize = gtk_gesture_long_press_finalize; |
274 | object_class->get_property = gtk_gesture_long_press_get_property; |
275 | object_class->set_property = gtk_gesture_long_press_set_property; |
276 | |
277 | gesture_class->check = gtk_gesture_long_press_check; |
278 | gesture_class->begin = gtk_gesture_long_press_begin; |
279 | gesture_class->update = gtk_gesture_long_press_update; |
280 | gesture_class->end = gtk_gesture_long_press_end; |
281 | gesture_class->cancel = gtk_gesture_long_press_cancel; |
282 | gesture_class->sequence_state_changed = gtk_gesture_long_press_sequence_state_changed; |
283 | |
284 | /** |
285 | * GtkGestureLongPress:delay-factor: (attributes org.gtk.Property.get=gtk_gesture_long_press_get_delay_factor org.gtk.Property.set=gtk_gesture_long_press_set_delay_factor) |
286 | * |
287 | * Factor by which to modify the default timeout. |
288 | */ |
289 | props[PROP_DELAY_FACTOR] = |
290 | g_param_spec_double (name: "delay-factor" , |
291 | P_("Delay factor" ), |
292 | P_("Factor by which to modify the default timeout" ), |
293 | minimum: 0.5, maximum: 2.0, default_value: 1.0, |
294 | flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); |
295 | |
296 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props); |
297 | |
298 | /** |
299 | * GtkGestureLongPress::pressed: |
300 | * @gesture: the object which received the signal |
301 | * @x: the X coordinate where the press happened, relative to the widget allocation |
302 | * @y: the Y coordinate where the press happened, relative to the widget allocation |
303 | * |
304 | * Emitted whenever a press goes unmoved/unreleased longer than |
305 | * what the GTK defaults tell. |
306 | */ |
307 | signals[PRESSED] = |
308 | g_signal_new (I_("pressed" ), |
309 | G_TYPE_FROM_CLASS (klass), |
310 | signal_flags: G_SIGNAL_RUN_LAST, |
311 | G_STRUCT_OFFSET (GtkGestureLongPressClass, pressed), |
312 | NULL, NULL, |
313 | c_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE, |
314 | G_TYPE_NONE, n_params: 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
315 | g_signal_set_va_marshaller (signal_id: signals[PRESSED], |
316 | G_TYPE_FROM_CLASS (klass), |
317 | va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLEv); |
318 | |
319 | /** |
320 | * GtkGestureLongPress::cancelled: |
321 | * @gesture: the object which received the signal |
322 | * |
323 | * Emitted whenever a press moved too far, or was released |
324 | * before [signal@Gtk.GestureLongPress::pressed] happened. |
325 | */ |
326 | signals[CANCELLED] = |
327 | g_signal_new (I_("cancelled" ), |
328 | G_TYPE_FROM_CLASS (klass), |
329 | signal_flags: G_SIGNAL_RUN_LAST, |
330 | G_STRUCT_OFFSET (GtkGestureLongPressClass, cancelled), |
331 | NULL, NULL, NULL, |
332 | G_TYPE_NONE, n_params: 0); |
333 | } |
334 | |
335 | /** |
336 | * gtk_gesture_long_press_new: |
337 | * |
338 | * Returns a newly created `GtkGesture` that recognizes long presses. |
339 | * |
340 | * Returns: a newly created `GtkGestureLongPress`. |
341 | */ |
342 | GtkGesture * |
343 | gtk_gesture_long_press_new (void) |
344 | { |
345 | return g_object_new (GTK_TYPE_GESTURE_LONG_PRESS, |
346 | NULL); |
347 | } |
348 | |
349 | /** |
350 | * gtk_gesture_long_press_set_delay_factor: (attributes org.gtk.Method.set_property=delay-factor) |
351 | * @gesture: A `GtkGestureLongPress` |
352 | * @delay_factor: The delay factor to apply |
353 | * |
354 | * Applies the given delay factor. |
355 | * |
356 | * The default long press time will be multiplied by this value. |
357 | * Valid values are in the range [0.5..2.0]. |
358 | */ |
359 | void |
360 | gtk_gesture_long_press_set_delay_factor (GtkGestureLongPress *gesture, |
361 | double delay_factor) |
362 | { |
363 | GtkGestureLongPressPrivate *priv = gtk_gesture_long_press_get_instance_private (self: gesture); |
364 | |
365 | g_return_if_fail (GTK_IS_GESTURE_LONG_PRESS (gesture)); |
366 | g_return_if_fail (delay_factor >= 0.5); |
367 | g_return_if_fail (delay_factor <= 2.0); |
368 | |
369 | if (delay_factor == priv->delay_factor) |
370 | return; |
371 | |
372 | priv->delay_factor = delay_factor; |
373 | |
374 | g_object_notify_by_pspec (G_OBJECT (gesture), pspec: props[PROP_DELAY_FACTOR]); |
375 | } |
376 | |
377 | /** |
378 | * gtk_gesture_long_press_get_delay_factor: (attributes org.gtk.Method.get_property=delay-factor) |
379 | * @gesture: A `GtkGestureLongPress` |
380 | * |
381 | * Returns the delay factor. |
382 | * |
383 | * Returns: the delay factor |
384 | */ |
385 | double |
386 | gtk_gesture_long_press_get_delay_factor (GtkGestureLongPress *gesture) |
387 | { |
388 | GtkGestureLongPressPrivate *priv = gtk_gesture_long_press_get_instance_private (self: gesture); |
389 | |
390 | g_return_val_if_fail (GTK_IS_GESTURE_LONG_PRESS (gesture), 0); |
391 | |
392 | return priv->delay_factor; |
393 | } |
394 | |