1 /* dzl-shortcut-controller.c
2  *
3  * Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #define G_LOG_DOMAIN "dzl-shortcut-controller"
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "dzl-debug.h"
27 
28 #include "shortcuts/dzl-shortcut-closure-chain.h"
29 #include "shortcuts/dzl-shortcut-context.h"
30 #include "shortcuts/dzl-shortcut-controller.h"
31 #include "shortcuts/dzl-shortcut-manager.h"
32 #include "shortcuts/dzl-shortcut-private.h"
33 #include "util/dzl-macros.h"
34 
35 typedef struct
36 {
37   /*
38    * This is the widget for which we are the shortcut controller. There are
39    * zero or one shortcut controller for a given widget. These are persistent
40    * and dispatch events to the current DzlShortcutContext (which can be
41    * changed upon theme changes or shortcuts emitting the ::set-context signal.
42    */
43   GtkWidget *widget;
44 
45   /*
46    * This is the name of the current context. Contexts are resolved at runtime
47    * by locating them within the theme (or inherited theme). They are interned
48    * strings to avoid lots of allocations between widgets.
49    */
50   const gchar *context_name;
51 
52   /*
53    * If we are building a chord, it will be tracked here. Each incoming
54    * GdkEventKey will contribute to the creation of this chord.
55    */
56   DzlShortcutChord *current_chord;
57 
58   /*
59    * This is a pointer to the root controller for the window. We register with
60    * the root controller so that keybindings can be activated even when the
61    * focus widget is somewhere else.
62    */
63   DzlShortcutController *root;
64 
65   /*
66    * The commands that are attached to this controller including callbacks,
67    * signals, or actions. We use the commands_table to get a chord to the
68    * intern'd string containing the command id (for direct comparisons).
69    */
70   GHashTable *commands;
71 
72   /*
73    * The command table is used to provide a mapping from accelerator/chord
74    * to the key for @commands. The data for each chord is an interned string
75    * which can be used as a direct pointer for lookups in @commands.
76    */
77   DzlShortcutChordTable *commands_table;
78 
79   /*
80    * The root controller may have a manager associated with it to determine
81    * what themes and shortcuts are available.
82    */
83   DzlShortcutManager *manager;
84 
85   /*
86    * The root controller keeps track of the children controllers in the window.
87    * Instead of allocating GList entries, we use an inline GList for the Queue
88    * link nodes.
89    */
90   GQueue descendants;
91 
92   /*
93    * To avoid allocating GList nodes for controllers, we just inline a link
94    * here and attach it to @descendants when necessary.
95    */
96   GList descendants_link;
97 
98   /* Signal handlers to react to various changes in the system. */
99   gulong hierarchy_changed_handler;
100   gulong widget_destroy_handler;
101   gulong manager_changed_handler;
102 
103   /* If we have any global shortcuts registered */
104   guint have_global : 1;
105 } DzlShortcutControllerPrivate;
106 
107 enum {
108   PROP_0,
109   PROP_CONTEXT,
110   PROP_CURRENT_CHORD,
111   PROP_MANAGER,
112   PROP_WIDGET,
113   N_PROPS
114 };
115 
116 enum {
117   RESET,
118   SET_CONTEXT_NAMED,
119   N_SIGNALS
120 };
121 
122 struct _DzlShortcutController { GObject object; };
123 G_DEFINE_TYPE_WITH_PRIVATE (DzlShortcutController, dzl_shortcut_controller, G_TYPE_OBJECT)
124 
125 static GParamSpec *properties [N_PROPS];
126 static guint       signals [N_SIGNALS];
127 static GQuark      root_quark;
128 static GQuark      controller_quark;
129 
130 static void dzl_shortcut_controller_connect    (DzlShortcutController *self);
131 static void dzl_shortcut_controller_disconnect (DzlShortcutController *self);
132 
133 static void
dzl_shortcut_controller_emit_reset(DzlShortcutController * self)134 dzl_shortcut_controller_emit_reset (DzlShortcutController *self)
135 {
136   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
137 
138   g_signal_emit (self, signals[RESET], 0);
139 }
140 
141 static inline gboolean
dzl_shortcut_controller_is_root(DzlShortcutController * self)142 dzl_shortcut_controller_is_root (DzlShortcutController *self)
143 {
144   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
145 
146   return priv->root == NULL;
147 }
148 
149 /**
150  * dzl_shortcut_controller_get_manager:
151  * @self: a #DzlShortcutController
152  *
153  * Gets the #DzlShortcutManager associated with this controller.
154  *
155  * Generally, this will look for the root controller's manager as mixing and
156  * matching managers in a single window hierarchy is not supported.
157  *
158  * Returns: (not nullable) (transfer none): A #DzlShortcutManager.
159  */
160 DzlShortcutManager *
dzl_shortcut_controller_get_manager(DzlShortcutController * self)161 dzl_shortcut_controller_get_manager (DzlShortcutController *self)
162 {
163   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
164 
165   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
166 
167   if (priv->root != NULL)
168     return dzl_shortcut_controller_get_manager (priv->root);
169 
170   if (priv->manager != NULL)
171     return priv->manager;
172 
173   return dzl_shortcut_manager_get_default ();
174 }
175 
176 /**
177  * dzl_shortcut_controller_set_manager:
178  * @self: a #DzlShortcutController
179  * @manager: (nullable): A #DzlShortcutManager or %NULL
180  *
181  * Sets the #DzlShortcutController:manager property.
182  *
183  * If you set this to %NULL, it will revert to the default #DzlShortcutManager
184  * for the process.
185  */
186 void
dzl_shortcut_controller_set_manager(DzlShortcutController * self,DzlShortcutManager * manager)187 dzl_shortcut_controller_set_manager (DzlShortcutController *self,
188                                      DzlShortcutManager     *manager)
189 {
190   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
191 
192   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
193   g_return_if_fail (!manager || DZL_IS_SHORTCUT_MANAGER (manager));
194 
195   if (g_set_object (&priv->manager, manager))
196     g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MANAGER]);
197 }
198 
199 static gboolean
dzl_shortcut_controller_is_mapped(DzlShortcutController * self)200 dzl_shortcut_controller_is_mapped (DzlShortcutController *self)
201 {
202   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
203   return priv->widget != NULL && gtk_widget_get_mapped (priv->widget);
204 }
205 
206 static void
dzl_shortcut_controller_add(DzlShortcutController * self,DzlShortcutController * descendant)207 dzl_shortcut_controller_add (DzlShortcutController *self,
208                              DzlShortcutController *descendant)
209 {
210   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
211   DzlShortcutControllerPrivate *dpriv = dzl_shortcut_controller_get_instance_private (descendant);
212 
213   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
214   g_assert (DZL_IS_SHORTCUT_CONTROLLER (descendant));
215 
216   g_object_ref (descendant);
217 
218   if (dzl_shortcut_controller_is_mapped (descendant))
219     g_queue_push_head_link (&priv->descendants, &dpriv->descendants_link);
220   else
221     g_queue_push_tail_link (&priv->descendants, &dpriv->descendants_link);
222 }
223 
224 static void
dzl_shortcut_controller_remove(DzlShortcutController * self,DzlShortcutController * descendant)225 dzl_shortcut_controller_remove (DzlShortcutController *self,
226                                 DzlShortcutController *descendant)
227 {
228   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
229   DzlShortcutControllerPrivate *dpriv = dzl_shortcut_controller_get_instance_private (descendant);
230 
231   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
232   g_assert (DZL_IS_SHORTCUT_CONTROLLER (descendant));
233 
234   g_queue_unlink (&priv->descendants, &dpriv->descendants_link);
235   g_object_unref (descendant);
236 }
237 
238 static void
dzl_shortcut_controller_on_manager_changed(DzlShortcutController * self,DzlShortcutManager * manager)239 dzl_shortcut_controller_on_manager_changed (DzlShortcutController *self,
240                                             DzlShortcutManager    *manager)
241 {
242   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
243 
244   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
245   g_assert (DZL_IS_SHORTCUT_MANAGER (manager));
246 
247   priv->context_name = NULL;
248   _dzl_shortcut_controller_clear (self);
249   dzl_shortcut_controller_emit_reset (self);
250 }
251 
252 static void
dzl_shortcut_controller_widget_destroy(DzlShortcutController * self,GtkWidget * widget)253 dzl_shortcut_controller_widget_destroy (DzlShortcutController *self,
254                                         GtkWidget             *widget)
255 {
256   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
257 
258   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
259   g_assert (GTK_IS_WIDGET (widget));
260 
261   dzl_shortcut_controller_disconnect (self);
262   dzl_clear_weak_pointer (&priv->widget);
263 
264   if (priv->root != NULL)
265     {
266       dzl_shortcut_controller_remove (priv->root, self);
267       g_clear_object (&priv->root);
268     }
269 }
270 
271 static void
dzl_shortcut_controller_widget_hierarchy_changed(DzlShortcutController * self,GtkWidget * previous_toplevel,GtkWidget * widget)272 dzl_shortcut_controller_widget_hierarchy_changed (DzlShortcutController *self,
273                                                   GtkWidget             *previous_toplevel,
274                                                   GtkWidget             *widget)
275 {
276   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
277   GtkWidget *toplevel;
278 
279   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
280   g_assert (!previous_toplevel || GTK_IS_WIDGET (previous_toplevel));
281   g_assert (GTK_IS_WIDGET (widget));
282 
283   /*
284    * We attach our controller to the root controller if we have shortcuts in
285    * the global activation phase. That allows the bubble/capture phase to
286    * potentially dispatch to our controller.
287    */
288 
289   g_object_ref (self);
290 
291   if (priv->root != NULL)
292     {
293       dzl_shortcut_controller_remove (priv->root, self);
294       g_clear_object (&priv->root);
295     }
296 
297   if (priv->have_global)
298     {
299       toplevel = gtk_widget_get_toplevel (widget);
300 
301       if (toplevel != widget)
302         {
303           priv->root = g_object_get_qdata (G_OBJECT (toplevel), root_quark);
304           if (priv->root == NULL)
305             priv->root = dzl_shortcut_controller_new (toplevel);
306           dzl_shortcut_controller_add (priv->root, self);
307         }
308     }
309 
310   g_object_unref (self);
311 }
312 
313 static void
dzl_shortcut_controller_disconnect(DzlShortcutController * self)314 dzl_shortcut_controller_disconnect (DzlShortcutController *self)
315 {
316   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
317   DzlShortcutManager *manager;
318 
319   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
320   g_assert (GTK_IS_WIDGET (priv->widget));
321 
322   manager = dzl_shortcut_controller_get_manager (self);
323 
324   g_signal_handler_disconnect (priv->widget, priv->widget_destroy_handler);
325   priv->widget_destroy_handler = 0;
326 
327   g_signal_handler_disconnect (priv->widget, priv->hierarchy_changed_handler);
328   priv->hierarchy_changed_handler = 0;
329 
330   g_signal_handler_disconnect (manager, priv->manager_changed_handler);
331   priv->manager_changed_handler = 0;
332 }
333 
334 static void
dzl_shortcut_controller_connect(DzlShortcutController * self)335 dzl_shortcut_controller_connect (DzlShortcutController *self)
336 {
337   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
338   DzlShortcutManager *manager;
339 
340   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
341   g_assert (GTK_IS_WIDGET (priv->widget));
342 
343   manager = dzl_shortcut_controller_get_manager (self);
344 
345   g_clear_pointer (&priv->current_chord, dzl_shortcut_chord_free);
346   priv->context_name = NULL;
347 
348   priv->widget_destroy_handler =
349     g_signal_connect_swapped (priv->widget,
350                               "destroy",
351                               G_CALLBACK (dzl_shortcut_controller_widget_destroy),
352                               self);
353 
354   priv->hierarchy_changed_handler =
355     g_signal_connect_swapped (priv->widget,
356                               "hierarchy-changed",
357                               G_CALLBACK (dzl_shortcut_controller_widget_hierarchy_changed),
358                               self);
359 
360   priv->manager_changed_handler =
361     g_signal_connect_swapped (manager,
362                               "changed",
363                               G_CALLBACK (dzl_shortcut_controller_on_manager_changed),
364                               self);
365 
366   dzl_shortcut_controller_widget_hierarchy_changed (self, NULL, priv->widget);
367 }
368 
369 static void
dzl_shortcut_controller_set_widget(DzlShortcutController * self,GtkWidget * widget)370 dzl_shortcut_controller_set_widget (DzlShortcutController *self,
371                                     GtkWidget             *widget)
372 {
373   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
374 
375   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
376   g_assert (GTK_IS_WIDGET (widget));
377 
378   if (widget != priv->widget)
379     {
380       if (priv->widget != NULL)
381         {
382           dzl_shortcut_controller_disconnect (self);
383           dzl_clear_weak_pointer (&priv->widget);
384         }
385 
386       if (widget != NULL && widget != priv->widget)
387         {
388           dzl_set_weak_pointer (&priv->widget, widget);
389           dzl_shortcut_controller_connect (self);
390         }
391 
392       g_assert (widget == priv->widget);
393 
394       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WIDGET]);
395     }
396 }
397 
398 /**
399  * dzl_shortcut_controller_set_context_by_name:
400  * @self: a #DzlShortcutController
401  * @name: (nullable): The name of the context
402  *
403  * Changes the context for the controller to the context matching @name.
404  *
405  * Contexts are resolved at runtime through the current theme (and possibly
406  * a parent theme if it inherits from one).
407  *
408  * Since: 3.26
409  */
410 void
dzl_shortcut_controller_set_context_by_name(DzlShortcutController * self,const gchar * name)411 dzl_shortcut_controller_set_context_by_name (DzlShortcutController *self,
412                                              const gchar           *name)
413 {
414   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
415 
416   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
417 
418   name = g_intern_string (name);
419 
420   if (name != priv->context_name)
421     {
422       priv->context_name = name;
423       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]);
424       dzl_shortcut_controller_emit_reset (self);
425     }
426 }
427 
428 static void
dzl_shortcut_controller_real_set_context_named(DzlShortcutController * self,const gchar * name)429 dzl_shortcut_controller_real_set_context_named (DzlShortcutController *self,
430                                                 const gchar           *name)
431 {
432   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
433 
434   dzl_shortcut_controller_set_context_by_name (self, name);
435 }
436 
437 static void
dzl_shortcut_controller_finalize(GObject * object)438 dzl_shortcut_controller_finalize (GObject *object)
439 {
440   DzlShortcutController *self = (DzlShortcutController *)object;
441   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
442 
443   dzl_clear_weak_pointer (&priv->widget);
444   g_clear_pointer (&priv->commands, g_hash_table_unref);
445   g_clear_pointer (&priv->commands_table, dzl_shortcut_chord_table_free);
446   g_clear_object (&priv->root);
447 
448   while (priv->descendants.length > 0)
449     g_queue_unlink (&priv->descendants, priv->descendants.head);
450 
451   priv->context_name = NULL;
452 
453   G_OBJECT_CLASS (dzl_shortcut_controller_parent_class)->finalize (object);
454 }
455 
456 static void
dzl_shortcut_controller_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)457 dzl_shortcut_controller_get_property (GObject    *object,
458                                       guint       prop_id,
459                                       GValue     *value,
460                                       GParamSpec *pspec)
461 {
462   DzlShortcutController *self = (DzlShortcutController *)object;
463   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
464 
465   switch (prop_id)
466     {
467     case PROP_CONTEXT:
468       g_value_set_object (value, dzl_shortcut_controller_get_context (self));
469       break;
470 
471     case PROP_CURRENT_CHORD:
472       g_value_set_boxed (value, dzl_shortcut_controller_get_current_chord (self));
473       break;
474 
475     case PROP_MANAGER:
476       g_value_set_object (value, dzl_shortcut_controller_get_manager (self));
477       break;
478 
479     case PROP_WIDGET:
480       g_value_set_object (value, priv->widget);
481       break;
482 
483     default:
484       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
485     }
486 }
487 
488 static void
dzl_shortcut_controller_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)489 dzl_shortcut_controller_set_property (GObject      *object,
490                                       guint         prop_id,
491                                       const GValue *value,
492                                       GParamSpec   *pspec)
493 {
494   DzlShortcutController *self = (DzlShortcutController *)object;
495 
496   switch (prop_id)
497     {
498     case PROP_MANAGER:
499       dzl_shortcut_controller_set_manager (self, g_value_get_object (value));
500       break;
501 
502     case PROP_WIDGET:
503       dzl_shortcut_controller_set_widget (self, g_value_get_object (value));
504       break;
505 
506     default:
507       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
508     }
509 }
510 
511 static void
dzl_shortcut_controller_class_init(DzlShortcutControllerClass * klass)512 dzl_shortcut_controller_class_init (DzlShortcutControllerClass *klass)
513 {
514   GObjectClass *object_class = G_OBJECT_CLASS (klass);
515 
516   object_class->finalize = dzl_shortcut_controller_finalize;
517   object_class->get_property = dzl_shortcut_controller_get_property;
518   object_class->set_property = dzl_shortcut_controller_set_property;
519 
520   properties [PROP_CURRENT_CHORD] =
521     g_param_spec_boxed ("current-chord",
522                         "Current Chord",
523                         "The current chord for the controller",
524                         DZL_TYPE_SHORTCUT_CHORD,
525                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
526 
527   properties [PROP_CONTEXT] =
528     g_param_spec_object ("context",
529                          "Context",
530                          "The current context of the controller, for dispatch phase",
531                          DZL_TYPE_SHORTCUT_CONTEXT,
532                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
533 
534   properties [PROP_MANAGER] =
535     g_param_spec_object ("manager",
536                          "Manager",
537                          "The shortcut manager",
538                          DZL_TYPE_SHORTCUT_MANAGER,
539                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
540 
541   properties [PROP_WIDGET] =
542     g_param_spec_object ("widget",
543                          "Widget",
544                          "The widget for which the controller attached",
545                          GTK_TYPE_WIDGET,
546                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
547 
548   g_object_class_install_properties (object_class, N_PROPS, properties);
549 
550   /**
551    * DzlShortcutController::reset:
552    *
553    * This signal is emitted when the shortcut controller is requesting
554    * the widget to reset any state it may have regarding the shortcut
555    * controller. Such an example might be a modal system that lives
556    * outside the controller whose state should be cleared in response
557    * to the controller changing modes.
558    */
559   signals [RESET] =
560     g_signal_new_class_handler ("reset",
561                                 G_TYPE_FROM_CLASS (klass),
562                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
563                                 NULL, NULL, NULL, NULL, G_TYPE_NONE, 0);
564 
565   /**
566    * DzlShortcutController::set-context-named:
567    * @self: An #DzlShortcutController
568    * @name: The name of the context
569    *
570    * This changes the current context on the #DzlShortcutController to be the
571    * context matching @name. This is found by looking up the context by name
572    * in the active #DzlShortcutTheme.
573    */
574   signals [SET_CONTEXT_NAMED] =
575     g_signal_new_class_handler ("set-context-named",
576                                 G_TYPE_FROM_CLASS (klass),
577                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
578                                 G_CALLBACK (dzl_shortcut_controller_real_set_context_named),
579                                 NULL, NULL, NULL,
580                                 G_TYPE_NONE, 1, G_TYPE_STRING);
581 
582   controller_quark = g_quark_from_static_string ("DZL_SHORTCUT_CONTROLLER");
583   root_quark = g_quark_from_static_string ("DZL_SHORTCUT_CONTROLLER_ROOT");
584 }
585 
586 static void
dzl_shortcut_controller_init(DzlShortcutController * self)587 dzl_shortcut_controller_init (DzlShortcutController *self)
588 {
589   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
590 
591   g_queue_init (&priv->descendants);
592 
593   priv->descendants_link.data = self;
594 }
595 
596 DzlShortcutController *
dzl_shortcut_controller_new(GtkWidget * widget)597 dzl_shortcut_controller_new (GtkWidget *widget)
598 {
599   DzlShortcutController *ret;
600 
601   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
602 
603   if (NULL != (ret = g_object_get_qdata (G_OBJECT (widget), controller_quark)))
604     return g_object_ref (ret);
605 
606   ret = g_object_new (DZL_TYPE_SHORTCUT_CONTROLLER,
607                       "widget", widget,
608                       NULL);
609 
610   g_object_set_qdata_full (G_OBJECT (widget),
611                            controller_quark,
612                            g_object_ref (ret),
613                            g_object_unref);
614 
615   return ret;
616 }
617 
618 /**
619  * dzl_shortcut_controller_try_find:
620  *
621  * Finds the registered #DzlShortcutController for a widget.
622  *
623  * If no controller is found, %NULL is returned.
624  *
625  * Returns: (nullable) (transfer none): An #DzlShortcutController or %NULL.
626  */
627 DzlShortcutController *
dzl_shortcut_controller_try_find(GtkWidget * widget)628 dzl_shortcut_controller_try_find (GtkWidget *widget)
629 {
630   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
631 
632   return g_object_get_qdata (G_OBJECT (widget), controller_quark);
633 }
634 
635 /**
636  * dzl_shortcut_controller_find:
637  *
638  * Finds the registered #DzlShortcutController for a widget.
639  *
640  * The controller is created if it does not already exist.
641  *
642  * Returns: (not nullable) (transfer none): An #DzlShortcutController or %NULL.
643  */
644 DzlShortcutController *
dzl_shortcut_controller_find(GtkWidget * widget)645 dzl_shortcut_controller_find (GtkWidget *widget)
646 {
647   DzlShortcutController *controller;
648 
649   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
650 
651   controller = g_object_get_qdata (G_OBJECT (widget), controller_quark);
652 
653   if (controller == NULL)
654     {
655       /* We want to pass a borrowed reference */
656       g_object_unref (dzl_shortcut_controller_new (widget));
657       controller = g_object_get_qdata (G_OBJECT (widget), controller_quark);
658     }
659 
660   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (controller), NULL);
661 
662   return controller;
663 }
664 
665 static DzlShortcutContext *
_dzl_shortcut_controller_get_context_for_phase(DzlShortcutController * self,DzlShortcutTheme * theme,DzlShortcutPhase phase)666 _dzl_shortcut_controller_get_context_for_phase (DzlShortcutController *self,
667                                                 DzlShortcutTheme      *theme,
668                                                 DzlShortcutPhase       phase)
669 {
670   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
671   g_autofree gchar *phased_name = NULL;
672   DzlShortcutContext *ret;
673   const gchar *name;
674 
675   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
676   g_return_val_if_fail (DZL_IS_SHORTCUT_THEME (theme), NULL);
677 
678   if (priv->widget == NULL)
679     return NULL;
680 
681   name = priv->context_name ? priv->context_name : G_OBJECT_TYPE_NAME (priv->widget);
682 
683   g_return_val_if_fail (name != NULL, NULL);
684 
685   /* If we are in dispatch phase, we use our direct context */
686   if (phase == DZL_SHORTCUT_PHASE_BUBBLE)
687     name = phased_name = g_strdup_printf ("%s:bubble", name);
688   else if (phase == DZL_SHORTCUT_PHASE_CAPTURE)
689     name = phased_name = g_strdup_printf ("%s:capture", name);
690 
691   ret = _dzl_shortcut_theme_try_find_context_by_name (theme, name);
692 
693   g_return_val_if_fail (!ret || DZL_IS_SHORTCUT_CONTEXT (ret), NULL);
694 
695   return ret;
696 }
697 
698 /**
699  * dzl_shortcut_controller_get_context_for_phase:
700  * @self: a #DzlShortcutController
701  * @phase: the phase for the shorcut delivery
702  *
703  * Controllers can have a different context for a particular phase, which allows
704  * them to activate different keybindings depending if the event in capture,
705  * bubble, or dispatch.
706  *
707  * Returns: (transfer none) (nullable): A #DzlShortcutContext or %NULL.
708  *
709  * Since: 3.26
710  */
711 DzlShortcutContext *
dzl_shortcut_controller_get_context_for_phase(DzlShortcutController * self,DzlShortcutPhase phase)712 dzl_shortcut_controller_get_context_for_phase (DzlShortcutController *self,
713                                                DzlShortcutPhase       phase)
714 {
715   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
716   DzlShortcutManager *manager;
717   DzlShortcutTheme *theme;
718 
719   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
720 
721   if (NULL == priv->widget ||
722       NULL == (manager = dzl_shortcut_controller_get_manager (self)) ||
723       NULL == (theme = dzl_shortcut_manager_get_theme (manager)))
724     return NULL;
725 
726   return _dzl_shortcut_controller_get_context_for_phase (self, theme, phase);
727 }
728 
729 /**
730  * dzl_shortcut_controller_get_context:
731  * @self: An #DzlShortcutController
732  *
733  * This function gets the #DzlShortcutController:context property, which
734  * is the current context to dispatch events to. An #DzlShortcutContext
735  * is a group of keybindings that may be activated in response to a
736  * single or series of #GdkEventKey.
737  *
738  * Returns: (transfer none) (nullable): A #DzlShortcutContext or %NULL.
739  *
740  * Since: 3.26
741  */
742 DzlShortcutContext *
dzl_shortcut_controller_get_context(DzlShortcutController * self)743 dzl_shortcut_controller_get_context (DzlShortcutController *self)
744 {
745   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
746 
747   return dzl_shortcut_controller_get_context_for_phase (self, DZL_SHORTCUT_PHASE_DISPATCH);
748 }
749 
750 static DzlShortcutContext *
dzl_shortcut_controller_get_inherited_context(DzlShortcutController * self,DzlShortcutPhase phase)751 dzl_shortcut_controller_get_inherited_context (DzlShortcutController *self,
752                                                DzlShortcutPhase       phase)
753 {
754   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
755   DzlShortcutManager *manager;
756   DzlShortcutContext *ret;
757   DzlShortcutTheme *theme;
758   DzlShortcutTheme *parent;
759   const gchar *parent_name = NULL;
760 
761   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
762 
763   if (NULL == priv->widget ||
764       NULL == (manager = dzl_shortcut_controller_get_manager (self)) ||
765       NULL == (theme = dzl_shortcut_manager_get_theme (manager)) ||
766       NULL == (parent_name = dzl_shortcut_theme_get_parent_name (theme)) ||
767       NULL == (parent = dzl_shortcut_manager_get_theme_by_name (manager, parent_name)))
768     return NULL;
769 
770   ret = _dzl_shortcut_controller_get_context_for_phase (self, parent, phase);
771 
772   g_return_val_if_fail (!ret || DZL_IS_SHORTCUT_CONTEXT (ret), NULL);
773 
774   return ret;
775 }
776 
777 static DzlShortcutMatch
dzl_shortcut_controller_process(DzlShortcutController * self,const DzlShortcutChord * chord,DzlShortcutPhase phase)778 dzl_shortcut_controller_process (DzlShortcutController  *self,
779                                  const DzlShortcutChord *chord,
780                                  DzlShortcutPhase        phase)
781 {
782   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
783   DzlShortcutContext *context;
784   DzlShortcutMatch match = DZL_SHORTCUT_MATCH_NONE;
785 
786   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
787   g_assert (chord != NULL);
788 
789   /* Try to activate our current context */
790   if (match == DZL_SHORTCUT_MATCH_NONE &&
791       NULL != (context = dzl_shortcut_controller_get_context_for_phase (self, phase)))
792     match = dzl_shortcut_context_activate (context, priv->widget, chord);
793 
794   /* If we didn't get a match, locate the context within the parent theme */
795   if (match == DZL_SHORTCUT_MATCH_NONE &&
796       NULL != (context = dzl_shortcut_controller_get_inherited_context (self, phase)))
797     match = dzl_shortcut_context_activate (context, priv->widget, chord);
798 
799   return match;
800 }
801 
802 static void
dzl_shortcut_controller_do_global_chain(DzlShortcutController * self,DzlShortcutClosureChain * chain,GtkWidget * widget,GList * next)803 dzl_shortcut_controller_do_global_chain (DzlShortcutController   *self,
804                                          DzlShortcutClosureChain *chain,
805                                          GtkWidget               *widget,
806                                          GList                   *next)
807 {
808   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
809 
810   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
811   g_assert (chain != NULL);
812   g_assert (GTK_IS_WIDGET (widget));
813 
814   /*
815    * If this is an action chain, the best we're going to be able to do is
816    * activate the action from the current widget. For commands, we can try to
817    * resolve them by locating commands within our registered controllers.
818    */
819 
820   if (chain->type != DZL_SHORTCUT_CLOSURE_COMMAND)
821     {
822       dzl_shortcut_closure_chain_execute (chain, widget);
823       return;
824     }
825 
826   if (priv->commands != NULL &&
827       g_hash_table_contains (priv->commands, chain->command.name))
828     {
829       dzl_shortcut_closure_chain_execute (chain, priv->widget);
830       return;
831     }
832 
833   if (next == NULL)
834     {
835       dzl_shortcut_closure_chain_execute (chain, widget);
836       return;
837     }
838 
839   dzl_shortcut_controller_do_global_chain (next->data, chain, widget, next->next);
840 }
841 
842 static DzlShortcutMatch
dzl_shortcut_controller_do_global(DzlShortcutController * self,const DzlShortcutChord * chord,DzlShortcutPhase phase,GtkWidget * widget)843 dzl_shortcut_controller_do_global (DzlShortcutController  *self,
844                                    const DzlShortcutChord *chord,
845                                    DzlShortcutPhase        phase,
846                                    GtkWidget              *widget)
847 {
848   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
849   DzlShortcutClosureChain *chain = NULL;
850   DzlShortcutManager *manager;
851   DzlShortcutTheme *theme;
852   DzlShortcutMatch match;
853 
854   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
855   g_assert (chord != NULL);
856   g_assert ((phase & DZL_SHORTCUT_PHASE_GLOBAL) != 0);
857   g_assert (GTK_IS_WIDGET (widget));
858 
859   manager = dzl_shortcut_controller_get_manager (self);
860   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
861 
862   theme = dzl_shortcut_manager_get_theme (manager);
863   g_assert (DZL_IS_SHORTCUT_THEME (theme));
864 
865   /* See if we have a chain for this chord */
866   match = _dzl_shortcut_theme_match (theme, phase, chord, &chain);
867 
868   /* If we matched, execute the chain, trying to locate the proper widget for
869    * the event delivery.
870    */
871   if (match == DZL_SHORTCUT_MATCH_EQUAL && chain->phase == phase)
872     dzl_shortcut_controller_do_global_chain (self, chain, widget, priv->descendants.head);
873 
874   return match;
875 }
876 
877 /**
878  * _dzl_shortcut_controller_handle:
879  * @self: An #DzlShortcutController
880  * @event: A #GdkEventKey
881  * @chord: the current chord for the toplevel
882  * @phase: the dispatch phase
883  * @widget: the widget receiving @event
884  *
885  * This function uses @event to determine if the current context has a shortcut
886  * registered matching the event. If so, the shortcut will be dispatched and
887  * %TRUE is returned. Otherwise, %FALSE is returned.
888  *
889  * @chord is used to track the current chord from the toplevel. Chord tracking
890  * is done in a single place to avoid inconsistencies between controllers.
891  *
892  * @phase should indicate the phase of the event dispatch. Capture is used
893  * to capture events before the destination #GdkWindow can process them, and
894  * bubble is to allow the destination window to handle it before processing
895  * the result afterwards if not yet handled.
896  *
897  * Returns: A #DzlShortcutMatch based on if the event was dispatched.
898  *
899  * Since: 3.26
900  */
901 DzlShortcutMatch
_dzl_shortcut_controller_handle(DzlShortcutController * self,const GdkEventKey * event,const DzlShortcutChord * chord,DzlShortcutPhase phase,GtkWidget * widget)902 _dzl_shortcut_controller_handle (DzlShortcutController  *self,
903                                  const GdkEventKey      *event,
904                                  const DzlShortcutChord *chord,
905                                  DzlShortcutPhase        phase,
906                                  GtkWidget              *widget)
907 {
908   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
909   DzlShortcutMatch match = DZL_SHORTCUT_MATCH_NONE;
910 
911   DZL_ENTRY;
912 
913   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), FALSE);
914   g_return_val_if_fail (event != NULL, FALSE);
915   g_return_val_if_fail (chord != NULL, FALSE);
916   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
917 
918   /* Nothing to do if the widget isn't visible/mapped/etc */
919   if (priv->widget == NULL ||
920       !gtk_widget_get_visible (priv->widget) ||
921       !gtk_widget_get_child_visible (priv->widget) ||
922       !gtk_widget_is_sensitive (priv->widget))
923     DZL_RETURN (DZL_SHORTCUT_MATCH_NONE);
924 
925   DZL_TRACE_MSG ("widget = %s, phase = %d", G_OBJECT_TYPE_NAME (priv->widget), phase);
926 
927   /* Try to dispatch our capture global shortcuts first */
928   if (phase == (DZL_SHORTCUT_PHASE_CAPTURE | DZL_SHORTCUT_PHASE_GLOBAL) &&
929       dzl_shortcut_controller_is_root (self))
930     match = dzl_shortcut_controller_do_global (self, chord, phase, widget);
931 
932   /*
933    * This function processes a particular phase for the event. If our phase
934    * is DZL_SHORTCUT_PHASE_CAPTURE, that means we are in the process of working
935    * our way from the toplevel down to the widget containing the event window.
936    *
937    * If our phase is DZL_SHORTCUT_PHASE_BUBBLE, then we are working our way
938    * up from the widget containing the event window to the toplevel. This is
939    * the phase where most activations should occur.
940    *
941    * During the capture phase, we look for a context matching the current
942    * context, but with a suffix on the context name like ":capture". So for
943    * the default GtkEntry, the capture context name would be something like
944    * "GtkEntry:capture". The bubble phase does not have a suffix.
945    *
946    *   Toplevel Global Capture Accels
947    *   Toplevel Capture
948    *     - Child 1 Capture
949    *       - Grandchild 1 Capture
950    *       - Grandchild 1 Bubble
951    *     - Child 1 Bubble
952    *   Toplevel Bubble
953    *   Toplevel Global Bubble Accels
954    *
955    * If we come across a keybinding that is a partial match, we assume that
956    * is the closest match in the dispatch chain and stop processing further.
957    * Overlapping and conflicting keybindings are considered undefined behavior
958    * and this falls under such a situation.
959    *
960    * Note that we do not perform the bubble/capture phase here, that is handled
961    * by our caller in DzlShortcutManager.
962    */
963 
964   if (match == DZL_SHORTCUT_MATCH_NONE)
965     match = dzl_shortcut_controller_process (self, chord, phase);
966 
967   /* Try to dispatch our capture global shortcuts first */
968   if (match == DZL_SHORTCUT_MATCH_NONE &&
969       dzl_shortcut_controller_is_root (self) &&
970       phase == (DZL_SHORTCUT_PHASE_BUBBLE | DZL_SHORTCUT_PHASE_GLOBAL))
971     match = dzl_shortcut_controller_do_global (self, chord, phase, widget);
972 
973   DZL_TRACE_MSG ("match = %s",
974                  match == DZL_SHORTCUT_MATCH_NONE ? "none" :
975                  match == DZL_SHORTCUT_MATCH_PARTIAL ? "partial" : "equal");
976 
977   DZL_RETURN (match);
978 }
979 
980 /**
981  * dzl_shortcut_controller_get_current_chord:
982  * @self: a #DzlShortcutController
983  *
984  * This method gets the #DzlShortcutController:current-chord property.
985  * This is useful if you want to monitor in-progress chord building.
986  *
987  * Note that this value will only be valid on the controller for the
988  * toplevel widget (a #GtkWindow). Chords are not tracked at the
989  * individual widget controller level.
990  *
991  * Returns: (transfer none) (nullable): A #DzlShortcutChord or %NULL.
992  */
993 const DzlShortcutChord *
dzl_shortcut_controller_get_current_chord(DzlShortcutController * self)994 dzl_shortcut_controller_get_current_chord (DzlShortcutController *self)
995 {
996   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
997 
998   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
999 
1000   return priv->current_chord;
1001 }
1002 
1003 /**
1004  * dzl_shortcut_controller_execute_command:
1005  * @self: a #DzlShortcutController
1006  * @command: the id of the command
1007  *
1008  * This method will locate and execute the command matching the id @command.
1009  *
1010  * If the command is not found, %FALSE is returned.
1011  *
1012  * Returns: %TRUE if the command was found and executed.
1013  */
1014 gboolean
dzl_shortcut_controller_execute_command(DzlShortcutController * self,const gchar * command)1015 dzl_shortcut_controller_execute_command (DzlShortcutController *self,
1016                                          const gchar           *command)
1017 {
1018   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1019 
1020   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), FALSE);
1021   g_return_val_if_fail (command != NULL, FALSE);
1022 
1023   if (priv->commands != NULL)
1024     {
1025       DzlShortcutClosureChain *chain;
1026 
1027       chain = g_hash_table_lookup (priv->commands, g_intern_string (command));
1028 
1029       if (chain != NULL)
1030         return dzl_shortcut_closure_chain_execute (chain, priv->widget);
1031     }
1032 
1033   for (const GList *iter = priv->descendants.head; iter != NULL; iter = iter->next)
1034     {
1035       DzlShortcutController *descendant = iter->data;
1036 
1037       if (dzl_shortcut_controller_execute_command (descendant, command))
1038         return TRUE;
1039     }
1040 
1041   return FALSE;
1042 }
1043 
1044 static void
dzl_shortcut_controller_add_command(DzlShortcutController * self,const gchar * command_id,const gchar * default_accel,DzlShortcutPhase phase,DzlShortcutClosureChain * chain)1045 dzl_shortcut_controller_add_command (DzlShortcutController   *self,
1046                                      const gchar             *command_id,
1047                                      const gchar             *default_accel,
1048                                      DzlShortcutPhase         phase,
1049                                      DzlShortcutClosureChain *chain)
1050 {
1051   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1052   g_autoptr(DzlShortcutChord) chord = NULL;
1053   DzlShortcutManager *manager;
1054   DzlShortcutTheme *theme;
1055 
1056   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
1057   g_assert (command_id != NULL);
1058   g_assert (chain != NULL);
1059 
1060   /* Always use interned strings for command ids */
1061   command_id = g_intern_string (command_id);
1062 
1063   /*
1064    * Set the phase on the closure chain so we know what phase we are allowed
1065    * to execute the chain within during capture/dispatch/bubble. There is no
1066    * "global + dispatch" phase, so if global is set, default to bubble.
1067    */
1068   if (phase == DZL_SHORTCUT_PHASE_GLOBAL)
1069     phase |= DZL_SHORTCUT_PHASE_BUBBLE;
1070   chain->phase = phase;
1071 
1072   /* Add the closure chain to our set of commands. */
1073   if (priv->commands == NULL)
1074     priv->commands = g_hash_table_new_full (NULL, NULL, NULL,
1075                                             (GDestroyNotify)dzl_shortcut_closure_chain_free);
1076   g_hash_table_insert (priv->commands, (gpointer)command_id, chain);
1077 
1078   /*
1079    * If this command can be executed in the global phase, we need to be
1080    * sure that the root controller knows that we must be checked during
1081    * global activation checks.
1082    */
1083   if ((phase & DZL_SHORTCUT_PHASE_GLOBAL) != 0)
1084     {
1085       if (priv->have_global != TRUE)
1086         {
1087           priv->have_global = TRUE;
1088           if (priv->widget != NULL)
1089             dzl_shortcut_controller_widget_hierarchy_changed (self, NULL, priv->widget);
1090         }
1091     }
1092 
1093   /* If an accel was provided, we need to register it in various places */
1094   if (default_accel != NULL)
1095     {
1096       /* Make sure this is a valid accelerator */
1097       chord = dzl_shortcut_chord_new_from_string (default_accel);
1098 
1099       if (chord != NULL)
1100         {
1101           DzlShortcutContext *context;
1102 
1103           /* Add the chord to our chord table for lookups */
1104           if (priv->commands_table == NULL)
1105             priv->commands_table = dzl_shortcut_chord_table_new ();
1106           dzl_shortcut_chord_table_add (priv->commands_table, chord, (gpointer)command_id);
1107 
1108           /* Set the value in the theme so it can have overrides by users */
1109           manager = dzl_shortcut_controller_get_manager (self);
1110           theme = _dzl_shortcut_manager_get_internal_theme (manager);
1111           dzl_shortcut_theme_set_chord_for_command (theme, command_id, chord, phase);
1112 
1113           /* Hook things up into the default context */
1114           context = _dzl_shortcut_theme_find_default_context_with_phase (theme, priv->widget, phase);
1115           if (!_dzl_shortcut_context_contains (context, chord))
1116             dzl_shortcut_context_add_command (context, default_accel, command_id);
1117         }
1118       else
1119         g_warning ("\"%s\" is not a valid accelerator chord", default_accel);
1120     }
1121 }
1122 
1123 void
dzl_shortcut_controller_add_command_action(DzlShortcutController * self,const gchar * command_id,const gchar * default_accel,DzlShortcutPhase phase,const gchar * action)1124 dzl_shortcut_controller_add_command_action (DzlShortcutController *self,
1125                                             const gchar           *command_id,
1126                                             const gchar           *default_accel,
1127                                             DzlShortcutPhase       phase,
1128                                             const gchar           *action)
1129 {
1130   DzlShortcutClosureChain *chain;
1131 
1132   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
1133   g_return_if_fail (command_id != NULL);
1134 
1135   chain = dzl_shortcut_closure_chain_append_action_string (NULL, action);
1136   dzl_shortcut_controller_add_command (self, command_id, default_accel, phase, chain);
1137 }
1138 
1139 void
dzl_shortcut_controller_add_command_callback(DzlShortcutController * self,const gchar * command_id,const gchar * default_accel,DzlShortcutPhase phase,GtkCallback callback,gpointer callback_data,GDestroyNotify callback_data_destroy)1140 dzl_shortcut_controller_add_command_callback (DzlShortcutController *self,
1141                                               const gchar           *command_id,
1142                                               const gchar           *default_accel,
1143                                               DzlShortcutPhase       phase,
1144                                               GtkCallback            callback,
1145                                               gpointer               callback_data,
1146                                               GDestroyNotify         callback_data_destroy)
1147 {
1148   DzlShortcutClosureChain *chain;
1149 
1150   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
1151   g_return_if_fail (command_id != NULL);
1152 
1153   chain = dzl_shortcut_closure_chain_append_callback (NULL,
1154                                                       callback,
1155                                                       callback_data,
1156                                                       callback_data_destroy);
1157 
1158   dzl_shortcut_controller_add_command (self, command_id, default_accel, phase, chain);
1159 }
1160 
1161 void
dzl_shortcut_controller_add_command_signal(DzlShortcutController * self,const gchar * command_id,const gchar * default_accel,DzlShortcutPhase phase,const gchar * signal_name,guint n_args,...)1162 dzl_shortcut_controller_add_command_signal (DzlShortcutController *self,
1163                                             const gchar           *command_id,
1164                                             const gchar           *default_accel,
1165                                             DzlShortcutPhase       phase,
1166                                             const gchar           *signal_name,
1167                                             guint                  n_args,
1168                                             ...)
1169 {
1170   DzlShortcutClosureChain *chain;
1171   va_list args;
1172 
1173   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
1174   g_return_if_fail (command_id != NULL);
1175 
1176   va_start (args, n_args);
1177   chain = dzl_shortcut_closure_chain_append_signal (NULL, signal_name, n_args, args);
1178   va_end (args);
1179 
1180   dzl_shortcut_controller_add_command (self, command_id, default_accel, phase, chain);
1181 }
1182 
1183 DzlShortcutChord *
_dzl_shortcut_controller_push(DzlShortcutController * self,const GdkEventKey * event)1184 _dzl_shortcut_controller_push (DzlShortcutController *self,
1185                                const GdkEventKey     *event)
1186 {
1187   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1188 
1189   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
1190   g_return_val_if_fail (event != NULL, NULL);
1191 
1192   /*
1193    * Only the toplevel controller handling the event needs to determine
1194    * the current "chord" as that state lives in the root controller only.
1195    *
1196    * So our first step is to determine the current chord, or if this input
1197    * breaks further chord processing.
1198    *
1199    * We will use these chords during capture/dispatch/bubble later on.
1200    */
1201   if (priv->current_chord == NULL)
1202     {
1203       /* Try to create a new chord starting with this key.
1204        * current_chord may still be NULL after this.
1205        */
1206       priv->current_chord = dzl_shortcut_chord_new_from_event (event);
1207     }
1208   else
1209     {
1210       if (!dzl_shortcut_chord_append_event (priv->current_chord, event))
1211         {
1212           /* Failed to add the key to the chord, cancel */
1213           _dzl_shortcut_controller_clear (self);
1214           return NULL;
1215         }
1216     }
1217 
1218   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT_CHORD]);
1219 
1220   return dzl_shortcut_chord_copy (priv->current_chord);
1221 }
1222 
1223 void
_dzl_shortcut_controller_clear(DzlShortcutController * self)1224 _dzl_shortcut_controller_clear (DzlShortcutController *self)
1225 {
1226   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1227 
1228   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
1229 
1230   g_clear_pointer (&priv->current_chord, dzl_shortcut_chord_free);
1231   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT_CHORD]);
1232 }
1233 
1234 /**
1235  * dzl_shortcut_controller_get_widget:
1236  * @self: a #DzlShortcutController
1237  *
1238  * Returns: (transfer none): the widget for the controller
1239  *
1240  * Since: 3.34
1241  */
1242 GtkWidget *
dzl_shortcut_controller_get_widget(DzlShortcutController * self)1243 dzl_shortcut_controller_get_widget (DzlShortcutController *self)
1244 {
1245   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1246 
1247   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), NULL);
1248 
1249   return priv->widget;
1250 }
1251 
1252 void
dzl_shortcut_controller_remove_accel(DzlShortcutController * self,const gchar * accel,DzlShortcutPhase phase)1253 dzl_shortcut_controller_remove_accel (DzlShortcutController *self,
1254                                       const gchar           *accel,
1255                                       DzlShortcutPhase       phase)
1256 {
1257   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
1258   g_autoptr(DzlShortcutChord) chord = NULL;
1259 
1260   g_return_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self));
1261   g_return_if_fail (accel != NULL);
1262 
1263   chord = dzl_shortcut_chord_new_from_string (accel);
1264 
1265   if (chord != NULL)
1266     {
1267       DzlShortcutContext *context;
1268       DzlShortcutManager *manager;
1269       DzlShortcutTheme *theme;
1270 
1271       /* Add the chord to our chord table for lookups */
1272       if (priv->commands_table != NULL)
1273         dzl_shortcut_chord_table_remove (priv->commands_table, chord);
1274 
1275       /* Set the value in the theme so it can have overrides by users */
1276       manager = dzl_shortcut_controller_get_manager (self);
1277       theme = _dzl_shortcut_manager_get_internal_theme (manager);
1278       dzl_shortcut_theme_set_chord_for_command (theme, NULL, chord, 0);
1279 
1280       if (priv->widget != NULL)
1281         {
1282           context = _dzl_shortcut_theme_find_default_context_with_phase (theme, priv->widget, phase);
1283           if (context && _dzl_shortcut_context_contains (context, chord))
1284             dzl_shortcut_context_remove (context, accel);
1285         }
1286     }
1287 }
1288