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