1 | /* |
2 | * Copyright © 2020 Benjamin Otte |
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.1 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 | * Authors: Benjamin Otte <otte@gnome.org> |
18 | */ |
19 | |
20 | /** |
21 | * GtkDropControllerMotion: |
22 | * |
23 | * `GtkDropControllerMotion` is an event controller tracking |
24 | * the pointer during Drag-and-Drop operations. |
25 | * |
26 | * It is modeled after [class@Gtk.EventControllerMotion] so if you |
27 | * have used that, this should feel really familiar. |
28 | * |
29 | * This controller is not able to accept drops, use [class@Gtk.DropTarget] |
30 | * for that purpose. |
31 | */ |
32 | |
33 | #include "config.h" |
34 | |
35 | #include "gtkdropcontrollermotion.h" |
36 | |
37 | #include "gtkintl.h" |
38 | #include "gtkprivate.h" |
39 | #include "gtkwidgetprivate.h" |
40 | #include "gtkmarshalers.h" |
41 | #include "gtkeventcontrollerprivate.h" |
42 | #include "gtktypebuiltins.h" |
43 | #include "gtkmarshalers.h" |
44 | |
45 | struct _GtkDropControllerMotion |
46 | { |
47 | GtkEventController parent_instance; |
48 | |
49 | GdkDrop *drop; |
50 | guint is_pointer : 1; |
51 | guint contains_pointer : 1; |
52 | }; |
53 | |
54 | struct _GtkDropControllerMotionClass |
55 | { |
56 | GtkEventControllerClass parent_class; |
57 | }; |
58 | |
59 | enum { |
60 | ENTER, |
61 | LEAVE, |
62 | MOTION, |
63 | N_SIGNALS |
64 | }; |
65 | |
66 | enum { |
67 | PROP_0, |
68 | PROP_CONTAINS_POINTER, |
69 | PROP_DROP, |
70 | PROP_IS_POINTER, |
71 | NUM_PROPERTIES |
72 | }; |
73 | |
74 | static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; |
75 | |
76 | static guint signals[N_SIGNALS] = { 0 }; |
77 | |
78 | G_DEFINE_TYPE (GtkDropControllerMotion, gtk_drop_controller_motion, GTK_TYPE_EVENT_CONTROLLER) |
79 | |
80 | static gboolean |
81 | gtk_drop_controller_motion_handle_event (GtkEventController *controller, |
82 | GdkEvent *event, |
83 | double x, |
84 | double y) |
85 | { |
86 | GtkEventControllerClass *parent_class; |
87 | GdkEventType type; |
88 | |
89 | type = gdk_event_get_event_type (event); |
90 | if (type == GDK_DRAG_MOTION) |
91 | g_signal_emit (instance: controller, signal_id: signals[MOTION], detail: 0, x, y); |
92 | |
93 | parent_class = GTK_EVENT_CONTROLLER_CLASS (gtk_drop_controller_motion_parent_class); |
94 | |
95 | return parent_class->handle_event (controller, event, x, y); |
96 | } |
97 | |
98 | static void |
99 | update_pointer_focus (GtkEventController *controller, |
100 | const GtkCrossingData *crossing, |
101 | double x, |
102 | double y) |
103 | { |
104 | GtkDropControllerMotion *self = GTK_DROP_CONTROLLER_MOTION (controller); |
105 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
106 | gboolean is_pointer = FALSE; |
107 | gboolean contains_pointer = FALSE; |
108 | gboolean enter = FALSE; |
109 | gboolean leave = FALSE; |
110 | |
111 | if (crossing->direction == GTK_CROSSING_IN) |
112 | { |
113 | if (crossing->new_descendent != NULL) |
114 | { |
115 | contains_pointer = TRUE; |
116 | } |
117 | if (crossing->new_target == widget) |
118 | { |
119 | contains_pointer = TRUE; |
120 | is_pointer = TRUE; |
121 | } |
122 | } |
123 | else |
124 | { |
125 | if (crossing->new_descendent != NULL || |
126 | crossing->new_target == widget) |
127 | contains_pointer = TRUE; |
128 | is_pointer = FALSE; |
129 | } |
130 | |
131 | if (self->contains_pointer != contains_pointer) |
132 | { |
133 | enter = contains_pointer; |
134 | leave = !contains_pointer; |
135 | } |
136 | |
137 | if (leave) |
138 | g_signal_emit (instance: controller, signal_id: signals[LEAVE], detail: 0); |
139 | |
140 | g_object_freeze_notify (G_OBJECT (self)); |
141 | if (self->is_pointer != is_pointer) |
142 | { |
143 | self->is_pointer = is_pointer; |
144 | g_object_notify (G_OBJECT (self), property_name: "is-pointer" ); |
145 | } |
146 | if (self->contains_pointer != contains_pointer) |
147 | { |
148 | self->contains_pointer = contains_pointer; |
149 | if (contains_pointer) |
150 | self->drop = g_object_ref (crossing->drop); |
151 | else |
152 | g_clear_object (&self->drop); |
153 | g_object_notify (G_OBJECT (self), property_name: "contains-pointer" ); |
154 | g_object_notify (G_OBJECT (self), property_name: "drop" ); |
155 | } |
156 | g_object_thaw_notify (G_OBJECT (self)); |
157 | |
158 | if (enter) |
159 | g_signal_emit (instance: controller, signal_id: signals[ENTER], detail: 0, x, y); |
160 | } |
161 | |
162 | static void |
163 | gtk_drop_controller_motion_handle_crossing (GtkEventController *controller, |
164 | const GtkCrossingData *crossing, |
165 | double x, |
166 | double y) |
167 | { |
168 | if (crossing->type == GTK_CROSSING_DROP) |
169 | update_pointer_focus (controller, crossing, x, y); |
170 | } |
171 | |
172 | static void |
173 | gtk_drop_controller_motion_get_property (GObject *object, |
174 | guint prop_id, |
175 | GValue *value, |
176 | GParamSpec *pspec) |
177 | { |
178 | GtkDropControllerMotion *self = GTK_DROP_CONTROLLER_MOTION (object); |
179 | |
180 | switch (prop_id) |
181 | { |
182 | case PROP_CONTAINS_POINTER: |
183 | g_value_set_boolean (value, v_boolean: self->contains_pointer); |
184 | break; |
185 | |
186 | case PROP_DROP: |
187 | g_value_set_object (value, v_object: self->drop); |
188 | break; |
189 | |
190 | case PROP_IS_POINTER: |
191 | g_value_set_boolean (value, v_boolean: self->is_pointer); |
192 | break; |
193 | |
194 | default: |
195 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
196 | } |
197 | } |
198 | |
199 | static void |
200 | gtk_drop_controller_motion_class_init (GtkDropControllerMotionClass *klass) |
201 | { |
202 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
203 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
204 | |
205 | object_class->get_property = gtk_drop_controller_motion_get_property; |
206 | |
207 | controller_class->handle_event = gtk_drop_controller_motion_handle_event; |
208 | controller_class->handle_crossing = gtk_drop_controller_motion_handle_crossing; |
209 | |
210 | /** |
211 | * GtkDropControllerMotion:contains-pointer: (attributes org.gtk.Property.get=gtk_drop_controller_motion_contains_pointer) |
212 | * |
213 | * Whether the pointer of a Drag-and-Drop operation is in |
214 | * the controller's widget or a descendant. |
215 | * |
216 | * See also [property@Gtk.DropControllerMotion:is-pointer]. |
217 | * |
218 | * When handling crossing events, this property is updated |
219 | * before [signal@Gtk.DropControllerMotion::enter], but after |
220 | * [signal@Gtk.DropControllerMotion::leave] is emitted. |
221 | */ |
222 | props[PROP_CONTAINS_POINTER] = |
223 | g_param_spec_boolean (name: "contains-pointer" , |
224 | P_("Contains Pointer" ), |
225 | P_("Whether the pointer is in the controllers widget or a descendant" ), |
226 | FALSE, |
227 | flags: G_PARAM_READABLE); |
228 | |
229 | /** |
230 | * GtkDropControllerMotion:drop: (attributes org.gtk.Property.get=gtk_drop_controller_motion_get_drop) |
231 | * |
232 | * The ongoing drop operation over the controller's widget or |
233 | * its descendant. |
234 | * |
235 | * If no drop operation is going on, this property returns %NULL. |
236 | * |
237 | * The event controller should not modify the @drop, but it might |
238 | * want to query its properties. |
239 | * |
240 | * When handling crossing events, this property is updated |
241 | * before [signal@Gtk.DropControllerMotion::enter], but after |
242 | * [signal@Gtk.DropControllerMotion::leave] is emitted. |
243 | */ |
244 | props[PROP_DROP] = |
245 | g_param_spec_object (name: "drop" , |
246 | P_("Drop" ), |
247 | P_("The ongoing drop operation" ), |
248 | GDK_TYPE_DROP, |
249 | flags: G_PARAM_READABLE); |
250 | |
251 | /** |
252 | * GtkDropControllerMotion:is-pointer: (attributes org.gtk.Property.get=gtk_drop_controller_motion_is_pointer) |
253 | * |
254 | * Whether the pointer is in the controllers widget itself, |
255 | * as opposed to in a descendent widget. |
256 | * |
257 | * See also [property@Gtk.DropControllerMotion:contains-pointer]. |
258 | * |
259 | * When handling crossing events, this property is updated |
260 | * before [signal@Gtk.DropControllerMotion::enter], but after |
261 | * [signal@Gtk.DropControllerMotion::leave] is emitted. |
262 | */ |
263 | props[PROP_IS_POINTER] = |
264 | g_param_spec_boolean (name: "is-pointer" , |
265 | P_("Is Pointer" ), |
266 | P_("Whether the pointer is in the controllers widget" ), |
267 | FALSE, |
268 | flags: G_PARAM_READABLE); |
269 | |
270 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: props); |
271 | |
272 | /** |
273 | * GtkDropControllerMotion::enter: |
274 | * @self: the object which received the signal |
275 | * @x: coordinates of pointer location |
276 | * @y: coordinates of pointer location |
277 | * |
278 | * Signals that the pointer has entered the widget. |
279 | */ |
280 | signals[ENTER] = |
281 | g_signal_new (I_("enter" ), |
282 | GTK_TYPE_DROP_CONTROLLER_MOTION, |
283 | signal_flags: G_SIGNAL_RUN_LAST, |
284 | class_offset: 0, NULL, NULL, |
285 | NULL, |
286 | G_TYPE_NONE, n_params: 2, |
287 | G_TYPE_DOUBLE, |
288 | G_TYPE_DOUBLE); |
289 | g_signal_set_va_marshaller (signal_id: signals[ENTER], |
290 | G_TYPE_FROM_CLASS (klass), |
291 | va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLEv); |
292 | |
293 | /** |
294 | * GtkDropControllerMotion::leave: |
295 | * @self: the object which received the signal |
296 | * |
297 | * Signals that the pointer has left the widget. |
298 | */ |
299 | signals[LEAVE] = |
300 | g_signal_new (I_("leave" ), |
301 | GTK_TYPE_DROP_CONTROLLER_MOTION, |
302 | signal_flags: G_SIGNAL_RUN_LAST, |
303 | class_offset: 0, NULL, NULL, |
304 | NULL, |
305 | G_TYPE_NONE, n_params: 0); |
306 | |
307 | /** |
308 | * GtkDropControllerMotion::motion: |
309 | * @self: The object that received the signal |
310 | * @x: the x coordinate |
311 | * @y: the y coordinate |
312 | * |
313 | * Emitted when the pointer moves inside the widget. |
314 | */ |
315 | signals[MOTION] = |
316 | g_signal_new (I_("motion" ), |
317 | GTK_TYPE_DROP_CONTROLLER_MOTION, |
318 | signal_flags: G_SIGNAL_RUN_FIRST, |
319 | class_offset: 0, NULL, NULL, |
320 | c_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE, |
321 | G_TYPE_NONE, n_params: 2, |
322 | G_TYPE_DOUBLE, |
323 | G_TYPE_DOUBLE); |
324 | g_signal_set_va_marshaller (signal_id: signals[MOTION], |
325 | G_TYPE_FROM_CLASS (klass), |
326 | va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLEv); |
327 | } |
328 | |
329 | static void |
330 | gtk_drop_controller_motion_init (GtkDropControllerMotion *self) |
331 | { |
332 | } |
333 | |
334 | /** |
335 | * gtk_drop_controller_motion_new: |
336 | * |
337 | * Creates a new event controller that will handle pointer motion |
338 | * events during drag and drop. |
339 | * |
340 | * Returns: a new `GtkDropControllerMotion` |
341 | **/ |
342 | GtkEventController * |
343 | gtk_drop_controller_motion_new (void) |
344 | { |
345 | return g_object_new (GTK_TYPE_DROP_CONTROLLER_MOTION, |
346 | NULL); |
347 | } |
348 | |
349 | /** |
350 | * gtk_drop_controller_motion_contains_pointer: (attributes org.gtk.Method.get_property=contains-pointer) |
351 | * @self: a `GtkDropControllerMotion` |
352 | * |
353 | * Returns if a Drag-and-Drop operation is within the widget |
354 | * @self or one of its children. |
355 | * |
356 | * Returns: %TRUE if a dragging pointer is within @self or one of its children. |
357 | */ |
358 | gboolean |
359 | gtk_drop_controller_motion_contains_pointer (GtkDropControllerMotion *self) |
360 | { |
361 | g_return_val_if_fail (GTK_IS_DROP_CONTROLLER_MOTION (self), FALSE); |
362 | |
363 | return self->contains_pointer; |
364 | } |
365 | |
366 | /** |
367 | * gtk_drop_controller_motion_get_drop: (attributes org.gtk.Method.get_property=drop) |
368 | * @self: a `GtkDropControllerMotion` |
369 | * |
370 | * Returns the `GdkDrop` of a current Drag-and-Drop operation |
371 | * over the widget of @self. |
372 | * |
373 | * Returns: (transfer none) (nullable): The `GdkDrop` currently |
374 | * happening within @self |
375 | */ |
376 | GdkDrop * |
377 | gtk_drop_controller_motion_get_drop (GtkDropControllerMotion *self) |
378 | { |
379 | g_return_val_if_fail (GTK_IS_DROP_CONTROLLER_MOTION (self), FALSE); |
380 | |
381 | return self->drop; |
382 | } |
383 | |
384 | /** |
385 | * gtk_drop_controller_motion_is_pointer: (attributes org.gtk.Method.get_property=is-pointer) |
386 | * @self: a `GtkDropControllerMotion` |
387 | * |
388 | * Returns if a Drag-and-Drop operation is within the widget |
389 | * @self, not one of its children. |
390 | * |
391 | * Returns: %TRUE if a dragging pointer is within @self but |
392 | * not one of its children |
393 | */ |
394 | gboolean |
395 | gtk_drop_controller_motion_is_pointer (GtkDropControllerMotion *self) |
396 | { |
397 | g_return_val_if_fail (GTK_IS_DROP_CONTROLLER_MOTION (self), FALSE); |
398 | |
399 | return self->is_pointer; |
400 | } |
401 | |