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
37typedef struct _GtkGestureZoomPrivate GtkGestureZoomPrivate;
38
39enum {
40 SCALE_CHANGED,
41 LAST_SIGNAL
42};
43
44struct _GtkGestureZoomPrivate
45{
46 double initial_distance;
47};
48
49static guint signals[LAST_SIGNAL] = { 0 };
50
51G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureZoom, gtk_gesture_zoom, GTK_TYPE_GESTURE)
52
53static void
54gtk_gesture_zoom_init (GtkGestureZoom *gesture)
55{
56}
57
58static GObject *
59gtk_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
73static 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
128static 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
148static gboolean
149gtk_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
169static void
170gtk_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
180static void
181gtk_gesture_zoom_update (GtkGesture *gesture,
182 GdkEventSequence *sequence)
183{
184 _gtk_gesture_zoom_check_emit (GTK_GESTURE_ZOOM (gesture));
185}
186
187static void
188gtk_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 */
225GtkGesture *
226gtk_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 */
245double
246gtk_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

source code of gtk/gtk/gtkgesturezoom.c