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