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 | * GtkGestureZoom: |
23 | * |
24 | * `GtkGestureZoom` is a `GtkGesture` for 2-finger pinch/zoom gestures. |
25 | * |
26 | * Whenever the distance between both tracked sequences changes, the |
27 | * [signal@Gtk.GestureZoom::scale-changed] signal is emitted to report |
28 | * the scale factor. |
29 | */ |
30 | |
31 | #include "config.h" |
32 | #include <math.h> |
33 | #include "gtkgesturezoom.h" |
34 | #include "gtkgesturezoomprivate.h" |
35 | #include "gtkintl.h" |
36 | |
37 | typedef struct _GtkGestureZoomPrivate GtkGestureZoomPrivate; |
38 | |
39 | enum { |
40 | SCALE_CHANGED, |
41 | LAST_SIGNAL |
42 | }; |
43 | |
44 | struct _GtkGestureZoomPrivate |
45 | { |
46 | double initial_distance; |
47 | }; |
48 | |
49 | static guint signals[LAST_SIGNAL] = { 0 }; |
50 | |
51 | G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureZoom, gtk_gesture_zoom, GTK_TYPE_GESTURE) |
52 | |
53 | static void |
54 | gtk_gesture_zoom_init (GtkGestureZoom *gesture) |
55 | { |
56 | } |
57 | |
58 | static GObject * |
59 | gtk_gesture_zoom_constructor (GType type, |
60 | guint n_construct_properties, |
61 | GObjectConstructParam *construct_properties) |
62 | { |
63 | GObject *object; |
64 | |
65 | object = G_OBJECT_CLASS (gtk_gesture_zoom_parent_class)->constructor (type, |
66 | n_construct_properties, |
67 | construct_properties); |
68 | g_object_set (object, first_property_name: "n-points" , 2, NULL); |
69 | |
70 | return object; |
71 | } |
72 | |
73 | static gboolean |
74 | _gtk_gesture_zoom_get_distance (GtkGestureZoom *zoom, |
75 | double *distance) |
76 | { |
77 | GdkEvent *last_event; |
78 | double x1, y1, x2, y2; |
79 | GtkGesture *gesture; |
80 | GList *sequences = NULL; |
81 | double dx, dy; |
82 | GdkTouchpadGesturePhase phase; |
83 | gboolean retval = FALSE; |
84 | |
85 | gesture = GTK_GESTURE (zoom); |
86 | |
87 | if (!gtk_gesture_is_recognized (gesture)) |
88 | goto out; |
89 | |
90 | sequences = gtk_gesture_get_sequences (gesture); |
91 | if (!sequences) |
92 | goto out; |
93 | |
94 | last_event = gtk_gesture_get_last_event (gesture, sequence: sequences->data); |
95 | |
96 | if (gdk_event_get_event_type (event: last_event) == GDK_TOUCHPAD_PINCH) |
97 | { |
98 | double scale; |
99 | |
100 | /* Touchpad pinch */ |
101 | phase = gdk_touchpad_event_get_gesture_phase (event: last_event); |
102 | if (phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL) |
103 | goto out; |
104 | |
105 | scale = gdk_touchpad_event_get_pinch_scale (event: last_event); |
106 | *distance = scale; |
107 | } |
108 | else |
109 | { |
110 | if (!sequences->next) |
111 | goto out; |
112 | |
113 | gtk_gesture_get_point (gesture, sequence: sequences->data, x: &x1, y: &y1); |
114 | gtk_gesture_get_point (gesture, sequence: sequences->next->data, x: &x2, y: &y2); |
115 | |
116 | dx = x1 - x2; |
117 | dy = y1 - y2;; |
118 | *distance = sqrt (x: (dx * dx) + (dy * dy)); |
119 | } |
120 | |
121 | retval = TRUE; |
122 | |
123 | out: |
124 | g_list_free (list: sequences); |
125 | return retval; |
126 | } |
127 | |
128 | static gboolean |
129 | _gtk_gesture_zoom_check_emit (GtkGestureZoom *gesture) |
130 | { |
131 | GtkGestureZoomPrivate *priv; |
132 | double distance, zoom; |
133 | |
134 | if (!_gtk_gesture_zoom_get_distance (zoom: gesture, distance: &distance)) |
135 | return FALSE; |
136 | |
137 | priv = gtk_gesture_zoom_get_instance_private (self: gesture); |
138 | |
139 | if (distance == 0 || priv->initial_distance == 0) |
140 | return FALSE; |
141 | |
142 | zoom = distance / priv->initial_distance; |
143 | g_signal_emit (instance: gesture, signal_id: signals[SCALE_CHANGED], detail: 0, zoom); |
144 | |
145 | return TRUE; |
146 | } |
147 | |
148 | static gboolean |
149 | gtk_gesture_zoom_filter_event (GtkEventController *controller, |
150 | GdkEvent *event) |
151 | { |
152 | /* Let 2-finger touchpad pinch and hold events go through */ |
153 | if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH || |
154 | gdk_event_get_event_type (event) == GDK_TOUCHPAD_HOLD) |
155 | { |
156 | guint n_fingers; |
157 | |
158 | n_fingers = gdk_touchpad_event_get_n_fingers (event); |
159 | |
160 | if (n_fingers == 2) |
161 | return FALSE; |
162 | else |
163 | return TRUE; |
164 | } |
165 | |
166 | return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_zoom_parent_class)->filter_event (controller, event); |
167 | } |
168 | |
169 | static void |
170 | gtk_gesture_zoom_begin (GtkGesture *gesture, |
171 | GdkEventSequence *sequence) |
172 | { |
173 | GtkGestureZoom *zoom = GTK_GESTURE_ZOOM (gesture); |
174 | GtkGestureZoomPrivate *priv; |
175 | |
176 | priv = gtk_gesture_zoom_get_instance_private (self: zoom); |
177 | _gtk_gesture_zoom_get_distance (zoom, distance: &priv->initial_distance); |
178 | } |
179 | |
180 | static void |
181 | gtk_gesture_zoom_update (GtkGesture *gesture, |
182 | GdkEventSequence *sequence) |
183 | { |
184 | _gtk_gesture_zoom_check_emit (GTK_GESTURE_ZOOM (gesture)); |
185 | } |
186 | |
187 | static void |
188 | gtk_gesture_zoom_class_init (GtkGestureZoomClass *klass) |
189 | { |
190 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
191 | GtkEventControllerClass *event_controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
192 | GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass); |
193 | |
194 | object_class->constructor = gtk_gesture_zoom_constructor; |
195 | |
196 | event_controller_class->filter_event = gtk_gesture_zoom_filter_event; |
197 | |
198 | gesture_class->begin = gtk_gesture_zoom_begin; |
199 | gesture_class->update = gtk_gesture_zoom_update; |
200 | |
201 | /** |
202 | * GtkGestureZoom::scale-changed: |
203 | * @controller: the object on which the signal is emitted |
204 | * @scale: Scale delta, taking the initial state as 1:1 |
205 | * |
206 | * Emitted whenever the distance between both tracked sequences changes. |
207 | */ |
208 | signals[SCALE_CHANGED] = |
209 | g_signal_new (I_("scale-changed" ), |
210 | GTK_TYPE_GESTURE_ZOOM, |
211 | signal_flags: G_SIGNAL_RUN_FIRST, |
212 | G_STRUCT_OFFSET (GtkGestureZoomClass, scale_changed), |
213 | NULL, NULL, NULL, |
214 | G_TYPE_NONE, n_params: 1, G_TYPE_DOUBLE); |
215 | } |
216 | |
217 | /** |
218 | * gtk_gesture_zoom_new: |
219 | * |
220 | * Returns a newly created `GtkGesture` that recognizes |
221 | * pinch/zoom gestures. |
222 | * |
223 | * Returns: a newly created `GtkGestureZoom` |
224 | */ |
225 | GtkGesture * |
226 | gtk_gesture_zoom_new (void) |
227 | { |
228 | return g_object_new (GTK_TYPE_GESTURE_ZOOM, |
229 | NULL); |
230 | } |
231 | |
232 | /** |
233 | * gtk_gesture_zoom_get_scale_delta: |
234 | * @gesture: a `GtkGestureZoom` |
235 | * |
236 | * Gets the scale delta. |
237 | * |
238 | * If @gesture is active, this function returns the zooming |
239 | * difference since the gesture was recognized (hence the |
240 | * starting point is considered 1:1). If @gesture is not |
241 | * active, 1 is returned. |
242 | * |
243 | * Returns: the scale delta |
244 | */ |
245 | double |
246 | gtk_gesture_zoom_get_scale_delta (GtkGestureZoom *gesture) |
247 | { |
248 | GtkGestureZoomPrivate *priv; |
249 | double distance; |
250 | |
251 | g_return_val_if_fail (GTK_IS_GESTURE_ZOOM (gesture), 1.0); |
252 | |
253 | if (!_gtk_gesture_zoom_get_distance (zoom: gesture, distance: &distance)) |
254 | return 1.0; |
255 | |
256 | priv = gtk_gesture_zoom_get_instance_private (self: gesture); |
257 | |
258 | return distance / priv->initial_distance; |
259 | } |
260 | |