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 | |
47 | typedef struct _GtkGesturePanPrivate GtkGesturePanPrivate; |
48 | |
49 | struct _GtkGesturePanPrivate |
50 | { |
51 | guint orientation : 2; |
52 | guint panning : 1; |
53 | }; |
54 | |
55 | enum { |
56 | PROP_ORIENTATION = 1 |
57 | }; |
58 | |
59 | enum { |
60 | PAN, |
61 | N_SIGNALS |
62 | }; |
63 | |
64 | static guint signals[N_SIGNALS] = { 0 }; |
65 | |
66 | G_DEFINE_TYPE_WITH_PRIVATE (GtkGesturePan, gtk_gesture_pan, GTK_TYPE_GESTURE_DRAG) |
67 | |
68 | static void |
69 | gtk_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 | |
88 | static void |
89 | gtk_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 | |
105 | static void |
106 | direction_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 | |
129 | static gboolean |
130 | guess_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 | |
154 | static gboolean |
155 | check_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 | |
168 | static void |
169 | gtk_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 | |
203 | static void |
204 | gtk_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 | |
214 | static void |
215 | gtk_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 | |
262 | static void |
263 | gtk_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 | */ |
279 | GtkGesture * |
280 | gtk_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 | */ |
295 | GtkOrientation |
296 | gtk_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 | */ |
314 | void |
315 | gtk_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 | |