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 * GtkGestureRotate:
23 *
24 * `GtkGestureRotate` is a `GtkGesture` for 2-finger rotations.
25 *
26 * Whenever the angle between both handled sequences changes, the
27 * [signal@Gtk.GestureRotate::angle-changed] signal is emitted.
28 */
29
30#include "config.h"
31#include <math.h>
32#include "gtkgesturerotate.h"
33#include "gtkgesturerotateprivate.h"
34#include "gtkmarshalers.h"
35#include "gtkintl.h"
36
37typedef struct _GtkGestureRotatePrivate GtkGestureRotatePrivate;
38
39enum {
40 ANGLE_CHANGED,
41 LAST_SIGNAL
42};
43
44struct _GtkGestureRotatePrivate
45{
46 double initial_angle;
47 double accum_touchpad_angle;
48};
49
50static guint signals[LAST_SIGNAL] = { 0 };
51
52G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureRotate, gtk_gesture_rotate, GTK_TYPE_GESTURE)
53
54static void
55gtk_gesture_rotate_init (GtkGestureRotate *gesture)
56{
57}
58
59static GObject *
60gtk_gesture_rotate_constructor (GType type,
61 guint n_construct_properties,
62 GObjectConstructParam *construct_properties)
63{
64 GObject *object;
65
66 object = G_OBJECT_CLASS (gtk_gesture_rotate_parent_class)->constructor (type,
67 n_construct_properties,
68 construct_properties);
69 g_object_set (object, first_property_name: "n-points", 2, NULL);
70
71 return object;
72}
73
74static gboolean
75_gtk_gesture_rotate_get_angle (GtkGestureRotate *rotate,
76 double *angle)
77{
78 GtkGestureRotatePrivate *priv;
79 GdkEvent *last_event;
80 double x1, y1, x2, y2;
81 GtkGesture *gesture;
82 double dx, dy;
83 GList *sequences = NULL;
84 GdkTouchpadGesturePhase phase;
85 gboolean retval = FALSE;
86
87 gesture = GTK_GESTURE (rotate);
88 priv = gtk_gesture_rotate_get_instance_private (self: rotate);
89
90 if (!gtk_gesture_is_recognized (gesture))
91 goto out;
92
93 sequences = gtk_gesture_get_sequences (gesture);
94 if (!sequences)
95 goto out;
96
97 last_event = gtk_gesture_get_last_event (gesture, sequence: sequences->data);
98
99 if (gdk_event_get_event_type (event: last_event) == GDK_TOUCHPAD_PINCH)
100 {
101 phase = gdk_touchpad_event_get_gesture_phase (event: last_event);
102 if (phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL)
103 goto out;
104
105 *angle = priv->accum_touchpad_angle;
106 }
107 else
108 {
109 if (!sequences->next)
110 goto out;
111
112 gtk_gesture_get_point (gesture, sequence: sequences->data, x: &x1, y: &y1);
113 gtk_gesture_get_point (gesture, sequence: sequences->next->data, x: &x2, y: &y2);
114
115 dx = x1 - x2;
116 dy = y1 - y2;
117
118 *angle = atan2 (y: dx, x: dy);
119
120 /* Invert angle */
121 *angle = (2 * G_PI) - *angle;
122
123 /* And constraint it to 0°-360° */
124 *angle = fmod (x: *angle, y: 2 * G_PI);
125 }
126
127 retval = TRUE;
128
129 out:
130 g_list_free (list: sequences);
131 return retval;
132}
133
134static gboolean
135_gtk_gesture_rotate_check_emit (GtkGestureRotate *gesture)
136{
137 GtkGestureRotatePrivate *priv;
138 double angle, delta;
139
140 if (!_gtk_gesture_rotate_get_angle (rotate: gesture, angle: &angle))
141 return FALSE;
142
143 priv = gtk_gesture_rotate_get_instance_private (self: gesture);
144 delta = angle - priv->initial_angle;
145
146 if (delta < 0)
147 delta += 2 * G_PI;
148
149 g_signal_emit (instance: gesture, signal_id: signals[ANGLE_CHANGED], detail: 0, angle, delta);
150 return TRUE;
151}
152
153static void
154gtk_gesture_rotate_begin (GtkGesture *gesture,
155 GdkEventSequence *sequence)
156{
157 GtkGestureRotate *rotate = GTK_GESTURE_ROTATE (gesture);
158 GtkGestureRotatePrivate *priv;
159
160 priv = gtk_gesture_rotate_get_instance_private (self: rotate);
161 _gtk_gesture_rotate_get_angle (rotate, angle: &priv->initial_angle);
162}
163
164static void
165gtk_gesture_rotate_update (GtkGesture *gesture,
166 GdkEventSequence *sequence)
167{
168 _gtk_gesture_rotate_check_emit (GTK_GESTURE_ROTATE (gesture));
169}
170
171static gboolean
172gtk_gesture_rotate_filter_event (GtkEventController *controller,
173 GdkEvent *event)
174{
175 /* Let 2-finger touchpad pinch and hold events go through */
176 if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH ||
177 gdk_event_get_event_type (event) == GDK_TOUCHPAD_HOLD)
178 {
179 guint n_fingers;
180
181 n_fingers = gdk_touchpad_event_get_n_fingers (event);
182
183 if (n_fingers == 2)
184 return FALSE;
185 else
186 return TRUE;
187 }
188
189 return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_rotate_parent_class)->filter_event (controller, event);
190}
191
192static gboolean
193gtk_gesture_rotate_handle_event (GtkEventController *controller,
194 GdkEvent *event,
195 double x,
196 double y)
197{
198 GtkGestureRotate *rotate = GTK_GESTURE_ROTATE (controller);
199 GtkGestureRotatePrivate *priv;
200 GdkTouchpadGesturePhase phase;
201 double delta;
202
203 priv = gtk_gesture_rotate_get_instance_private (self: rotate);
204
205 if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH)
206 {
207 phase = gdk_touchpad_event_get_gesture_phase (event);
208 delta = gdk_touchpad_event_get_pinch_angle_delta (event);
209 if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN ||
210 phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
211 priv->accum_touchpad_angle = 0;
212 else if (phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)
213 priv->accum_touchpad_angle += delta;
214 }
215
216 return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_rotate_parent_class)->handle_event (controller, event, x, y);
217}
218
219static void
220gtk_gesture_rotate_class_init (GtkGestureRotateClass *klass)
221{
222 GObjectClass *object_class = G_OBJECT_CLASS (klass);
223 GtkEventControllerClass *event_controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
224 GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass);
225
226 object_class->constructor = gtk_gesture_rotate_constructor;
227
228 event_controller_class->filter_event = gtk_gesture_rotate_filter_event;
229 event_controller_class->handle_event = gtk_gesture_rotate_handle_event;
230
231 gesture_class->begin = gtk_gesture_rotate_begin;
232 gesture_class->update = gtk_gesture_rotate_update;
233
234 /**
235 * GtkGestureRotate::angle-changed:
236 * @gesture: the object on which the signal is emitted
237 * @angle: Current angle in radians
238 * @angle_delta: Difference with the starting angle, in radians
239 *
240 * Emitted when the angle between both tracked points changes.
241 */
242 signals[ANGLE_CHANGED] =
243 g_signal_new (I_("angle-changed"),
244 GTK_TYPE_GESTURE_ROTATE,
245 signal_flags: G_SIGNAL_RUN_FIRST,
246 G_STRUCT_OFFSET (GtkGestureRotateClass, angle_changed),
247 NULL, NULL,
248 c_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE,
249 G_TYPE_NONE, n_params: 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
250 g_signal_set_va_marshaller (signal_id: signals[ANGLE_CHANGED],
251 G_TYPE_FROM_CLASS (klass),
252 va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLEv);
253}
254
255/**
256 * gtk_gesture_rotate_new:
257 *
258 * Returns a newly created `GtkGesture` that recognizes 2-touch
259 * rotation gestures.
260 *
261 * Returns: a newly created `GtkGestureRotate`
262 **/
263GtkGesture *
264gtk_gesture_rotate_new (void)
265{
266 return g_object_new (GTK_TYPE_GESTURE_ROTATE,
267 NULL);
268}
269
270/**
271 * gtk_gesture_rotate_get_angle_delta:
272 * @gesture: a `GtkGestureRotate`
273 *
274 * Gets the angle delta in radians.
275 *
276 * If @gesture is active, this function returns the angle difference
277 * in radians since the gesture was first recognized. If @gesture is
278 * not active, 0 is returned.
279 *
280 * Returns: the angle delta in radians
281 */
282double
283gtk_gesture_rotate_get_angle_delta (GtkGestureRotate *gesture)
284{
285 GtkGestureRotatePrivate *priv;
286 double angle;
287
288 g_return_val_if_fail (GTK_IS_GESTURE_ROTATE (gesture), 0.0);
289
290 if (!_gtk_gesture_rotate_get_angle (rotate: gesture, angle: &angle))
291 return 0.0;
292
293 priv = gtk_gesture_rotate_get_instance_private (self: gesture);
294
295 return angle - priv->initial_angle;
296}
297

source code of gtk/gtk/gtkgesturerotate.c