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 | |
37 | typedef struct _GtkGestureRotatePrivate GtkGestureRotatePrivate; |
38 | |
39 | enum { |
40 | ANGLE_CHANGED, |
41 | LAST_SIGNAL |
42 | }; |
43 | |
44 | struct _GtkGestureRotatePrivate |
45 | { |
46 | double initial_angle; |
47 | double accum_touchpad_angle; |
48 | }; |
49 | |
50 | static guint signals[LAST_SIGNAL] = { 0 }; |
51 | |
52 | G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureRotate, gtk_gesture_rotate, GTK_TYPE_GESTURE) |
53 | |
54 | static void |
55 | gtk_gesture_rotate_init (GtkGestureRotate *gesture) |
56 | { |
57 | } |
58 | |
59 | static GObject * |
60 | gtk_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 | |
74 | static 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 | |
134 | static 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 | |
153 | static void |
154 | gtk_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 | |
164 | static void |
165 | gtk_gesture_rotate_update (GtkGesture *gesture, |
166 | GdkEventSequence *sequence) |
167 | { |
168 | _gtk_gesture_rotate_check_emit (GTK_GESTURE_ROTATE (gesture)); |
169 | } |
170 | |
171 | static gboolean |
172 | gtk_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 | |
192 | static gboolean |
193 | gtk_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 | |
219 | static void |
220 | gtk_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 | **/ |
263 | GtkGesture * |
264 | gtk_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 | */ |
282 | double |
283 | gtk_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 | |