1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2020, 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 | * GtkEventControllerFocus: |
22 | * |
23 | * `GtkEventControllerFocus` is an event controller to keep track of |
24 | * keyboard focus. |
25 | * |
26 | * The event controller offers [signal@Gtk.EventControllerFocus::enter] |
27 | * and [signal@Gtk.EventControllerFocus::leave] signals, as well as |
28 | * [property@Gtk.EventControllerFocus:is-focus] and |
29 | * [property@Gtk.EventControllerFocus:contains-focus] properties |
30 | * which are updated to reflect focus changes inside the widget hierarchy |
31 | * that is rooted at the controllers widget. |
32 | */ |
33 | |
34 | #include "config.h" |
35 | |
36 | #include "gtkintl.h" |
37 | #include "gtkmarshalers.h" |
38 | #include "gtkprivate.h" |
39 | #include "gtkwidgetprivate.h" |
40 | #include "gtkeventcontrollerprivate.h" |
41 | #include "gtkeventcontrollerfocus.h" |
42 | #include "gtkenums.h" |
43 | #include "gtkmain.h" |
44 | #include "gtktypebuiltins.h" |
45 | |
46 | #include <gdk/gdk.h> |
47 | |
48 | struct _GtkEventControllerFocus |
49 | { |
50 | GtkEventController parent_instance; |
51 | |
52 | guint is_focus : 1; |
53 | guint contains_focus : 1; |
54 | }; |
55 | |
56 | struct _GtkEventControllerFocusClass |
57 | { |
58 | GtkEventControllerClass parent_class; |
59 | }; |
60 | |
61 | enum { |
62 | ENTER, |
63 | LEAVE, |
64 | N_SIGNALS |
65 | }; |
66 | |
67 | static guint signals[N_SIGNALS] = { 0 }; |
68 | |
69 | enum { |
70 | PROP_IS_FOCUS = 1, |
71 | PROP_CONTAINS_FOCUS, |
72 | NUM_PROPERTIES |
73 | }; |
74 | |
75 | static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; |
76 | |
77 | G_DEFINE_TYPE (GtkEventControllerFocus, gtk_event_controller_focus, |
78 | GTK_TYPE_EVENT_CONTROLLER) |
79 | |
80 | static void |
81 | gtk_event_controller_focus_finalize (GObject *object) |
82 | { |
83 | //GtkEventControllerFocus *focus = GTK_EVENT_CONTROLLER_FOCUS (object); |
84 | |
85 | G_OBJECT_CLASS (gtk_event_controller_focus_parent_class)->finalize (object); |
86 | } |
87 | |
88 | static void |
89 | update_focus (GtkEventController *controller, |
90 | const GtkCrossingData *crossing) |
91 | { |
92 | GtkEventControllerFocus *focus = GTK_EVENT_CONTROLLER_FOCUS (controller); |
93 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
94 | gboolean is_focus = FALSE; |
95 | gboolean contains_focus = FALSE; |
96 | gboolean enter = FALSE; |
97 | gboolean leave = FALSE; |
98 | |
99 | if (crossing->direction == GTK_CROSSING_IN) |
100 | { |
101 | if (crossing->new_descendent != NULL) |
102 | { |
103 | contains_focus = TRUE; |
104 | } |
105 | if (crossing->new_target == widget) |
106 | { |
107 | contains_focus = TRUE; |
108 | is_focus = TRUE; |
109 | } |
110 | } |
111 | else |
112 | { |
113 | if (crossing->new_descendent != NULL || |
114 | crossing->new_target == widget) |
115 | contains_focus = TRUE; |
116 | is_focus = FALSE; |
117 | } |
118 | |
119 | if (focus->contains_focus != contains_focus) |
120 | { |
121 | enter = contains_focus; |
122 | leave = !contains_focus; |
123 | } |
124 | |
125 | if (leave) |
126 | g_signal_emit (instance: controller, signal_id: signals[LEAVE], detail: 0); |
127 | |
128 | g_object_freeze_notify (G_OBJECT (focus)); |
129 | if (focus->is_focus != is_focus) |
130 | { |
131 | focus->is_focus = is_focus; |
132 | g_object_notify (G_OBJECT (focus), property_name: "is-focus" ); |
133 | } |
134 | |
135 | if (focus->contains_focus != contains_focus) |
136 | { |
137 | focus->contains_focus = contains_focus; |
138 | g_object_notify (G_OBJECT (focus), property_name: "contains-focus" ); |
139 | } |
140 | g_object_thaw_notify (G_OBJECT (focus)); |
141 | |
142 | if (enter) |
143 | g_signal_emit (instance: controller, signal_id: signals[ENTER], detail: 0); |
144 | } |
145 | |
146 | static void |
147 | gtk_event_controller_focus_handle_crossing (GtkEventController *controller, |
148 | const GtkCrossingData *crossing, |
149 | double x, |
150 | double y) |
151 | { |
152 | if (crossing->type == GTK_CROSSING_FOCUS || |
153 | crossing->type == GTK_CROSSING_ACTIVE) |
154 | update_focus (controller, crossing); |
155 | } |
156 | |
157 | static void |
158 | gtk_event_controller_focus_get_property (GObject *object, |
159 | guint prop_id, |
160 | GValue *value, |
161 | GParamSpec *pspec) |
162 | { |
163 | GtkEventControllerFocus *controller = GTK_EVENT_CONTROLLER_FOCUS (object); |
164 | |
165 | switch (prop_id) |
166 | { |
167 | case PROP_IS_FOCUS: |
168 | g_value_set_boolean (value, v_boolean: controller->is_focus); |
169 | break; |
170 | |
171 | case PROP_CONTAINS_FOCUS: |
172 | g_value_set_boolean (value, v_boolean: controller->contains_focus); |
173 | break; |
174 | |
175 | default: |
176 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
177 | } |
178 | } |
179 | |
180 | static void |
181 | gtk_event_controller_focus_class_init (GtkEventControllerFocusClass *klass) |
182 | { |
183 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
184 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
185 | |
186 | object_class->finalize = gtk_event_controller_focus_finalize; |
187 | object_class->get_property = gtk_event_controller_focus_get_property; |
188 | controller_class->handle_crossing = gtk_event_controller_focus_handle_crossing; |
189 | |
190 | /** |
191 | * GtkEventControllerFocus:is-focus: (attributes org.gtk.Property.get=gtk_event_controller_focus_is_focus) |
192 | * |
193 | * %TRUE if focus is in the controllers widget itself, |
194 | * as opposed to in a descendent widget. |
195 | * |
196 | * See also [property@Gtk.EventControllerFocus:contains-focus]. |
197 | * |
198 | * When handling focus events, this property is updated |
199 | * before [signal@Gtk.EventControllerFocus::enter] or |
200 | * [signal@Gtk.EventControllerFocus::leave] are emitted. |
201 | */ |
202 | props[PROP_IS_FOCUS] = |
203 | g_param_spec_boolean (name: "is-focus" , |
204 | P_("Is Focus" ), |
205 | P_("Whether the focus is in the controllers widget" ), |
206 | FALSE, |
207 | flags: G_PARAM_READABLE); |
208 | |
209 | /** |
210 | * GtkEventControllerFocus:contains-focus: (attributes org.gtk.Property.get=gtk_event_controller_focus_contains_focus) |
211 | * |
212 | * %TRUE if focus is contained in the controllers widget. |
213 | * |
214 | * See [property@Gtk.EventControllerFocus:is-focus] for whether |
215 | * the focus is in the widget itself or inside a descendent. |
216 | * |
217 | * When handling focus events, this property is updated |
218 | * before [signal@Gtk.EventControllerFocus::enter] or |
219 | * [signal@Gtk.EventControllerFocus::leave] are emitted. |
220 | */ |
221 | props[PROP_CONTAINS_FOCUS] = |
222 | g_param_spec_boolean (name: "contains-focus" , |
223 | P_("Contains Focus" ), |
224 | P_("Whether the focus is in a descendant of the controllers widget" ), |
225 | FALSE, |
226 | flags: G_PARAM_READABLE); |
227 | |
228 | g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: props); |
229 | |
230 | /** |
231 | * GtkEventControllerFocus::enter: |
232 | * @controller: the object which received the signal |
233 | * |
234 | * Emitted whenever the focus enters into the widget or one |
235 | * of its descendents. |
236 | * |
237 | * Note that this means you may not get an ::enter signal |
238 | * even though the widget becomes the focus location, in |
239 | * certain cases (such as when the focus moves from a descendent |
240 | * of the widget to the widget itself). If you are interested |
241 | * in these cases, you can monitor the |
242 | * [property@Gtk.EventControllerFocus:is-focus] |
243 | * property for changes. |
244 | */ |
245 | signals[ENTER] = |
246 | g_signal_new (I_("enter" ), |
247 | GTK_TYPE_EVENT_CONTROLLER_FOCUS, |
248 | signal_flags: G_SIGNAL_RUN_LAST, |
249 | class_offset: 0, NULL, NULL, |
250 | NULL, |
251 | G_TYPE_NONE, n_params: 0); |
252 | |
253 | /** |
254 | * GtkEventControllerFocus::leave: |
255 | * @controller: the object which received the signal |
256 | * |
257 | * Emitted whenever the focus leaves the widget hierarchy |
258 | * that is rooted at the widget that the controller is attached to. |
259 | * |
260 | * Note that this means you may not get a ::leave signal |
261 | * even though the focus moves away from the widget, in |
262 | * certain cases (such as when the focus moves from the widget |
263 | * to a descendent). If you are interested in these cases, you |
264 | * can monitor the [property@Gtk.EventControllerFocus:is-focus] |
265 | * property for changes. |
266 | */ |
267 | signals[LEAVE] = |
268 | g_signal_new (I_("leave" ), |
269 | GTK_TYPE_EVENT_CONTROLLER_FOCUS, |
270 | signal_flags: G_SIGNAL_RUN_LAST, |
271 | class_offset: 0, NULL, NULL, |
272 | NULL, |
273 | G_TYPE_NONE, n_params: 0); |
274 | } |
275 | |
276 | static void |
277 | gtk_event_controller_focus_init (GtkEventControllerFocus *controller) |
278 | { |
279 | } |
280 | |
281 | /** |
282 | * gtk_event_controller_focus_new: |
283 | * |
284 | * Creates a new event controller that will handle focus events. |
285 | * |
286 | * Returns: a new `GtkEventControllerFocus` |
287 | **/ |
288 | GtkEventController * |
289 | gtk_event_controller_focus_new (void) |
290 | { |
291 | return g_object_new (GTK_TYPE_EVENT_CONTROLLER_FOCUS, NULL); |
292 | } |
293 | |
294 | /** |
295 | * gtk_event_controller_focus_contains_focus: (attributes org.gtk.Method.get_property=contains-focus) |
296 | * @self: a `GtkEventControllerFocus` |
297 | * |
298 | * Returns %TRUE if focus is within @self or one of its children. |
299 | * |
300 | * Returns: %TRUE if focus is within @self or one of its children |
301 | */ |
302 | gboolean |
303 | gtk_event_controller_focus_contains_focus (GtkEventControllerFocus *self) |
304 | { |
305 | g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (self), FALSE); |
306 | |
307 | return self->contains_focus; |
308 | } |
309 | |
310 | /** |
311 | * gtk_event_controller_focus_is_focus: (attributes org.gtk.Method.get_property=is-focus) |
312 | * @self: a `GtkEventControllerFocus` |
313 | * |
314 | * Returns %TRUE if focus is within @self, but not one of its children. |
315 | * |
316 | * Returns: %TRUE if focus is within @self, but not one of its children |
317 | */ |
318 | gboolean |
319 | gtk_event_controller_focus_is_focus (GtkEventControllerFocus *self) |
320 | { |
321 | g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_FOCUS (self), FALSE); |
322 | |
323 | return self->is_focus; |
324 | } |
325 | |