1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2014, Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author(s): Carlos Garnacho <carlosg@gnome.org>
18 */
19
20/**
21 * GtkGesturePan:
22 *
23 * `GtkGesturePan` is a `GtkGesture` for pan gestures.
24 *
25 * These are drags that are locked to happen along one axis. The axis
26 * that a `GtkGesturePan` handles is defined at construct time, and
27 * can be changed through [method@Gtk.GesturePan.set_orientation].
28 *
29 * When the gesture starts to be recognized, `GtkGesturePan` will
30 * attempt to determine as early as possible whether the sequence
31 * is moving in the expected direction, and denying the sequence if
32 * this does not happen.
33 *
34 * Once a panning gesture along the expected axis is recognized,
35 * the [signal@Gtk.GesturePan::pan] signal will be emitted as input
36 * events are received, containing the offset in the given axis.
37 */
38
39#include "config.h"
40#include "gtkgesturepan.h"
41#include "gtkgesturepanprivate.h"
42#include "gtktypebuiltins.h"
43#include "gtkprivate.h"
44#include "gtkintl.h"
45#include "gtkmarshalers.h"
46
47typedef struct _GtkGesturePanPrivate GtkGesturePanPrivate;
48
49struct _GtkGesturePanPrivate
50{
51 guint orientation : 2;
52 guint panning : 1;
53};
54
55enum {
56 PROP_ORIENTATION = 1
57};
58
59enum {
60 PAN,
61 N_SIGNALS
62};
63
64static guint signals[N_SIGNALS] = { 0 };
65
66G_DEFINE_TYPE_WITH_PRIVATE (GtkGesturePan, gtk_gesture_pan, GTK_TYPE_GESTURE_DRAG)
67
68static void
69gtk_gesture_pan_get_property (GObject *object,
70 guint prop_id,
71 GValue *value,
72 GParamSpec *pspec)
73{
74 GtkGesturePanPrivate *priv;
75
76 priv = gtk_gesture_pan_get_instance_private (GTK_GESTURE_PAN (object));
77
78 switch (prop_id)
79 {
80 case PROP_ORIENTATION:
81 g_value_set_enum (value, v_enum: priv->orientation);
82 break;
83 default:
84 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
85 }
86}
87
88static void
89gtk_gesture_pan_set_property (GObject *object,
90 guint prop_id,
91 const GValue *value,
92 GParamSpec *pspec)
93{
94 switch (prop_id)
95 {
96 case PROP_ORIENTATION:
97 gtk_gesture_pan_set_orientation (GTK_GESTURE_PAN (object),
98 orientation: g_value_get_enum (value));
99 break;
100 default:
101 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
102 }
103}
104
105static void
106direction_from_offset (double offset_x,
107 double offset_y,
108 GtkOrientation orientation,
109 GtkPanDirection *direction)
110{
111 if (orientation == GTK_ORIENTATION_HORIZONTAL)
112 {
113 if (offset_x > 0)
114 *direction = GTK_PAN_DIRECTION_RIGHT;
115 else
116 *direction = GTK_PAN_DIRECTION_LEFT;
117 }
118 else if (orientation == GTK_ORIENTATION_VERTICAL)
119 {
120 if (offset_y > 0)
121 *direction = GTK_PAN_DIRECTION_DOWN;
122 else
123 *direction = GTK_PAN_DIRECTION_UP;
124 }
125 else
126 g_assert_not_reached ();
127}
128
129static gboolean
130guess_direction (GtkGesturePan *gesture,
131 double offset_x,
132 double offset_y,
133 GtkPanDirection *direction)
134{
135 double abs_x, abs_y;
136
137 abs_x = ABS (offset_x);
138 abs_y = ABS (offset_y);
139
140#define FACTOR 2
141 if (abs_x > abs_y * FACTOR)
142 direction_from_offset (offset_x, offset_y,
143 orientation: GTK_ORIENTATION_HORIZONTAL, direction);
144 else if (abs_y > abs_x * FACTOR)
145 direction_from_offset (offset_x, offset_y,
146 orientation: GTK_ORIENTATION_VERTICAL, direction);
147 else
148 return FALSE;
149
150 return TRUE;
151#undef FACTOR
152}
153
154static gboolean
155check_orientation_matches (GtkGesturePan *gesture,
156 GtkPanDirection direction)
157{
158 GtkGesturePanPrivate *priv = gtk_gesture_pan_get_instance_private (self: gesture);
159
160 return (((direction == GTK_PAN_DIRECTION_LEFT ||
161 direction == GTK_PAN_DIRECTION_RIGHT) &&
162 priv->orientation == GTK_ORIENTATION_HORIZONTAL) ||
163 ((direction == GTK_PAN_DIRECTION_UP ||
164 direction == GTK_PAN_DIRECTION_DOWN) &&
165 priv->orientation == GTK_ORIENTATION_VERTICAL));
166}
167
168static void
169gtk_gesture_pan_drag_update (GtkGestureDrag *gesture,
170 double offset_x,
171 double offset_y)
172{
173 GtkGesturePanPrivate *priv;
174 GtkPanDirection direction;
175 GtkGesturePan *pan;
176 double offset;
177
178 pan = GTK_GESTURE_PAN (gesture);
179 priv = gtk_gesture_pan_get_instance_private (self: pan);
180
181 if (!priv->panning)
182 {
183 if (!guess_direction (gesture: pan, offset_x, offset_y, direction: &direction))
184 return;
185
186 if (!check_orientation_matches (gesture: pan, direction))
187 {
188 gtk_gesture_set_state (GTK_GESTURE (gesture),
189 state: GTK_EVENT_SEQUENCE_DENIED);
190 return;
191 }
192
193 priv->panning = TRUE;
194 }
195 else
196 direction_from_offset (offset_x, offset_y, orientation: priv->orientation, direction: &direction);
197
198 offset = (priv->orientation == GTK_ORIENTATION_VERTICAL) ?
199 ABS (offset_y) : ABS (offset_x);
200 g_signal_emit (instance: gesture, signal_id: signals[PAN], detail: 0, direction, offset);
201}
202
203static void
204gtk_gesture_pan_drag_end (GtkGestureDrag *gesture,
205 double offset_x,
206 double offset_y)
207{
208 GtkGesturePanPrivate *priv;
209
210 priv = gtk_gesture_pan_get_instance_private (GTK_GESTURE_PAN (gesture));
211 priv->panning = FALSE;
212}
213
214static void
215gtk_gesture_pan_class_init (GtkGesturePanClass *klass)
216{
217 GtkGestureDragClass *drag_gesture_class = GTK_GESTURE_DRAG_CLASS (klass);
218 GObjectClass *object_class = G_OBJECT_CLASS (klass);
219
220 object_class->get_property = gtk_gesture_pan_get_property;
221 object_class->set_property = gtk_gesture_pan_set_property;
222
223 drag_gesture_class->drag_update = gtk_gesture_pan_drag_update;
224 drag_gesture_class->drag_end = gtk_gesture_pan_drag_end;
225
226 /**
227 * GtkGesturePan:orientation: (attributes org.gtk.Property.get=gtk_gesture_pan_get_orientation org.gtk.Property.set=gtk_gesture_pan_set_orientation)
228 *
229 * The expected orientation of pan gestures.
230 */
231 g_object_class_install_property (oclass: object_class,
232 property_id: PROP_ORIENTATION,
233 pspec: g_param_spec_enum (name: "orientation",
234 P_("Orientation"),
235 P_("Allowed orientations"),
236 enum_type: GTK_TYPE_ORIENTATION,
237 default_value: GTK_ORIENTATION_HORIZONTAL,
238 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
239
240 /**
241 * GtkGesturePan::pan:
242 * @gesture: The object which received the signal
243 * @direction: current direction of the pan gesture
244 * @offset: Offset along the gesture orientation
245 *
246 * Emitted once a panning gesture along the expected axis is detected.
247 */
248 signals[PAN] =
249 g_signal_new (I_("pan"),
250 G_TYPE_FROM_CLASS (klass),
251 signal_flags: G_SIGNAL_RUN_LAST,
252 G_STRUCT_OFFSET (GtkGesturePanClass, pan),
253 NULL, NULL,
254 c_marshaller: _gtk_marshal_VOID__ENUM_DOUBLE,
255 G_TYPE_NONE, n_params: 2, GTK_TYPE_PAN_DIRECTION,
256 G_TYPE_DOUBLE);
257 g_signal_set_va_marshaller (signal_id: signals[PAN],
258 G_TYPE_FROM_CLASS (klass),
259 va_marshaller: _gtk_marshal_VOID__ENUM_DOUBLEv);
260}
261
262static void
263gtk_gesture_pan_init (GtkGesturePan *gesture)
264{
265 GtkGesturePanPrivate *priv;
266
267 priv = gtk_gesture_pan_get_instance_private (self: gesture);
268 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
269}
270
271/**
272 * gtk_gesture_pan_new:
273 * @orientation: expected orientation
274 *
275 * Returns a newly created `GtkGesture` that recognizes pan gestures.
276 *
277 * Returns: a newly created `GtkGesturePan`
278 */
279GtkGesture *
280gtk_gesture_pan_new (GtkOrientation orientation)
281{
282 return g_object_new (GTK_TYPE_GESTURE_PAN,
283 first_property_name: "orientation", orientation,
284 NULL);
285}
286
287/**
288 * gtk_gesture_pan_get_orientation: (attributes org.gtk.Method.get_property=orientation)
289 * @gesture: A `GtkGesturePan`
290 *
291 * Returns the orientation of the pan gestures that this @gesture expects.
292 *
293 * Returns: the expected orientation for pan gestures
294 */
295GtkOrientation
296gtk_gesture_pan_get_orientation (GtkGesturePan *gesture)
297{
298 GtkGesturePanPrivate *priv;
299
300 g_return_val_if_fail (GTK_IS_GESTURE_PAN (gesture), 0);
301
302 priv = gtk_gesture_pan_get_instance_private (self: gesture);
303
304 return priv->orientation;
305}
306
307/**
308 * gtk_gesture_pan_set_orientation: (attributes org.gtk.Method.set_property=orientation)
309 * @gesture: A `GtkGesturePan`
310 * @orientation: expected orientation
311 *
312 * Sets the orientation to be expected on pan gestures.
313 */
314void
315gtk_gesture_pan_set_orientation (GtkGesturePan *gesture,
316 GtkOrientation orientation)
317{
318 GtkGesturePanPrivate *priv;
319
320 g_return_if_fail (GTK_IS_GESTURE_PAN (gesture));
321 g_return_if_fail (orientation == GTK_ORIENTATION_HORIZONTAL ||
322 orientation == GTK_ORIENTATION_VERTICAL);
323
324 priv = gtk_gesture_pan_get_instance_private (self: gesture);
325
326 if (priv->orientation == orientation)
327 return;
328
329 priv->orientation = orientation;
330 g_object_notify (G_OBJECT (gesture), property_name: "orientation");
331}
332

source code of gtk/gtk/gtkgesturepan.c