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
48struct _GtkEventControllerFocus
49{
50 GtkEventController parent_instance;
51
52 guint is_focus : 1;
53 guint contains_focus : 1;
54};
55
56struct _GtkEventControllerFocusClass
57{
58 GtkEventControllerClass parent_class;
59};
60
61enum {
62 ENTER,
63 LEAVE,
64 N_SIGNALS
65};
66
67static guint signals[N_SIGNALS] = { 0 };
68
69enum {
70 PROP_IS_FOCUS = 1,
71 PROP_CONTAINS_FOCUS,
72 NUM_PROPERTIES
73};
74
75static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
76
77G_DEFINE_TYPE (GtkEventControllerFocus, gtk_event_controller_focus,
78 GTK_TYPE_EVENT_CONTROLLER)
79
80static void
81gtk_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
88static void
89update_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
146static void
147gtk_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
157static void
158gtk_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
180static void
181gtk_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
276static void
277gtk_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 **/
288GtkEventController *
289gtk_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 */
302gboolean
303gtk_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 */
318gboolean
319gtk_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

source code of gtk/gtk/gtkeventcontrollerfocus.c