gtkgesturerotate.c 8.38 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
 * GtkGestureRotate:
23
 *
Matthias Clasen's avatar
Matthias Clasen committed
24
25
26
27
 * `GtkGestureRotate` is a `GtkGesture` for 2-finger rotations.
 *
 * Whenever the angle between both handled sequences changes, the
 * [signal@Gtk.GestureRotate::angle-changed] signal is emitted.
28
29
 */

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

typedef struct _GtkGestureRotatePrivate GtkGestureRotatePrivate;

enum {
  ANGLE_CHANGED,
  LAST_SIGNAL
};

struct _GtkGestureRotatePrivate
{
46
47
  double initial_angle;
  double accum_touchpad_angle;
Carlos Garnacho's avatar
Carlos Garnacho committed
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
75
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_PRIVATE (GtkGestureRotate, gtk_gesture_rotate, GTK_TYPE_GESTURE)

static void
gtk_gesture_rotate_init (GtkGestureRotate *gesture)
{
}

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

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

  return object;
}

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

  gesture = GTK_GESTURE (rotate);
88
  priv = gtk_gesture_rotate_get_instance_private (rotate);
Carlos Garnacho's avatar
Carlos Garnacho committed
89
90

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

  sequences = gtk_gesture_get_sequences (gesture);
94
  if (!sequences)
95
    goto out;
96
97

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

99
  if (gdk_event_get_event_type (last_event) == GDK_TOUCHPAD_PINCH)
100
    {
101
102
103
104
      phase = gdk_touchpad_event_get_gesture_phase (last_event);
      if (phase == GDK_TOUCHPAD_GESTURE_PHASE_CANCEL)
        goto out;

105
106
107
108
109
      *angle = priv->accum_touchpad_angle;
    }
  else
    {
      if (!sequences->next)
110
        goto out;
Carlos Garnacho's avatar
Carlos Garnacho committed
111

112
113
      gtk_gesture_get_point (gesture, sequences->data, &x1, &y1);
      gtk_gesture_get_point (gesture, sequences->next->data, &x2, &y2);
Carlos Garnacho's avatar
Carlos Garnacho committed
114

115
116
      dx = x1 - x2;
      dy = y1 - y2;
Carlos Garnacho's avatar
Carlos Garnacho committed
117

118
      *angle = atan2 (dx, dy);
Carlos Garnacho's avatar
Carlos Garnacho committed
119

120
121
      /* Invert angle */
      *angle = (2 * G_PI) - *angle;
Carlos Garnacho's avatar
Carlos Garnacho committed
122

123
124
125
      /* And constraint it to 0°-360° */
      *angle = fmod (*angle, 2 * G_PI);
    }
Carlos Garnacho's avatar
Carlos Garnacho committed
126

127
128
129
130
131
  retval = TRUE;

 out:
  g_list_free (sequences);
  return retval;
Carlos Garnacho's avatar
Carlos Garnacho committed
132
133
134
135
136
137
}

static gboolean
_gtk_gesture_rotate_check_emit (GtkGestureRotate *gesture)
{
  GtkGestureRotatePrivate *priv;
138
  double angle, delta;
Carlos Garnacho's avatar
Carlos Garnacho committed
139
140
141
142
143

  if (!_gtk_gesture_rotate_get_angle (gesture, &angle))
    return FALSE;

  priv = gtk_gesture_rotate_get_instance_private (gesture);
144
  delta = angle - priv->initial_angle;
Carlos Garnacho's avatar
Carlos Garnacho committed
145

146
147
148
149
  if (delta < 0)
    delta += 2 * G_PI;

  g_signal_emit (gesture, signals[ANGLE_CHANGED], 0, angle, delta);
Carlos Garnacho's avatar
Carlos Garnacho committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
  return TRUE;
}

static void
gtk_gesture_rotate_begin (GtkGesture       *gesture,
                          GdkEventSequence *sequence)
{
  GtkGestureRotate *rotate = GTK_GESTURE_ROTATE (gesture);
  GtkGestureRotatePrivate *priv;

  priv = gtk_gesture_rotate_get_instance_private (rotate);
  _gtk_gesture_rotate_get_angle (rotate, &priv->initial_angle);
}

static void
gtk_gesture_rotate_update (GtkGesture       *gesture,
                           GdkEventSequence *sequence)
{
  _gtk_gesture_rotate_check_emit (GTK_GESTURE_ROTATE (gesture));
}

171
172
static gboolean
gtk_gesture_rotate_filter_event (GtkEventController *controller,
Matthias Clasen's avatar
Matthias Clasen committed
173
                                 GdkEvent           *event)
174
175
{
  /* Let 2-finger touchpad pinch events go through */
176
  if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH)
177
    {
178
179
      guint n_fingers;

Matthias Clasen's avatar
Matthias Clasen committed
180
      n_fingers = gdk_touchpad_event_get_n_fingers (event);
181
182

      if (n_fingers == 2)
183
184
185
186
187
188
189
190
191
192
        return FALSE;
      else
        return TRUE;
    }

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

static gboolean
gtk_gesture_rotate_handle_event (GtkEventController *controller,
Matthias Clasen's avatar
Matthias Clasen committed
193
                                 GdkEvent           *event,
194
195
                                 double              x,
                                 double              y)
196
197
198
{
  GtkGestureRotate *rotate = GTK_GESTURE_ROTATE (controller);
  GtkGestureRotatePrivate *priv;
199
200
  GdkTouchpadGesturePhase phase;
  double delta;
201
202
203

  priv = gtk_gesture_rotate_get_instance_private (rotate);

204
  if (gdk_event_get_event_type (event) == GDK_TOUCHPAD_PINCH)
205
    {
Matthias Clasen's avatar
Matthias Clasen committed
206
      phase = gdk_touchpad_event_get_gesture_phase (event);
207
      delta = gdk_touchpad_event_get_pinch_angle_delta (event);
208
209
      if (phase == GDK_TOUCHPAD_GESTURE_PHASE_BEGIN ||
          phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
210
        priv->accum_touchpad_angle = 0;
211
212
      else if (phase == GDK_TOUCHPAD_GESTURE_PHASE_UPDATE)
        priv->accum_touchpad_angle += delta;
213
214
    }

215
  return GTK_EVENT_CONTROLLER_CLASS (gtk_gesture_rotate_parent_class)->handle_event (controller, event, x, y);
216
217
}

Carlos Garnacho's avatar
Carlos Garnacho committed
218
219
220
221
static void
gtk_gesture_rotate_class_init (GtkGestureRotateClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
222
  GtkEventControllerClass *event_controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
Carlos Garnacho's avatar
Carlos Garnacho committed
223
224
225
226
  GtkGestureClass *gesture_class = GTK_GESTURE_CLASS (klass);

  object_class->constructor = gtk_gesture_rotate_constructor;

227
228
229
  event_controller_class->filter_event = gtk_gesture_rotate_filter_event;
  event_controller_class->handle_event = gtk_gesture_rotate_handle_event;

Carlos Garnacho's avatar
Carlos Garnacho committed
230
231
232
233
234
235
236
  gesture_class->begin = gtk_gesture_rotate_begin;
  gesture_class->update = gtk_gesture_rotate_update;

  /**
   * GtkGestureRotate::angle-changed:
   * @gesture: the object on which the signal is emitted
   * @angle: Current angle in radians
237
238
   * @angle_delta: Difference with the starting angle, in radians
   *
Matthias Clasen's avatar
Matthias Clasen committed
239
   * Emitted when the angle between both tracked points changes.
Carlos Garnacho's avatar
Carlos Garnacho committed
240
241
   */
  signals[ANGLE_CHANGED] =
242
    g_signal_new (I_("angle-changed"),
Carlos Garnacho's avatar
Carlos Garnacho committed
243
244
245
                  GTK_TYPE_GESTURE_ROTATE,
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GtkGestureRotateClass, angle_changed),
246
247
                  NULL, NULL,
                  _gtk_marshal_VOID__DOUBLE_DOUBLE,
Carlos Garnacho's avatar
Carlos Garnacho committed
248
                  G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
249
250
251
  g_signal_set_va_marshaller (signals[ANGLE_CHANGED],
                              G_TYPE_FROM_CLASS (klass),
                              _gtk_marshal_VOID__DOUBLE_DOUBLEv);
Carlos Garnacho's avatar
Carlos Garnacho committed
252
253
254
255
256
}

/**
 * gtk_gesture_rotate_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
257
 * Returns a newly created `GtkGesture` that recognizes 2-touch
Carlos Garnacho's avatar
Carlos Garnacho committed
258
259
 * rotation gestures.
 *
Matthias Clasen's avatar
Matthias Clasen committed
260
 * Returns: a newly created `GtkGestureRotate`
Carlos Garnacho's avatar
Carlos Garnacho committed
261
262
 **/
GtkGesture *
263
gtk_gesture_rotate_new (void)
Carlos Garnacho's avatar
Carlos Garnacho committed
264
265
266
267
268
269
270
{
  return g_object_new (GTK_TYPE_GESTURE_ROTATE,
                       NULL);
}

/**
 * gtk_gesture_rotate_get_angle_delta:
Matthias Clasen's avatar
Matthias Clasen committed
271
272
273
 * @gesture: a `GtkGestureRotate`
 *
 * Gets the angle delta in radians.
Carlos Garnacho's avatar
Carlos Garnacho committed
274
 *
275
 * If @gesture is active, this function returns the angle difference
Matthias Clasen's avatar
Matthias Clasen committed
276
277
 * in radians since the gesture was first recognized. If @gesture is
 * not active, 0 is returned.
Carlos Garnacho's avatar
Carlos Garnacho committed
278
 *
279
 * Returns: the angle delta in radians
Matthias Clasen's avatar
Matthias Clasen committed
280
 */
281
double
282
gtk_gesture_rotate_get_angle_delta (GtkGestureRotate *gesture)
Carlos Garnacho's avatar
Carlos Garnacho committed
283
284
{
  GtkGestureRotatePrivate *priv;
285
  double angle;
Carlos Garnacho's avatar
Carlos Garnacho committed
286

Matthias Clasen's avatar
Matthias Clasen committed
287
  g_return_val_if_fail (GTK_IS_GESTURE_ROTATE (gesture), 0.0);
Carlos Garnacho's avatar
Carlos Garnacho committed
288
289

  if (!_gtk_gesture_rotate_get_angle (gesture, &angle))
Matthias Clasen's avatar
Matthias Clasen committed
290
    return 0.0;
Carlos Garnacho's avatar
Carlos Garnacho committed
291
292
293

  priv = gtk_gesture_rotate_get_instance_private (gesture);

294
  return angle - priv->initial_angle;
Carlos Garnacho's avatar
Carlos Garnacho committed
295
}