1 | /* |
2 | * Copyright © 2018 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 | /** |
22 | * GtkShortcutController: |
23 | * |
24 | * `GtkShortcutController` is an event controller that manages shortcuts. |
25 | * |
26 | * Most common shortcuts are using this controller implicitly, e.g. by |
27 | * adding a mnemonic underline to a `GtkLabel`, or by installing a key |
28 | * binding using [method@Gtk.WidgetClass.add_binding], or by adding accelerators |
29 | * to global actions using [method@Gtk.Application.set_accels_for_action]. |
30 | * |
31 | * But it is possible to create your own shortcut controller, and add |
32 | * shortcuts to it. |
33 | * |
34 | * `GtkShortcutController` implements `GListModel` for querying the |
35 | * shortcuts that have been added to it. |
36 | * |
37 | * # GtkShortcutController as a GtkBuildable |
38 | * |
39 | * `GtkShortcutControllers` can be creates in ui files to set up |
40 | * shortcuts in the same place as the widgets. |
41 | * |
42 | * An example of a UI definition fragment with `GtkShortcutController`: |
43 | * ```xml |
44 | * <object class='GtkButton'> |
45 | * <child> |
46 | * <object class='GtkShortcutController'> |
47 | * <property name='scope'>managed</property> |
48 | * <child> |
49 | * <object class='GtkShortcut'> |
50 | * <property name='trigger'><Control>k</property> |
51 | * <property name='action'>activate</property> |
52 | * </object> |
53 | * </child> |
54 | * </object> |
55 | * </child> |
56 | * </object> |
57 | * ``` |
58 | * |
59 | * This example creates a [class@Gtk.ActivateAction] for triggering the |
60 | * `activate` signal of the `GtkButton`. See [ctor@Gtk.ShortcutAction.parse_string] |
61 | * for the syntax for other kinds of `GtkShortcutAction`. See |
62 | * [ctor@Gtk.ShortcutTrigger.parse_string] to learn more about the syntax |
63 | * for triggers. |
64 | */ |
65 | |
66 | #include "config.h" |
67 | |
68 | #include "gtkshortcutcontrollerprivate.h" |
69 | |
70 | #include "gtkflattenlistmodel.h" |
71 | #include "gtkbuildable.h" |
72 | #include "gtkeventcontrollerprivate.h" |
73 | #include "gtkintl.h" |
74 | #include "gtkshortcut.h" |
75 | #include "gtkshortcutmanager.h" |
76 | #include "gtkshortcuttrigger.h" |
77 | #include "gtktypebuiltins.h" |
78 | #include "gtkwidgetprivate.h" |
79 | #include "gtknative.h" |
80 | #include "gtkdebug.h" |
81 | #include "gtkmodelbuttonprivate.h" |
82 | |
83 | #include <gdk/gdk.h> |
84 | |
85 | struct _GtkShortcutController |
86 | { |
87 | GtkEventController parent_instance; |
88 | |
89 | GListModel *shortcuts; |
90 | GtkShortcutScope scope; |
91 | GdkModifierType mnemonics_modifiers; |
92 | |
93 | gulong shortcuts_changed_id; |
94 | guint custom_shortcuts : 1; |
95 | |
96 | guint last_activated; |
97 | }; |
98 | |
99 | struct _GtkShortcutControllerClass |
100 | { |
101 | GtkEventControllerClass parent_class; |
102 | }; |
103 | |
104 | enum { |
105 | PROP_0, |
106 | PROP_MNEMONICS_MODIFIERS, |
107 | PROP_MODEL, |
108 | PROP_SCOPE, |
109 | |
110 | N_PROPS |
111 | }; |
112 | |
113 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
114 | |
115 | static GType |
116 | gtk_shortcut_controller_list_model_get_item_type (GListModel *list) |
117 | { |
118 | return G_TYPE_OBJECT; |
119 | } |
120 | |
121 | static guint |
122 | gtk_shortcut_controller_list_model_get_n_items (GListModel *list) |
123 | { |
124 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (list); |
125 | |
126 | return g_list_model_get_n_items (list: self->shortcuts); |
127 | } |
128 | |
129 | static gpointer |
130 | gtk_shortcut_controller_list_model_get_item (GListModel *list, |
131 | guint position) |
132 | { |
133 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (list); |
134 | |
135 | return g_list_model_get_item (list: self->shortcuts, position); |
136 | } |
137 | |
138 | static void |
139 | gtk_shortcut_controller_list_model_init (GListModelInterface *iface) |
140 | { |
141 | iface->get_item_type = gtk_shortcut_controller_list_model_get_item_type; |
142 | iface->get_n_items = gtk_shortcut_controller_list_model_get_n_items; |
143 | iface->get_item = gtk_shortcut_controller_list_model_get_item; |
144 | } |
145 | |
146 | static void |
147 | gtk_shortcut_controller_buildable_add_child (GtkBuildable *buildable, |
148 | GtkBuilder *builder, |
149 | GObject *child, |
150 | const char *type) |
151 | { |
152 | if (type != NULL) |
153 | { |
154 | GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type); |
155 | } |
156 | if (GTK_IS_SHORTCUT (ptr: child)) |
157 | { |
158 | gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (buildable), g_object_ref (GTK_SHORTCUT (child))); |
159 | } |
160 | else |
161 | { |
162 | g_warning ("Cannot add an object of type %s to a controller of type %s" , |
163 | g_type_name (G_OBJECT_TYPE (child)), g_type_name (G_OBJECT_TYPE (buildable))); |
164 | } |
165 | } |
166 | |
167 | static void |
168 | gtk_shortcut_controller_buildable_init (GtkBuildableIface *iface) |
169 | { |
170 | iface->add_child = gtk_shortcut_controller_buildable_add_child; |
171 | } |
172 | |
173 | G_DEFINE_TYPE_WITH_CODE (GtkShortcutController, gtk_shortcut_controller, |
174 | GTK_TYPE_EVENT_CONTROLLER, |
175 | G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_shortcut_controller_list_model_init) |
176 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_shortcut_controller_buildable_init)) |
177 | |
178 | static gboolean |
179 | gtk_shortcut_controller_is_rooted (GtkShortcutController *self) |
180 | { |
181 | GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
182 | |
183 | if (widget == NULL) |
184 | return FALSE; |
185 | |
186 | return gtk_widget_get_root (widget) != NULL; |
187 | } |
188 | |
189 | static void |
190 | gtk_shortcut_controller_set_property (GObject *object, |
191 | guint prop_id, |
192 | const GValue *value, |
193 | GParamSpec *pspec) |
194 | { |
195 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object); |
196 | |
197 | switch (prop_id) |
198 | { |
199 | case PROP_MNEMONICS_MODIFIERS: |
200 | gtk_shortcut_controller_set_mnemonics_modifiers (self, modifiers: g_value_get_flags (value)); |
201 | break; |
202 | |
203 | case PROP_MODEL: |
204 | { |
205 | GListModel *model = g_value_get_object (value); |
206 | if (model == NULL) |
207 | { |
208 | self->shortcuts = G_LIST_MODEL (ptr: g_list_store_new (GTK_TYPE_SHORTCUT)); |
209 | self->custom_shortcuts = TRUE; |
210 | } |
211 | else |
212 | { |
213 | self->shortcuts = g_object_ref (model); |
214 | self->custom_shortcuts = FALSE; |
215 | } |
216 | |
217 | self->shortcuts_changed_id = g_signal_connect_swapped (self->shortcuts, |
218 | "items-changed" , |
219 | G_CALLBACK (g_list_model_items_changed), |
220 | self); |
221 | } |
222 | break; |
223 | |
224 | case PROP_SCOPE: |
225 | gtk_shortcut_controller_set_scope (self, scope: g_value_get_enum (value)); |
226 | break; |
227 | |
228 | default: |
229 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
230 | } |
231 | } |
232 | |
233 | static void |
234 | gtk_shortcut_controller_get_property (GObject *object, |
235 | guint prop_id, |
236 | GValue *value, |
237 | GParamSpec *pspec) |
238 | { |
239 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object); |
240 | |
241 | switch (prop_id) |
242 | { |
243 | case PROP_MNEMONICS_MODIFIERS: |
244 | g_value_set_flags (value, v_flags: self->mnemonics_modifiers); |
245 | break; |
246 | |
247 | case PROP_SCOPE: |
248 | g_value_set_enum (value, v_enum: self->scope); |
249 | break; |
250 | |
251 | default: |
252 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
253 | } |
254 | } |
255 | |
256 | static void |
257 | gtk_shortcut_controller_dispose (GObject *object) |
258 | { |
259 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object); |
260 | |
261 | if (self->custom_shortcuts) |
262 | g_list_store_remove_all (store: G_LIST_STORE (ptr: self->shortcuts)); |
263 | |
264 | G_OBJECT_CLASS (gtk_shortcut_controller_parent_class)->dispose (object); |
265 | } |
266 | |
267 | static void |
268 | gtk_shortcut_controller_finalize (GObject *object) |
269 | { |
270 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object); |
271 | |
272 | g_clear_signal_handler (&self->shortcuts_changed_id, self->shortcuts); |
273 | g_clear_object (&self->shortcuts); |
274 | |
275 | G_OBJECT_CLASS (gtk_shortcut_controller_parent_class)->finalize (object); |
276 | } |
277 | |
278 | typedef struct { |
279 | GtkShortcut *shortcut; |
280 | GtkWidget *widget; |
281 | guint index; |
282 | } ShortcutData; |
283 | |
284 | static void |
285 | shortcut_data_free (gpointer data) |
286 | { |
287 | ShortcutData *sdata = data; |
288 | |
289 | g_object_unref (object: sdata->shortcut); |
290 | } |
291 | |
292 | static gboolean |
293 | gtk_shortcut_controller_run_controllers (GtkEventController *controller, |
294 | GdkEvent *event, |
295 | double x, |
296 | double y, |
297 | gboolean enable_mnemonics) |
298 | { |
299 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller); |
300 | int i, p; |
301 | GArray *shortcuts = NULL; |
302 | gboolean has_exact = FALSE; |
303 | gboolean retval = FALSE; |
304 | |
305 | for (i = 0, p = g_list_model_get_n_items (list: self->shortcuts); i < p; i++) |
306 | { |
307 | GtkShortcut *shortcut; |
308 | ShortcutData *data; |
309 | guint index; |
310 | GtkWidget *widget; |
311 | GtkNative *native; |
312 | |
313 | /* This is not entirely right, but we only want to do round-robin cycling |
314 | * for mnemonics. |
315 | */ |
316 | if (enable_mnemonics) |
317 | index = (self->last_activated + 1 + i) % g_list_model_get_n_items (list: self->shortcuts); |
318 | else |
319 | index = i; |
320 | |
321 | shortcut = g_list_model_get_item (list: self->shortcuts, position: index); |
322 | if (!GTK_IS_SHORTCUT (ptr: shortcut)) |
323 | { |
324 | g_object_unref (object: shortcut); |
325 | continue; |
326 | } |
327 | |
328 | switch (gtk_shortcut_trigger_trigger (self: gtk_shortcut_get_trigger (self: shortcut), event, enable_mnemonics)) |
329 | { |
330 | case GDK_KEY_MATCH_PARTIAL: |
331 | if (!has_exact) |
332 | break; |
333 | G_GNUC_FALLTHROUGH; |
334 | |
335 | case GDK_KEY_MATCH_NONE: |
336 | g_object_unref (object: shortcut); |
337 | continue; |
338 | |
339 | case GDK_KEY_MATCH_EXACT: |
340 | if (!has_exact) |
341 | { |
342 | if (shortcuts) |
343 | g_array_set_size (array: shortcuts, length: 0); |
344 | } |
345 | has_exact = TRUE; |
346 | break; |
347 | |
348 | default: |
349 | g_assert_not_reached (); |
350 | } |
351 | |
352 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
353 | if (!self->custom_shortcuts && |
354 | GTK_IS_FLATTEN_LIST_MODEL (ptr: self->shortcuts)) |
355 | { |
356 | GListModel *model = gtk_flatten_list_model_get_model_for_item (self: GTK_FLATTEN_LIST_MODEL (ptr: self->shortcuts), position: index); |
357 | if (GTK_IS_SHORTCUT_CONTROLLER (model)) |
358 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (model)); |
359 | } |
360 | |
361 | if (!_gtk_widget_is_sensitive (widget) || |
362 | !_gtk_widget_get_mapped (widget)) |
363 | { |
364 | g_object_unref (object: shortcut); |
365 | continue; |
366 | } |
367 | |
368 | native = gtk_widget_get_native (widget); |
369 | if (!native || |
370 | !gdk_surface_get_mapped (surface: gtk_native_get_surface (self: native))) |
371 | { |
372 | g_object_unref (object: shortcut); |
373 | continue; |
374 | } |
375 | |
376 | if (G_UNLIKELY (!shortcuts)) |
377 | { |
378 | shortcuts = g_array_sized_new (FALSE, TRUE, element_size: sizeof (ShortcutData), reserved_size: 8); |
379 | g_array_set_clear_func (array: shortcuts, clear_func: shortcut_data_free); |
380 | } |
381 | |
382 | g_array_set_size (array: shortcuts, length: shortcuts->len + 1); |
383 | data = &g_array_index (shortcuts, ShortcutData, shortcuts->len - 1); |
384 | data->shortcut = shortcut; |
385 | data->index = index; |
386 | data->widget = widget; |
387 | } |
388 | |
389 | #ifdef G_ENABLE_DEBUG |
390 | if (GTK_DEBUG_CHECK (KEYBINDINGS)) |
391 | { |
392 | g_message ("Found %u shortcuts triggered %s by %s %u %u" , |
393 | shortcuts ? shortcuts->len : 0, |
394 | has_exact ? "exactly" : "approximately" , |
395 | gdk_event_get_event_type (event) == GDK_KEY_PRESS ? "key press" : "key release" , |
396 | gdk_key_event_get_keyval (event), |
397 | gdk_event_get_modifier_state (event)); |
398 | } |
399 | #endif |
400 | |
401 | if (!shortcuts) |
402 | return retval; |
403 | |
404 | p = shortcuts->len; |
405 | for (i = 0; i < shortcuts->len; i++) |
406 | { |
407 | const ShortcutData *data = &g_array_index (shortcuts, ShortcutData, i); |
408 | |
409 | if (gtk_shortcut_action_activate (self: gtk_shortcut_get_action (self: data->shortcut), |
410 | flags: i == p - 1 ? GTK_SHORTCUT_ACTION_EXCLUSIVE : 0, |
411 | widget: data->widget, |
412 | args: gtk_shortcut_get_arguments (self: data->shortcut))) |
413 | { |
414 | self->last_activated = data->index; |
415 | retval = TRUE; |
416 | break; |
417 | } |
418 | } |
419 | |
420 | g_array_free (array: shortcuts, TRUE); |
421 | |
422 | return retval; |
423 | } |
424 | |
425 | static gboolean |
426 | gtk_shortcut_controller_handle_event (GtkEventController *controller, |
427 | GdkEvent *event, |
428 | double x, |
429 | double y) |
430 | { |
431 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller); |
432 | GdkEventType event_type = gdk_event_get_event_type (event); |
433 | gboolean enable_mnemonics; |
434 | |
435 | if (self->scope != GTK_SHORTCUT_SCOPE_LOCAL) |
436 | return FALSE; |
437 | |
438 | if (event_type != GDK_KEY_PRESS && event_type != GDK_KEY_RELEASE) |
439 | return FALSE; |
440 | |
441 | if (event_type == GDK_KEY_PRESS) |
442 | { |
443 | GdkModifierType modifiers, consumed_modifiers; |
444 | |
445 | modifiers = gdk_event_get_modifier_state (event); |
446 | consumed_modifiers = gdk_key_event_get_consumed_modifiers (event); |
447 | enable_mnemonics = (modifiers & ~consumed_modifiers & gtk_accelerator_get_default_mod_mask ()) == self->mnemonics_modifiers; |
448 | } |
449 | else |
450 | { |
451 | enable_mnemonics = FALSE; |
452 | } |
453 | |
454 | return gtk_shortcut_controller_run_controllers (controller, event, x, y, enable_mnemonics); |
455 | } |
456 | |
457 | static void |
458 | update_accel (GtkShortcut *shortcut, |
459 | GtkActionMuxer *muxer, |
460 | gboolean set) |
461 | { |
462 | GtkShortcutTrigger *trigger; |
463 | GtkShortcutAction *action; |
464 | GVariant *target; |
465 | const char *action_name; |
466 | char *action_and_target; |
467 | char *accel = NULL; |
468 | |
469 | if (!muxer) |
470 | return; |
471 | |
472 | action = gtk_shortcut_get_action (self: shortcut); |
473 | if (!GTK_IS_NAMED_ACTION (ptr: action)) |
474 | return; |
475 | |
476 | trigger = gtk_shortcut_get_trigger (self: shortcut); |
477 | if (!GTK_IS_KEYVAL_TRIGGER (ptr: trigger)) |
478 | return; |
479 | |
480 | target = gtk_shortcut_get_arguments (self: shortcut); |
481 | action_name = gtk_named_action_get_action_name (self: GTK_NAMED_ACTION (ptr: action)); |
482 | action_and_target = gtk_print_action_and_target (NULL, action_name, target); |
483 | if (set) |
484 | accel = gtk_shortcut_trigger_to_string (self: trigger); |
485 | gtk_action_muxer_set_primary_accel (muxer, action_and_target, primary_accel: accel); |
486 | |
487 | g_free (mem: action_and_target); |
488 | g_free (mem: accel); |
489 | } |
490 | |
491 | void |
492 | gtk_shortcut_controller_update_accels (GtkShortcutController *self) |
493 | { |
494 | GListModel *shortcuts = self->shortcuts; |
495 | GtkWidget *widget; |
496 | GtkActionMuxer *muxer; |
497 | guint i, p; |
498 | |
499 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
500 | if (!widget || GTK_IS_MODEL_BUTTON (widget)) |
501 | return; |
502 | |
503 | muxer = _gtk_widget_get_action_muxer (widget, TRUE); |
504 | for (i = 0, p = g_list_model_get_n_items (list: shortcuts); i < p; i++) |
505 | { |
506 | GtkShortcut *shortcut = g_list_model_get_item (list: shortcuts, position: i); |
507 | if (GTK_IS_SHORTCUT (ptr: shortcut)) |
508 | update_accel (shortcut, muxer, TRUE); |
509 | g_object_unref (object: shortcut); |
510 | } |
511 | } |
512 | |
513 | static void |
514 | gtk_shortcut_controller_set_widget (GtkEventController *controller, |
515 | GtkWidget *widget) |
516 | { |
517 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller); |
518 | |
519 | GTK_EVENT_CONTROLLER_CLASS (gtk_shortcut_controller_parent_class)->set_widget (controller, widget); |
520 | |
521 | gtk_shortcut_controller_update_accels (self); |
522 | |
523 | if (_gtk_widget_get_root (widget)) |
524 | gtk_shortcut_controller_root (controller: self); |
525 | } |
526 | |
527 | static void |
528 | gtk_shortcut_controller_unset_widget (GtkEventController *controller) |
529 | { |
530 | GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller); |
531 | GtkWidget *widget = gtk_event_controller_get_widget (controller); |
532 | |
533 | if (_gtk_widget_get_root (widget)) |
534 | gtk_shortcut_controller_unroot (controller: self); |
535 | |
536 | #if 0 |
537 | int i; |
538 | for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (controller)); i++) |
539 | { |
540 | GtkShortcut *shortcut = g_list_model_get_item (G_LIST_MODEL (controller), i); |
541 | if (GTK_IS_SHORTCUT (shortcut)) |
542 | update_accel (shortcut, widget, FALSE); |
543 | g_object_unref (shortcut); |
544 | } |
545 | #endif |
546 | |
547 | GTK_EVENT_CONTROLLER_CLASS (gtk_shortcut_controller_parent_class)->unset_widget (controller); |
548 | } |
549 | |
550 | static void |
551 | gtk_shortcut_controller_class_init (GtkShortcutControllerClass *klass) |
552 | { |
553 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
554 | GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); |
555 | |
556 | object_class->dispose = gtk_shortcut_controller_dispose; |
557 | object_class->finalize = gtk_shortcut_controller_finalize; |
558 | object_class->set_property = gtk_shortcut_controller_set_property; |
559 | object_class->get_property = gtk_shortcut_controller_get_property; |
560 | |
561 | controller_class->handle_event = gtk_shortcut_controller_handle_event; |
562 | controller_class->set_widget = gtk_shortcut_controller_set_widget; |
563 | controller_class->unset_widget = gtk_shortcut_controller_unset_widget; |
564 | |
565 | /** |
566 | * GtkShortcutController:mnemonic-modifiers: (attributes org.gtk.Property.get=gtk_shortcut_controller_get_mnemonics_modifiers org.gtk.Property.set=gtk_shortcut_controller_set_mnemonics_modifiers) |
567 | * |
568 | * The modifiers that need to be pressed to allow mnemonics activation. |
569 | */ |
570 | properties[PROP_MNEMONICS_MODIFIERS] = |
571 | g_param_spec_flags (name: "mnemonic-modifiers" , |
572 | P_("Mnemonic modifiers" ), |
573 | P_("The modifiers to be pressed to allow mnemonics activation" ), |
574 | flags_type: GDK_TYPE_MODIFIER_TYPE, |
575 | default_value: GDK_ALT_MASK, |
576 | flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
577 | |
578 | /** |
579 | * GtkShortcutController:model: |
580 | * |
581 | * A list model to take shortcuts from. |
582 | */ |
583 | properties[PROP_MODEL] = |
584 | g_param_spec_object (name: "model" , |
585 | P_("Model" ), |
586 | P_("A list model to take shortcuts from" ), |
587 | G_TYPE_LIST_MODEL, |
588 | flags: G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
589 | |
590 | /** |
591 | * GtkShortcutController:scope: (attributes org.gtk.Property.get=gtk_shortcut_controller_get_scope org.gtk.Property.set=gtk_shortcut_controller_set_scope) |
592 | * |
593 | * What scope the shortcuts will be handled in. |
594 | */ |
595 | properties[PROP_SCOPE] = |
596 | g_param_spec_enum (name: "scope" , |
597 | P_("Scope" ), |
598 | P_("What scope the shortcuts will be handled in" ), |
599 | enum_type: GTK_TYPE_SHORTCUT_SCOPE, |
600 | default_value: GTK_SHORTCUT_SCOPE_LOCAL, |
601 | flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
602 | |
603 | g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPS, pspecs: properties); |
604 | } |
605 | |
606 | static void |
607 | gtk_shortcut_controller_init (GtkShortcutController *self) |
608 | { |
609 | self->mnemonics_modifiers = GDK_ALT_MASK; |
610 | } |
611 | |
612 | void |
613 | gtk_shortcut_controller_root (GtkShortcutController *self) |
614 | { |
615 | GtkShortcutManager *manager; |
616 | |
617 | switch (self->scope) |
618 | { |
619 | case GTK_SHORTCUT_SCOPE_LOCAL: |
620 | return; |
621 | |
622 | case GTK_SHORTCUT_SCOPE_MANAGED: |
623 | { |
624 | GtkWidget *widget; |
625 | |
626 | for (widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
627 | !GTK_IS_SHORTCUT_MANAGER (ptr: widget); |
628 | widget = _gtk_widget_get_parent (widget)) |
629 | ; |
630 | |
631 | if (!GTK_IS_SHORTCUT_MANAGER (ptr: widget)) |
632 | return; |
633 | |
634 | manager = GTK_SHORTCUT_MANAGER (ptr: widget); |
635 | } |
636 | break; |
637 | |
638 | case GTK_SHORTCUT_SCOPE_GLOBAL: |
639 | { |
640 | GtkRoot *root = gtk_widget_get_root (widget: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self))); |
641 | |
642 | if (!GTK_IS_SHORTCUT_MANAGER (ptr: root)) |
643 | return; |
644 | |
645 | manager = GTK_SHORTCUT_MANAGER (ptr: root); |
646 | } |
647 | break; |
648 | |
649 | default: |
650 | g_assert_not_reached (); |
651 | return; |
652 | } |
653 | |
654 | GTK_SHORTCUT_MANAGER_GET_IFACE (ptr: manager)->add_controller (manager, self); |
655 | } |
656 | |
657 | void |
658 | gtk_shortcut_controller_unroot (GtkShortcutController *self) |
659 | { |
660 | GtkShortcutManager *manager; |
661 | |
662 | switch (self->scope) |
663 | { |
664 | case GTK_SHORTCUT_SCOPE_LOCAL: |
665 | return; |
666 | |
667 | case GTK_SHORTCUT_SCOPE_MANAGED: |
668 | { |
669 | GtkWidget *widget; |
670 | |
671 | for (widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
672 | !GTK_IS_SHORTCUT_MANAGER (ptr: widget); |
673 | widget = _gtk_widget_get_parent (widget)) |
674 | ; |
675 | |
676 | if (!GTK_IS_SHORTCUT_MANAGER (ptr: widget)) |
677 | return; |
678 | |
679 | manager = GTK_SHORTCUT_MANAGER (ptr: widget); |
680 | } |
681 | break; |
682 | |
683 | case GTK_SHORTCUT_SCOPE_GLOBAL: |
684 | { |
685 | GtkRoot *root = gtk_widget_get_root (widget: gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self))); |
686 | |
687 | if (!GTK_IS_SHORTCUT_MANAGER (ptr: root)) |
688 | return; |
689 | |
690 | manager = GTK_SHORTCUT_MANAGER (ptr: root); |
691 | } |
692 | break; |
693 | |
694 | default: |
695 | g_assert_not_reached (); |
696 | return; |
697 | } |
698 | |
699 | GTK_SHORTCUT_MANAGER_GET_IFACE (ptr: manager)->remove_controller (manager, self); |
700 | } |
701 | |
702 | /** |
703 | * gtk_shortcut_controller_new: |
704 | * |
705 | * Creates a new shortcut controller. |
706 | * |
707 | * Returns: a newly created shortcut controller |
708 | */ |
709 | GtkEventController * |
710 | gtk_shortcut_controller_new (void) |
711 | { |
712 | return g_object_new (GTK_TYPE_SHORTCUT_CONTROLLER, |
713 | NULL); |
714 | } |
715 | |
716 | /** |
717 | * gtk_shortcut_controller_new_for_model: |
718 | * @model: a `GListModel` containing shortcuts |
719 | * |
720 | * Creates a new shortcut controller that takes its shortcuts from |
721 | * the given list model. |
722 | * |
723 | * A controller created by this function does not let you add or |
724 | * remove individual shortcuts using the shortcut controller api, |
725 | * but you can change the contents of the model. |
726 | * |
727 | * Returns: a newly created shortcut controller |
728 | */ |
729 | GtkEventController * |
730 | gtk_shortcut_controller_new_for_model (GListModel *model) |
731 | { |
732 | g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); |
733 | |
734 | return g_object_new (GTK_TYPE_SHORTCUT_CONTROLLER, |
735 | first_property_name: "model" , model, |
736 | NULL); |
737 | } |
738 | |
739 | /** |
740 | * gtk_shortcut_controller_add_shortcut: |
741 | * @self: the controller |
742 | * @shortcut: (transfer full): a `GtkShortcut` |
743 | * |
744 | * Adds @shortcut to the list of shortcuts handled by @self. |
745 | * |
746 | * If this controller uses an external shortcut list, this |
747 | * function does nothing. |
748 | */ |
749 | void |
750 | gtk_shortcut_controller_add_shortcut (GtkShortcutController *self, |
751 | GtkShortcut *shortcut) |
752 | { |
753 | GtkWidget *widget; |
754 | |
755 | g_return_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self)); |
756 | g_return_if_fail (GTK_IS_SHORTCUT (shortcut)); |
757 | |
758 | if (!self->custom_shortcuts) |
759 | { |
760 | g_object_unref (object: shortcut); |
761 | return; |
762 | } |
763 | |
764 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
765 | if (widget) |
766 | { |
767 | GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, TRUE); |
768 | |
769 | update_accel (shortcut, muxer, TRUE); |
770 | } |
771 | |
772 | g_list_store_append (store: G_LIST_STORE (ptr: self->shortcuts), item: shortcut); |
773 | g_object_unref (object: shortcut); |
774 | } |
775 | |
776 | /** |
777 | * gtk_shortcut_controller_remove_shortcut: |
778 | * @self: the controller |
779 | * @shortcut: a `GtkShortcut` |
780 | * |
781 | * Removes @shortcut from the list of shortcuts handled by @self. |
782 | * |
783 | * If @shortcut had not been added to @controller or this controller |
784 | * uses an external shortcut list, this function does nothing. |
785 | **/ |
786 | void |
787 | gtk_shortcut_controller_remove_shortcut (GtkShortcutController *self, |
788 | GtkShortcut *shortcut) |
789 | { |
790 | GtkWidget *widget; |
791 | guint i; |
792 | |
793 | g_return_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self)); |
794 | g_return_if_fail (GTK_IS_SHORTCUT (shortcut)); |
795 | |
796 | if (!self->custom_shortcuts) |
797 | return; |
798 | |
799 | widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self)); |
800 | if (widget) |
801 | { |
802 | GtkActionMuxer *muxer = _gtk_widget_get_action_muxer (widget, FALSE); |
803 | |
804 | update_accel (shortcut, muxer, FALSE); |
805 | } |
806 | |
807 | for (i = 0; i < g_list_model_get_n_items (list: self->shortcuts); i++) |
808 | { |
809 | GtkShortcut *item = g_list_model_get_item (list: self->shortcuts, position: i); |
810 | |
811 | if (item == shortcut) |
812 | { |
813 | g_object_unref (object: item); |
814 | g_list_store_remove (store: G_LIST_STORE (ptr: self->shortcuts), position: i); |
815 | return; |
816 | } |
817 | |
818 | g_object_unref (object: item); |
819 | } |
820 | } |
821 | |
822 | /** |
823 | * gtk_shortcut_controller_set_scope: (attributes org.gtk.Method.set_property=scope) |
824 | * @self: a `GtkShortcutController` |
825 | * @scope: the new scope to use |
826 | * |
827 | * Sets the controller to have the given @scope. |
828 | * |
829 | * The scope allows shortcuts to be activated outside of the normal |
830 | * event propagation. In particular, it allows installing global |
831 | * keyboard shortcuts that can be activated even when a widget does |
832 | * not have focus. |
833 | * |
834 | * With %GTK_SHORTCUT_SCOPE_LOCAL, shortcuts will only be activated |
835 | * when the widget has focus. |
836 | */ |
837 | void |
838 | gtk_shortcut_controller_set_scope (GtkShortcutController *self, |
839 | GtkShortcutScope scope) |
840 | { |
841 | gboolean rooted; |
842 | |
843 | g_return_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self)); |
844 | |
845 | if (self->scope == scope) |
846 | return; |
847 | |
848 | rooted = gtk_shortcut_controller_is_rooted (self); |
849 | |
850 | if (rooted) |
851 | gtk_shortcut_controller_unroot (self); |
852 | |
853 | self->scope = scope; |
854 | |
855 | if (rooted) |
856 | gtk_shortcut_controller_root (self); |
857 | |
858 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SCOPE]); |
859 | } |
860 | |
861 | /** |
862 | * gtk_shortcut_controller_get_scope: (attributes org.gtk.Method.get_property=scope) |
863 | * @self: a `GtkShortcutController` |
864 | * |
865 | * Gets the scope for when this controller activates its shortcuts. |
866 | * |
867 | * See [method@Gtk.ShortcutController.set_scope] for details. |
868 | * |
869 | * Returns: the controller's scope |
870 | */ |
871 | GtkShortcutScope |
872 | gtk_shortcut_controller_get_scope (GtkShortcutController *self) |
873 | { |
874 | g_return_val_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self), GTK_SHORTCUT_SCOPE_LOCAL); |
875 | |
876 | return self->scope; |
877 | } |
878 | |
879 | /** |
880 | * gtk_shortcut_controller_set_mnemonics_modifiers: (attributes org.gtk.MEthod.set_property=mnemonic-modifiers) |
881 | * @self: a `GtkShortcutController` |
882 | * @modifiers: the new mnemonics_modifiers to use |
883 | * |
884 | * Sets the controller to use the given modifier for mnemonics. |
885 | * |
886 | * The mnemonics modifiers determines which modifiers need to be pressed to allow |
887 | * activation of shortcuts with mnemonics triggers. |
888 | * |
889 | * GTK normally uses the Alt modifier for mnemonics, except in `GtkPopoverMenu`s, |
890 | * where mnemonics can be triggered without any modifiers. It should be very |
891 | * rarely necessary to change this, and doing so is likely to interfere with |
892 | * other shortcuts. |
893 | * |
894 | * This value is only relevant for local shortcut controllers. Global and managed |
895 | * shortcut controllers will have their shortcuts activated from other places which |
896 | * have their own modifiers for activating mnemonics. |
897 | */ |
898 | void |
899 | gtk_shortcut_controller_set_mnemonics_modifiers (GtkShortcutController *self, |
900 | GdkModifierType modifiers) |
901 | { |
902 | g_return_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self)); |
903 | |
904 | if (self->mnemonics_modifiers == modifiers) |
905 | return; |
906 | |
907 | self->mnemonics_modifiers = modifiers; |
908 | |
909 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MNEMONICS_MODIFIERS]); |
910 | } |
911 | |
912 | /** |
913 | * gtk_shortcut_controller_get_mnemonics_modifiers: (attributes org.gtk.Method.get_property=mnemonic-modifiers) |
914 | * @self: a `GtkShortcutController` |
915 | * |
916 | * Gets the mnemonics modifiers for when this controller activates its shortcuts. |
917 | * |
918 | * Returns: the controller's mnemonics modifiers |
919 | */ |
920 | GdkModifierType |
921 | gtk_shortcut_controller_get_mnemonics_modifiers (GtkShortcutController *self) |
922 | { |
923 | g_return_val_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self), 0); |
924 | |
925 | return self->mnemonics_modifiers; |
926 | } |
927 | |