gtkgesturezoom.c 6.84 KB
Newer Older
Carlos Garnacho's avatar
Carlos Garnacho committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* GTK - The GIMP Toolkit
 * Copyright (C) 2012, One Laptop Per Child.
 * Copyright (C) 2014, Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author(s): Carlos Garnacho <carlosg@gnome.org>
 */
20
21

/**
Matthias Clasen's avatar
Matthias Clasen committed
22
 * GtkGestureZoom:
23
 *
Matthias Clasen's avatar
Matthias Clasen committed
24
25
26
27
28
 * `GtkGestureZoom` is a `GtkGesture` for 2-finger pinch/zoom gestures.
 *
 * Whenever the distance between both tracked sequences changes, the
 * [signal@Gtk.GestureZoom::scale-changed] signal is emitted to report
 * the scale factor.
29
30
 */

Carlos Garnacho's avatar
Carlos Garnacho committed
31
32
#include "config.h"
#include <math.h>
33
34
#include "gtkgesturezoom.h"
#include "gtkgesturezoomprivate.h"
35
#include "gtkintl.h"
Carlos Garnacho's avatar
Carlos Garnacho committed
36
37
38
39
40
41
42
43
44
45

typedef struct _GtkGestureZoomPrivate GtkGestureZoomPrivate;

enum {
  SCALE_CHANGED,
  LAST_SIGNAL
};

struct _GtkGestureZoomPrivate
{
46
  double initial_distance;
Carlos Garnacho's avatar
Carlos Garnacho committed
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureZoom, gtk_gesture_zoom, GTK_TYPE_GESTURE)

static void
gtk_gesture_zoom_init (GtkGestureZoom *gesture)
{
}

static GObject *
gtk_gesture_zoom_constructor (GType                  type,
                              guint                  n_construct_properties,
                              GObjectConstructParam *construct_properties)
{
  GObject *object;

  object = G_OBJECT_CLASS (gtk_gesture_zoom_parent_class)->constructor (type,
                                                                        n_construct_properties,
                                                                        construct_properties);
  g_object_set (object, "n-points", 2, NULL);

  return object;
}

static gboolean
_gtk_gesture_zoom_get_distance (GtkGestureZoom *zoom,
75
                                double         *distance)
Carlos Garnacho's avatar
Carlos Garnacho committed
76
{
Matthias Clasen's avatar
Matthias Clasen committed
77
  GdkEvent *last_event;
78
  double x1, y1, x2, y2;
Carlos Garnacho's avatar
Carlos Garnacho committed
79
  GtkGesture *gesture;
80
  GList *sequences = NULL;
81
  double dx, dy;
82
  GdkTouchpadGesturePhase phase;
83
  gboolean retval = FALSE;
Carlos Garnacho's avatar
Carlos Garnacho committed
84
85
86
87

  gesture = GTK_GESTURE (zoom);

  if (!gtk_gesture_is_recognized (gesture))
88
    goto out;
Carlos Garnacho's avatar
Carlos Garnacho committed
89
90

  sequences = gtk_gesture_get_sequences (gesture);
91
  if (!sequences)
92
    goto out;
Carlos Garnacho's avatar
Carlos Garnacho committed
93

94
95
  last_event = gtk_gesture_get_last_event (gesture, sequences->data);

96
  if (gdk_event_get_event_type (last_event) == GDK_TOUCHPAD_PINCH)
97
    {
98
      double scale;
99

100
      /* Touchpad pinch */
101
102
103
      phase = gdk_touchpad_event_get_gesture_phase (last_event);
      if (phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL)
        goto out;
104

105
      scale = gdk_touchpad_event_get_pinch_scale (last_event);
106
      *distance = scale;
107
108
109
110
    }
  else
    {
      if (!sequences->next)
111
        goto out;
112
113
114
115
116
117
118
119

      gtk_gesture_get_point (gesture, sequences->data, &x1, &y1);
      gtk_gesture_get_point (gesture, sequences->next->data, &x2, &y2);

      dx = x1 - x2;
      dy = y1 - y2;;
      *distance = sqrt ((dx * dx) + (dy * dy));
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
120

121
122
123
124
125
  retval = TRUE;

 out:
  g_list_free (sequences);
  return retval;
Carlos Garnacho's avatar
Carlos Garnacho committed
126
127
128
129
130
131
}

static gboolean
_gtk_gesture_zoom_check_emit (GtkGestureZoom *gesture)
{
  GtkGestureZoomPrivate *priv;
132
  double distance, zoom;
Carlos Garnacho's avatar
Carlos Garnacho committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

  if (!_gtk_gesture_zoom_get_distance (gesture, &distance))
    return FALSE;

  priv = gtk_gesture_zoom_get_instance_private (gesture);

  if (distance == 0 || priv->initial_distance == 0)
    return FALSE;

  zoom = distance / priv->initial_distance;
  g_signal_emit (gesture, signals[SCALE_CHANGED], 0, zoom);

  return TRUE;
}

148
149
static gboolean
gtk_gesture_zoom_filter_event (GtkEventController *controller,
Matthias Clasen's avatar
Matthias Clasen committed
150
                               GdkEvent           *event)
151
152
{
  /* Let 2-finger touchpad pinch events go through */
153
  if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH)
154
    {
155
156
      guint n_fingers;

Matthias Clasen's avatar
Matthias Clasen committed
157
      n_fingers = gdk_touchpad_event_get_n_fingers (event);
158
159

      if (n_fingers == 2)
160
161
162
163
164
165
166
167
        return FALSE;
      else
        return TRUE;
    }

  return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_zoom_parent_class)->filter_event (controller, event);
}

Carlos Garnacho's avatar
Carlos Garnacho committed
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
static void
gtk_gesture_zoom_begin (GtkGesture       *gesture,
                        GdkEventSequence *sequence)
{
  GtkGestureZoom *zoom = GTK_GESTURE_ZOOM (gesture);
  GtkGestureZoomPrivate *priv;

  priv = gtk_gesture_zoom_get_instance_private (zoom);
  _gtk_gesture_zoom_get_distance (zoom, &priv->initial_distance);
}

static void
gtk_gesture_zoom_update (GtkGesture       *gesture,
                         GdkEventSequence *sequence)
{
  _gtk_gesture_zoom_check_emit (GTK_GESTURE_ZOOM (gesture));
}

static void
gtk_gesture_zoom_class_init (GtkGestureZoomClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
190
  GtkEventControllerClass *event_controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
Carlos Garnacho's avatar
Carlos Garnacho committed
191
192
193
194
  GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass);

  object_class->constructor = gtk_gesture_zoom_constructor;

195
196
  event_controller_class->filter_event = gtk_gesture_zoom_filter_event;

Carlos Garnacho's avatar
Carlos Garnacho committed
197
198
199
200
201
202
203
  gesture_class->begin = gtk_gesture_zoom_begin;
  gesture_class->update = gtk_gesture_zoom_update;

  /**
   * GtkGestureZoom::scale-changed:
   * @controller: the object on which the signal is emitted
   * @scale: Scale delta, taking the initial state as 1:1
204
   *
Matthias Clasen's avatar
Matthias Clasen committed
205
   * Emitted whenever the distance between both tracked sequences changes.
Carlos Garnacho's avatar
Carlos Garnacho committed
206
207
   */
  signals[SCALE_CHANGED] =
208
    g_signal_new (I_("scale-changed"),
Carlos Garnacho's avatar
Carlos Garnacho committed
209
210
211
212
213
214
215
216
217
218
                  GTK_TYPE_GESTURE_ZOOM,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkGestureZoomClass, scale_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1, G_TYPE_DOUBLE);
}

/**
 * gtk_gesture_zoom_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
219
220
 * Returns a newly created `GtkGesture` that recognizes
 * pinch/zoom gestures.
Carlos Garnacho's avatar
Carlos Garnacho committed
221
 *
Matthias Clasen's avatar
Matthias Clasen committed
222
223
 * Returns: a newly created `GtkGestureZoom`
 */
Carlos Garnacho's avatar
Carlos Garnacho committed
224
GtkGesture *
225
gtk_gesture_zoom_new (void)
Carlos Garnacho's avatar
Carlos Garnacho committed
226
227
228
229
230
231
232
{
  return g_object_new (GTK_TYPE_GESTURE_ZOOM,
                       NULL);
}

/**
 * gtk_gesture_zoom_get_scale_delta:
Matthias Clasen's avatar
Matthias Clasen committed
233
 * @gesture: a `GtkGestureZoom`
Carlos Garnacho's avatar
Carlos Garnacho committed
234
 *
Matthias Clasen's avatar
Matthias Clasen committed
235
236
237
238
239
240
 * Gets the scale delta.
 *
 * If @gesture is active, this function returns the zooming
 * difference since the gesture was recognized (hence the
 * starting point is considered 1:1). If @gesture is not
 * active, 1 is returned.
Carlos Garnacho's avatar
Carlos Garnacho committed
241
 *
Matthias Clasen's avatar
Matthias Clasen committed
242
 * Returns: the scale delta
Matthias Clasen's avatar
Matthias Clasen committed
243
 */
244
double
245
gtk_gesture_zoom_get_scale_delta (GtkGestureZoom *gesture)
Carlos Garnacho's avatar
Carlos Garnacho committed
246
247
{
  GtkGestureZoomPrivate *priv;
248
  double distance;
Carlos Garnacho's avatar
Carlos Garnacho committed
249

Matthias Clasen's avatar
Matthias Clasen committed
250
  g_return_val_if_fail (GTK_IS_GESTURE_ZOOM (gesture), 1.0);
Carlos Garnacho's avatar
Carlos Garnacho committed
251
252

  if (!_gtk_gesture_zoom_get_distance (gesture, &distance))
Matthias Clasen's avatar
Matthias Clasen committed
253
    return 1.0;
Carlos Garnacho's avatar
Carlos Garnacho committed
254
255
256

  priv = gtk_gesture_zoom_get_instance_private (gesture);

257
  return distance / priv->initial_distance;
Carlos Garnacho's avatar
Carlos Garnacho committed
258
}