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