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
45struct _GtkDropControllerMotion
46{
47 GtkEventController parent_instance;
48
49 GdkDrop *drop;
50 guint is_pointer : 1;
51 guint contains_pointer : 1;
52};
53
54struct _GtkDropControllerMotionClass
55{
56 GtkEventControllerClass parent_class;
57};
58
59enum {
60 ENTER,
61 LEAVE,
62 MOTION,
63 N_SIGNALS
64};
65
66enum {
67 PROP_0,
68 PROP_CONTAINS_POINTER,
69 PROP_DROP,
70 PROP_IS_POINTER,
71 NUM_PROPERTIES
72};
73
74static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
75
76static guint signals[N_SIGNALS] = { 0 };
77
78G_DEFINE_TYPE (GtkDropControllerMotion, gtk_drop_controller_motion, GTK_TYPE_EVENT_CONTROLLER)
79
80static gboolean
81gtk_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
98static void
99update_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
162static void
163gtk_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
172static void
173gtk_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
199static void
200gtk_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
329static void
330gtk_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 **/
342GtkEventController *
343gtk_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 */
358gboolean
359gtk_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 */
376GdkDrop *
377gtk_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 */
394gboolean
395gtk_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

source code of gtk/gtk/gtkdropcontrollermotion.c