1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2017, 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): Matthias Clasen <mclasen@redhat.com> |
18 | */ |
19 | |
20 | /** |
21 | * GtkEventControllerMotion: |
22 | * |
23 | * `GtkEventControllerMotion` is an event controller tracking the pointer |
24 | * position. |
25 | * |
26 | * The event controller offers [signal@Gtk.EventControllerMotion::enter] |
27 | * and [signal@Gtk.EventControllerMotion::leave] signals, as well as |
28 | * [property@Gtk.EventControllerMotion:is-pointer] and |
29 | * [property@Gtk.EventControllerMotion:contains-pointer] properties |
30 | * which are updated to reflect changes in the pointer position as it |
31 | * moves over the widget. |
32 | */ |
33 | #include "config.h" |
34 | |
35 | #include "gtkintl.h" |
36 | #include "gtkprivate.h" |
37 | #include "gtkwidgetprivate.h" |
38 | #include "gtkmarshalers.h" |
39 | #include "gtkeventcontrollerprivate.h" |
40 | #include "gtkeventcontrollermotion.h" |
41 | #include "gtktypebuiltins.h" |
42 | #include "gtkmarshalers.h" |
43 | |
44 | struct _GtkEventControllerMotion |
45 | { |
46 | GtkEventController parent_instance; |
47 | |
48 | guint is_pointer : 1; |
49 | guint contains_pointer : 1; |
50 | }; |
51 | |
52 | struct _GtkEventControllerMotionClass |
53 | { |
54 | GtkEventControllerClass parent_class; |
55 | }; |
56 | |
57 | enum { |
58 | ENTER, |
59 | LEAVE, |
60 | MOTION, |
61 | N_SIGNALS |
62 | }; |
63 | |
64 | enum { |
65 | PROP_IS_POINTER = 1, |
66 | PROP_CONTAINS_POINTER, |
67 | NUM_PROPERTIES |
68 | }; |
69 | |
70 | static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; |
71 | |
72 | static guint signals[N_SIGNALS] = { 0 }; |
73 | |
74 | G_DEFINE_TYPE (GtkEventControllerMotion, gtk_event_controller_motion, GTK_TYPE_EVENT_CONTROLLER) |
75 | |
76 | static gboolean |
77 | gtk_event_controller_motion_handle_event (GtkEventController *controller, |
78 | GdkEvent *event, |
79 | double x, |
80 | double y) |
81 | { |
82 | GtkEventControllerClass *parent_class; |
83 | GdkEventType type; |
84 | |
85 | type = gdk_event_get_event_type (event); |
86 | if (type == GDK_MOTION_NOTIFY) |
87 | g_signal_emit (instance: controller, signal_id: signals[MOTION], detail: 0, x, y); |
88 | |
89 | parent_class = GTK_EVENT_CONTROLLER_CLASS (gtk_event_controller_motion_parent_class); |
90 | |
91 | return parent_class->handle_event (controller, event, x, y); |
92 | } |
93 | |
94 | static void |
95 | update_pointer_focus (GtkEventController *controller, |
96 | const GtkCrossingData *crossing, |
97 | double x, |
98 | double y) |
99 | { |
100 | GtkEventControllerMotion *motion = GTK_EVENT_CONTROLLER_MOTION (controller); |
101 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
102 | gboolean is_pointer = FALSE; |
103 | gboolean contains_pointer = FALSE; |
104 | gboolean enter = FALSE; |
105 | gboolean leave = FALSE; |
106 | |
107 | if (crossing->direction == GTK_CROSSING_IN) |
108 | { |
109 | if (crossing->new_descendent != NULL) |
110 | { |
111 | contains_pointer = TRUE; |
112 | } |
113 | if (crossing->new_target == widget) |
114 | { |
115 | contains_pointer = TRUE; |
116 | is_pointer = TRUE; |
117 | } |
118 | } |
119 | else |
120 | { |
121 | if (crossing->new_descendent != NULL || |
122 | crossing->new_target == widget) |
123 | contains_pointer = TRUE; |
124 | is_pointer = FALSE; |
125 | } |
126 | |
127 | if (motion->contains_pointer != contains_pointer) |
128 | { |
129 | enter = contains_pointer; |
130 | leave = !contains_pointer; |
131 | } |
132 | |
133 | if (leave) |
134 | g_signal_emit (instance: controller, signal_id: signals[LEAVE], detail: 0); |
135 | |
136 | g_object_freeze_notify (G_OBJECT (motion)); |
137 | if (motion->is_pointer != is_pointer) |
138 | { |
139 | motion->is_pointer = is_pointer; |
140 | g_object_notify_by_pspec (G_OBJECT (motion), pspec: props[PROP_IS_POINTER]); |
141 | } |
142 | if (motion->contains_pointer != contains_pointer) |
143 | { |
144 | motion->contains_pointer = contains_pointer; |
145 | g_object_notify_by_pspec (G_OBJECT (motion), pspec: props[PROP_CONTAINS_POINTER]); |
146 | } |
147 | g_object_thaw_notify (G_OBJECT (motion)); |
148 | |
149 | if (enter) |
150 | g_signal_emit (instance: controller, signal_id: signals[ENTER], detail: 0, x, y); |
151 | } |
152 | |
153 | static void |
154 | gtk_event_controller_motion_handle_crossing (GtkEventController *controller, |
155 | const GtkCrossingData *crossing, |
156 | double x, |
157 | double y) |
158 | { |
159 | if (crossing->type == GTK_CROSSING_POINTER) |
160 | update_pointer_focus (controller, crossing, x, y); |
161 | } |
162 | |
163 | static void |
164 | gtk_event_controller_motion_get_property (GObject *object, |
165 | guint prop_id, |
166 | GValue *value, |
167 | GParamSpec *pspec) |
168 | { |
169 | GtkEventControllerMotion *controller = GTK_EVENT_CONTROLLER_MOTION (object); |
170 | |
171 | switch (prop_id) |
172 | { |
173 | case PROP_IS_POINTER: |
174 | g_value_set_boolean (value, v_boolean: controller->is_pointer); |
175 | break; |
176 | |
177 | case PROP_CONTAINS_POINTER: |
178 | g_value_set_boolean (value, v_boolean: controller->contains_pointer); |
179 | break; |
180 | |
181 | default: |
182 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
183 | } |
184 | } |
185 | |
186 | static void |
187 | gtk_event_controller_motion_class_init (GtkEventControllerMotionClass *klass) |
188 | { |
189 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
190 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
191 | |
192 | object_class->get_property = gtk_event_controller_motion_get_property; |
193 | |
194 | controller_class->handle_event = gtk_event_controller_motion_handle_event; |
195 | controller_class->handle_crossing = gtk_event_controller_motion_handle_crossing; |
196 | |
197 | /** |
198 | * GtkEventControllerMotion:is-pointer: (attributes org.gtk.Property.get=gtk_event_controller_motion_is_pointer) |
199 | * |
200 | * Whether the pointer is in the controllers widget itself, |
201 | * as opposed to in a descendent widget. |
202 | * |
203 | * See also [property@Gtk.EventControllerMotion:contains-pointer]. |
204 | * |
205 | * When handling crossing events, this property is updated |
206 | * before [signal@Gtk.EventControllerMotion::enter], but after |
207 | * [signal@Gtk.EventControllerMotion::leave] is emitted. |
208 | */ |
209 | props[PROP_IS_POINTER] = |
210 | g_param_spec_boolean (name: "is-pointer" , |
211 | P_("Is Pointer" ), |
212 | P_("Whether the pointer is in the controllers widget" ), |
213 | FALSE, |
214 | flags: G_PARAM_READABLE); |
215 | |
216 | /** |
217 | * GtkEventControllerMotion:contains-pointer: (attributes org.gtk.Property.get=gtk_event_controller_motion_contains_pointer) |
218 | * |
219 | * Whether the pointer is in the controllers widget or a descendant. |
220 | * |
221 | * See also [property@Gtk.EventControllerMotion:is-pointer]. |
222 | * |
223 | * When handling crossing events, this property is updated |
224 | * before [signal@Gtk.EventControllerMotion::enter], but after |
225 | * [signal@Gtk.EventControllerMotion::leave] is emitted. |
226 | */ |
227 | props[PROP_CONTAINS_POINTER] = |
228 | g_param_spec_boolean (name: "contains-pointer" , |
229 | P_("Contains Pointer" ), |
230 | P_("Whether the pointer is in the controllers widget or a descendant" ), |
231 | FALSE, |
232 | flags: G_PARAM_READABLE); |
233 | |
234 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: props); |
235 | |
236 | /** |
237 | * GtkEventControllerMotion::enter: |
238 | * @controller: the object which received the signal |
239 | * @x: coordinates of pointer location |
240 | * @y: coordinates of pointer location |
241 | * |
242 | * Signals that the pointer has entered the widget. |
243 | */ |
244 | signals[ENTER] = |
245 | g_signal_new (I_("enter" ), |
246 | GTK_TYPE_EVENT_CONTROLLER_MOTION, |
247 | signal_flags: G_SIGNAL_RUN_LAST, |
248 | class_offset: 0, NULL, NULL, |
249 | NULL, |
250 | G_TYPE_NONE, n_params: 2, |
251 | G_TYPE_DOUBLE, |
252 | G_TYPE_DOUBLE); |
253 | |
254 | /** |
255 | * GtkEventControllerMotion::leave: |
256 | * @controller: the object which received the signal |
257 | * |
258 | * Signals that the pointer has left the widget. |
259 | */ |
260 | signals[LEAVE] = |
261 | g_signal_new (I_("leave" ), |
262 | GTK_TYPE_EVENT_CONTROLLER_MOTION, |
263 | signal_flags: G_SIGNAL_RUN_LAST, |
264 | class_offset: 0, NULL, NULL, |
265 | NULL, |
266 | G_TYPE_NONE, n_params: 0); |
267 | |
268 | /** |
269 | * GtkEventControllerMotion::motion: |
270 | * @controller: The object that received the signal |
271 | * @x: the x coordinate |
272 | * @y: the y coordinate |
273 | * |
274 | * Emitted when the pointer moves inside the widget. |
275 | */ |
276 | signals[MOTION] = |
277 | g_signal_new (I_("motion" ), |
278 | GTK_TYPE_EVENT_CONTROLLER_MOTION, |
279 | signal_flags: G_SIGNAL_RUN_FIRST, |
280 | class_offset: 0, NULL, NULL, |
281 | c_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLE, |
282 | G_TYPE_NONE, n_params: 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE); |
283 | g_signal_set_va_marshaller (signal_id: signals[MOTION], |
284 | G_TYPE_FROM_CLASS (klass), |
285 | va_marshaller: _gtk_marshal_VOID__DOUBLE_DOUBLEv); |
286 | } |
287 | |
288 | static void |
289 | gtk_event_controller_motion_init (GtkEventControllerMotion *motion) |
290 | { |
291 | } |
292 | |
293 | /** |
294 | * gtk_event_controller_motion_new: |
295 | * |
296 | * Creates a new event controller that will handle motion events. |
297 | * |
298 | * Returns: a new `GtkEventControllerMotion` |
299 | **/ |
300 | GtkEventController * |
301 | gtk_event_controller_motion_new (void) |
302 | { |
303 | return g_object_new (GTK_TYPE_EVENT_CONTROLLER_MOTION, |
304 | NULL); |
305 | } |
306 | |
307 | /** |
308 | * gtk_event_controller_motion_contains_pointer: (attributes org.gtk.Method.get_property=contains-pointer) |
309 | * @self: a `GtkEventControllerMotion` |
310 | * |
311 | * Returns if a pointer is within @self or one of its children. |
312 | * |
313 | * Returns: %TRUE if a pointer is within @self or one of its children |
314 | */ |
315 | gboolean |
316 | gtk_event_controller_motion_contains_pointer (GtkEventControllerMotion *self) |
317 | { |
318 | g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_MOTION (self), FALSE); |
319 | |
320 | return self->contains_pointer; |
321 | } |
322 | |
323 | /** |
324 | * gtk_event_controller_motion_is_pointer: (attributes org.gtk.Method.get_property=is-pointer) |
325 | * @self: a `GtkEventControllerMotion` |
326 | * |
327 | * Returns if a pointer is within @self, but not one of its children. |
328 | * |
329 | * Returns: %TRUE if a pointer is within @self but not one of its children |
330 | */ |
331 | gboolean |
332 | gtk_event_controller_motion_is_pointer (GtkEventControllerMotion *self) |
333 | { |
334 | g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_MOTION (self), FALSE); |
335 | |
336 | return self->is_pointer; |
337 | } |
338 | |