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'>&lt;Control&gt;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
85struct _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
99struct _GtkShortcutControllerClass
100{
101 GtkEventControllerClass parent_class;
102};
103
104enum {
105 PROP_0,
106 PROP_MNEMONICS_MODIFIERS,
107 PROP_MODEL,
108 PROP_SCOPE,
109
110 N_PROPS
111};
112
113static GParamSpec *properties[N_PROPS] = { NULL, };
114
115static GType
116gtk_shortcut_controller_list_model_get_item_type (GListModel *list)
117{
118 return G_TYPE_OBJECT;
119}
120
121static guint
122gtk_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
129static gpointer
130gtk_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
138static void
139gtk_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
146static void
147gtk_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
167static void
168gtk_shortcut_controller_buildable_init (GtkBuildableIface *iface)
169{
170 iface->add_child = gtk_shortcut_controller_buildable_add_child;
171}
172
173G_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
178static gboolean
179gtk_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
189static void
190gtk_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
233static void
234gtk_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
256static void
257gtk_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
267static void
268gtk_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
278typedef struct {
279 GtkShortcut *shortcut;
280 GtkWidget *widget;
281 guint index;
282} ShortcutData;
283
284static void
285shortcut_data_free (gpointer data)
286{
287 ShortcutData *sdata = data;
288
289 g_object_unref (object: sdata->shortcut);
290}
291
292static gboolean
293gtk_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
425static gboolean
426gtk_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
457static void
458update_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
491void
492gtk_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
513static void
514gtk_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
527static void
528gtk_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
550static void
551gtk_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
606static void
607gtk_shortcut_controller_init (GtkShortcutController *self)
608{
609 self->mnemonics_modifiers = GDK_ALT_MASK;
610}
611
612void
613gtk_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
657void
658gtk_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 */
709GtkEventController *
710gtk_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 */
729GtkEventController *
730gtk_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 */
749void
750gtk_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 **/
786void
787gtk_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 */
837void
838gtk_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 */
871GtkShortcutScope
872gtk_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 */
898void
899gtk_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 */
920GdkModifierType
921gtk_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

source code of gtk/gtk/gtkshortcutcontroller.c