1/*
2 * Copyright © 2018 Benjamin Otte
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authors: Benjamin Otte <otte@gnome.org>
20 */
21
22/**
23 * GtkShortcutTrigger:
24 *
25 * `GtkShortcutTrigger` tracks how a `GtkShortcut` should be activated.
26 *
27 * To find out if a `GtkShortcutTrigger` triggers, you can call
28 * [method@Gtk.ShortcutTrigger.trigger] on a `GdkEvent`.
29 *
30 * `GtkShortcutTriggers` contain functions that allow easy presentation
31 * to end users as well as being printed for debugging.
32 *
33 * All `GtkShortcutTriggers` are immutable, you can only specify their
34 * properties during construction. If you want to change a trigger, you
35 * have to replace it with a new one.
36 */
37
38#include "config.h"
39
40#include "gtkshortcuttrigger.h"
41
42#include "gtkaccelgroupprivate.h"
43#include "gtkprivate.h"
44#include "gtkintl.h"
45
46#define GTK_SHORTCUT_TRIGGER_HASH_NEVER 0u
47#define GTK_SHORTCUT_TRIGGER_HASH_KEYVAL 1u
48#define GTK_SHORTCUT_TRIGGER_HASH_MNEMONIC 2u
49#define GTK_SHORTCUT_TRIGGER_HASH_ALTERNATIVE 3u
50
51struct _GtkShortcutTrigger
52{
53 GObject parent_instance;
54};
55
56struct _GtkShortcutTriggerClass
57{
58 GObjectClass parent_class;
59
60 GdkKeyMatch (* trigger) (GtkShortcutTrigger *trigger,
61 GdkEvent *event,
62 gboolean enable_mnemonics);
63 guint (* hash) (GtkShortcutTrigger *trigger);
64 int (* compare) (GtkShortcutTrigger *trigger1,
65 GtkShortcutTrigger *trigger2);
66 void (* print) (GtkShortcutTrigger *trigger,
67 GString *string);
68 gboolean (* print_label) (GtkShortcutTrigger *trigger,
69 GdkDisplay *display,
70 GString *string);
71};
72
73G_DEFINE_ABSTRACT_TYPE (GtkShortcutTrigger, gtk_shortcut_trigger, G_TYPE_OBJECT)
74
75static void
76gtk_shortcut_trigger_class_init (GtkShortcutTriggerClass *klass)
77{
78}
79
80static void
81gtk_shortcut_trigger_init (GtkShortcutTrigger *self)
82{
83}
84
85/**
86 * gtk_shortcut_trigger_trigger:
87 * @self: a `GtkShortcutTrigger`
88 * @event: the event to check
89 * @enable_mnemonics: %TRUE if mnemonics should trigger. Usually the
90 * value of this property is determined by checking that the passed
91 * in @event is a Key event and has the right modifiers set.
92 *
93 * Checks if the given @event triggers @self.
94 *
95 * Returns: Whether the event triggered the shortcut
96 */
97GdkKeyMatch
98gtk_shortcut_trigger_trigger (GtkShortcutTrigger *self,
99 GdkEvent *event,
100 gboolean enable_mnemonics)
101{
102 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (self), GDK_KEY_MATCH_NONE);
103
104 return GTK_SHORTCUT_TRIGGER_GET_CLASS (ptr: self)->trigger (self, event, enable_mnemonics);
105}
106
107/**
108 * gtk_shortcut_trigger_parse_string: (constructor)
109 * @string: the string to parse
110 *
111 * Tries to parse the given string into a trigger.
112 *
113 * On success, the parsed trigger is returned.
114 * When parsing failed, %NULL is returned.
115 *
116 * The accepted strings are:
117 *
118 * - `never`, for `GtkNeverTrigger`
119 * - a string parsed by gtk_accelerator_parse(), for a `GtkKeyvalTrigger`, e.g. `<Control>C`
120 * - underscore, followed by a single character, for `GtkMnemonicTrigger`, e.g. `_l`
121 * - two valid trigger strings, separated by a `|` character, for a
122 * `GtkAlternativeTrigger`: `<Control>q|<Control>w`
123 *
124 * Note that you will have to escape the `<` and `>` characters when specifying
125 * triggers in XML files, such as GtkBuilder ui files. Use `&lt;` instead of
126 * `<` and `&gt;` instead of `>`.
127 *
128 * Returns: (nullable) (transfer full): a new `GtkShortcutTrigger`
129 */
130GtkShortcutTrigger *
131gtk_shortcut_trigger_parse_string (const char *string)
132{
133 GdkModifierType modifiers;
134 guint keyval;
135 const char *sep;
136
137 g_return_val_if_fail (string != NULL, NULL);
138
139 if ((sep = strchr (s: string, c: '|')) != NULL)
140 {
141 char *frag_a = g_strndup (str: string, n: sep - string);
142 const char *frag_b = sep + 1;
143 GtkShortcutTrigger *t1, *t2;
144
145 /* empty first slot */
146 if (*frag_a == '\0')
147 {
148 g_free (mem: frag_a);
149 return NULL;
150 }
151
152 /* empty second slot */
153 if (*frag_b == '\0')
154 {
155 g_free (mem: frag_a);
156 return NULL;
157 }
158
159 t1 = gtk_shortcut_trigger_parse_string (string: frag_a);
160 if (t1 == NULL)
161 {
162 g_free (mem: frag_a);
163 return NULL;
164 }
165
166 t2 = gtk_shortcut_trigger_parse_string (string: frag_b);
167 if (t2 == NULL)
168 {
169 g_object_unref (object: t1);
170 g_free (mem: frag_a);
171 return NULL;
172 }
173
174 g_free (mem: frag_a);
175
176 return gtk_alternative_trigger_new (first: t1, second: t2);
177 }
178
179 if (g_str_equal (v1: string, v2: "never"))
180 return g_object_ref (gtk_never_trigger_get ());
181
182 if (string[0] == '_')
183 {
184 keyval = gdk_keyval_from_name (keyval_name: string + 1);
185 if (keyval != GDK_KEY_VoidSymbol)
186 return gtk_mnemonic_trigger_new (keyval: gdk_keyval_to_lower (keyval));
187 }
188
189 if (gtk_accelerator_parse (accelerator: string, accelerator_key: &keyval, accelerator_mods: &modifiers))
190 return gtk_keyval_trigger_new (keyval, modifiers);
191
192 return NULL;
193}
194
195/**
196 * gtk_shortcut_trigger_to_string:
197 * @self: a `GtkShortcutTrigger`
198 *
199 * Prints the given trigger into a human-readable string.
200 *
201 * This is a small wrapper around [method@Gtk.ShortcutTrigger.print]
202 * to help when debugging.
203 *
204 * Returns: (transfer full): a new string
205 */
206char *
207gtk_shortcut_trigger_to_string (GtkShortcutTrigger *self)
208{
209 GString *string;
210
211 g_return_val_if_fail (self != NULL, NULL);
212
213 string = g_string_new (NULL);
214
215 gtk_shortcut_trigger_print (self, string);
216
217 return g_string_free (string, FALSE);
218}
219
220/**
221 * gtk_shortcut_trigger_print:
222 * @self: a `GtkShortcutTrigger`
223 * @string: a `GString` to print into
224 *
225 * Prints the given trigger into a string for the developer.
226 * This is meant for debugging and logging.
227 *
228 * The form of the representation may change at any time
229 * and is not guaranteed to stay identical.
230 */
231void
232gtk_shortcut_trigger_print (GtkShortcutTrigger *self,
233 GString *string)
234{
235 g_return_if_fail (GTK_IS_SHORTCUT_TRIGGER (self));
236 g_return_if_fail (string != NULL);
237
238 GTK_SHORTCUT_TRIGGER_GET_CLASS (ptr: self)->print (self, string);
239}
240
241/**
242 * gtk_shortcut_trigger_to_label:
243 * @self: a `GtkShortcutTrigger`
244 * @display: `GdkDisplay` to print for
245 *
246 * Gets textual representation for the given trigger.
247 *
248 * This function is returning a translated string for
249 * presentation to end users for example in menu items
250 * or in help texts.
251 *
252 * The @display in use may influence the resulting string in
253 * various forms, such as resolving hardware keycodes or by
254 * causing display-specific modifier names.
255 *
256 * The form of the representation may change at any time and is
257 * not guaranteed to stay identical.
258 *
259 * Returns: (transfer full): a new string
260 */
261char *
262gtk_shortcut_trigger_to_label (GtkShortcutTrigger *self,
263 GdkDisplay *display)
264{
265 GString *string;
266
267 g_return_val_if_fail (self != NULL, NULL);
268
269 string = g_string_new (NULL);
270 gtk_shortcut_trigger_print_label (self, display, string);
271
272 return g_string_free (string, FALSE);
273}
274
275/**
276 * gtk_shortcut_trigger_print_label:
277 * @self: a `GtkShortcutTrigger`
278 * @display: `GdkDisplay` to print for
279 * @string: a `GString` to print into
280 *
281 * Prints the given trigger into a string.
282 *
283 * This function is returning a translated string for presentation
284 * to end users for example in menu items or in help texts.
285 *
286 * The @display in use may influence the resulting string in
287 * various forms, such as resolving hardware keycodes or by
288 * causing display-specific modifier names.
289 *
290 * The form of the representation may change at any time and is
291 * not guaranteed to stay identical.
292 *
293 * Returns: %TRUE if something was printed or %FALSE if the
294 * trigger did not have a textual representation suitable
295 * for end users.
296 **/
297gboolean
298gtk_shortcut_trigger_print_label (GtkShortcutTrigger *self,
299 GdkDisplay *display,
300 GString *string)
301{
302 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (self), FALSE);
303 g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
304 g_return_val_if_fail (string != NULL, FALSE);
305
306 return GTK_SHORTCUT_TRIGGER_GET_CLASS (ptr: self)->print_label (self, display, string);
307}
308
309/**
310 * gtk_shortcut_trigger_hash:
311 * @trigger: (type GtkShortcutTrigger): a `GtkShortcutTrigger`
312 *
313 * Generates a hash value for a `GtkShortcutTrigger`.
314 *
315 * The output of this function is guaranteed to be the same for a given
316 * value only per-process. It may change between different processor
317 * architectures or even different versions of GTK. Do not use this
318 * function as a basis for building protocols or file formats.
319 *
320 * The types of @trigger is `gconstpointer` only to allow use of this
321 * function with `GHashTable`. They must each be a `GtkShortcutTrigger`.
322 *
323 * Returns: a hash value corresponding to @trigger
324 */
325guint
326gtk_shortcut_trigger_hash (gconstpointer trigger)
327{
328 GtkShortcutTrigger *t = (GtkShortcutTrigger *) trigger;
329
330 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t), 0);
331
332 return GTK_SHORTCUT_TRIGGER_GET_CLASS (ptr: t)->hash (t);
333}
334
335/**
336 * gtk_shortcut_trigger_equal:
337 * @trigger1: (type GtkShortcutTrigger): a `GtkShortcutTrigger`
338 * @trigger2: (type GtkShortcutTrigger): a `GtkShortcutTrigger`
339 *
340 * Checks if @trigger1 and @trigger2 trigger under the same conditions.
341 *
342 * The types of @one and @two are `gconstpointer` only to allow use of this
343 * function with `GHashTable`. They must each be a `GtkShortcutTrigger`.
344 *
345 * Returns: %TRUE if @trigger1 and @trigger2 are equal
346 */
347gboolean
348gtk_shortcut_trigger_equal (gconstpointer trigger1,
349 gconstpointer trigger2)
350{
351 return gtk_shortcut_trigger_compare (trigger1, trigger2) == 0;
352}
353
354/**
355 * gtk_shortcut_trigger_compare:
356 * @trigger1: (type GtkShortcutTrigger): a `GtkShortcutTrigger`
357 * @trigger2: (type GtkShortcutTrigger): a `GtkShortcutTrigger`
358 *
359 * The types of @trigger1 and @trigger2 are `gconstpointer` only to allow
360 * use of this function as a `GCompareFunc`.
361 *
362 * They must each be a `GtkShortcutTrigger`.
363 *
364 * Returns: An integer less than, equal to, or greater than zero if
365 * @trigger1 is found, respectively, to be less than, to match,
366 * or be greater than @trigger2.
367 */
368int
369gtk_shortcut_trigger_compare (gconstpointer trigger1,
370 gconstpointer trigger2)
371{
372 GtkShortcutTrigger *t1 = (GtkShortcutTrigger *) trigger1;
373 GtkShortcutTrigger *t2 = (GtkShortcutTrigger *) trigger2;
374 GType type1, type2;
375
376 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t1), -1);
377 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t2), 1);
378
379 type1 = G_OBJECT_TYPE (t1);
380 type2 = G_OBJECT_TYPE (t2);
381
382 if (type1 == type2)
383 {
384 return GTK_SHORTCUT_TRIGGER_GET_CLASS (ptr: t1)->compare (t1, t2);
385 }
386 else
387 { /* never < keyval < mnemonic < alternative */
388 if (type1 == GTK_TYPE_NEVER_TRIGGER ||
389 type2 == GTK_TYPE_ALTERNATIVE_TRIGGER)
390 return -1;
391 if (type2 == GTK_TYPE_NEVER_TRIGGER ||
392 type1 == GTK_TYPE_ALTERNATIVE_TRIGGER)
393 return 1;
394
395 if (type1 == GTK_TYPE_KEYVAL_TRIGGER)
396 return -1;
397 else
398 return 1;
399 }
400}
401
402struct _GtkNeverTrigger
403{
404 GtkShortcutTrigger parent_instance;
405};
406
407struct _GtkNeverTriggerClass
408{
409 GtkShortcutTriggerClass parent_class;
410};
411
412G_DEFINE_TYPE (GtkNeverTrigger, gtk_never_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
413
414static void G_GNUC_NORETURN
415gtk_never_trigger_finalize (GObject *gobject)
416{
417 g_assert_not_reached ();
418
419 G_OBJECT_CLASS (gtk_never_trigger_parent_class)->finalize (gobject);
420}
421
422static GdkKeyMatch
423gtk_never_trigger_trigger (GtkShortcutTrigger *trigger,
424 GdkEvent *event,
425 gboolean enable_mnemonics)
426{
427 return GDK_KEY_MATCH_NONE;
428}
429
430static guint
431gtk_never_trigger_hash (GtkShortcutTrigger *trigger)
432{
433 return GTK_SHORTCUT_TRIGGER_HASH_NEVER;
434}
435
436static int
437gtk_never_trigger_compare (GtkShortcutTrigger *trigger1,
438 GtkShortcutTrigger *trigger2)
439{
440 return 0;
441}
442
443static void
444gtk_never_trigger_print (GtkShortcutTrigger *trigger,
445 GString *string)
446{
447 g_string_append (string, val: "never");
448}
449
450static gboolean
451gtk_never_trigger_print_label (GtkShortcutTrigger *trigger,
452 GdkDisplay *display,
453 GString *string)
454{
455 return FALSE;
456}
457
458static void
459gtk_never_trigger_class_init (GtkNeverTriggerClass *klass)
460{
461 GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (ptr: klass);
462 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
463
464 gobject_class->finalize = gtk_never_trigger_finalize;
465
466 trigger_class->trigger = gtk_never_trigger_trigger;
467 trigger_class->hash = gtk_never_trigger_hash;
468 trigger_class->compare = gtk_never_trigger_compare;
469 trigger_class->print = gtk_never_trigger_print;
470 trigger_class->print_label = gtk_never_trigger_print_label;
471}
472
473static void
474gtk_never_trigger_init (GtkNeverTrigger *self)
475{
476}
477
478/**
479 * gtk_never_trigger_get:
480 *
481 * Gets the never trigger.
482 *
483 * This is a singleton for a trigger that never triggers.
484 * Use this trigger instead of %NULL because it implements
485 * all virtual functions.
486 *
487 * Returns: (type GtkNeverTrigger) (transfer none): The never trigger
488 */
489GtkShortcutTrigger *
490gtk_never_trigger_get (void)
491{
492 static GtkShortcutTrigger *never = NULL;
493
494 if (G_UNLIKELY (never == NULL))
495 never = g_object_new (GTK_TYPE_NEVER_TRIGGER, NULL);
496
497 return never;
498}
499
500struct _GtkKeyvalTrigger
501{
502 GtkShortcutTrigger parent_instance;
503
504 guint keyval;
505 GdkModifierType modifiers;
506};
507
508struct _GtkKeyvalTriggerClass
509{
510 GtkShortcutTriggerClass parent_class;
511};
512
513G_DEFINE_TYPE (GtkKeyvalTrigger, gtk_keyval_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
514
515enum
516{
517 KEYVAL_PROP_KEYVAL = 1,
518 KEYVAL_PROP_MODIFIERS,
519 KEYVAL_N_PROPS
520};
521
522static GParamSpec *keyval_props[KEYVAL_N_PROPS];
523
524static GdkKeyMatch
525gtk_keyval_trigger_trigger (GtkShortcutTrigger *trigger,
526 GdkEvent *event,
527 gboolean enable_mnemonics)
528{
529 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: trigger);
530
531 if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
532 return GDK_KEY_MATCH_NONE;
533
534 return gdk_key_event_matches (event, keyval: self->keyval, modifiers: self->modifiers);
535}
536
537static guint
538gtk_keyval_trigger_hash (GtkShortcutTrigger *trigger)
539{
540 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: trigger);
541
542 return (self->modifiers << 24)
543 | (self->modifiers >> 8)
544 | (self->keyval << 16)
545 | GTK_SHORTCUT_TRIGGER_HASH_KEYVAL;
546}
547
548static int
549gtk_keyval_trigger_compare (GtkShortcutTrigger *trigger1,
550 GtkShortcutTrigger *trigger2)
551{
552 GtkKeyvalTrigger *self1 = GTK_KEYVAL_TRIGGER (ptr: trigger1);
553 GtkKeyvalTrigger *self2 = GTK_KEYVAL_TRIGGER (ptr: trigger2);
554
555 if (self1->modifiers != self2->modifiers)
556 return self2->modifiers - self1->modifiers;
557
558 return self1->keyval - self2->keyval;
559}
560
561static void
562gtk_keyval_trigger_print (GtkShortcutTrigger *trigger,
563 GString *string)
564{
565 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: trigger);
566 char *accelerator_name;
567
568 accelerator_name = gtk_accelerator_name (accelerator_key: self->keyval, accelerator_mods: self->modifiers);
569 g_string_append (string, val: accelerator_name);
570 g_free (mem: accelerator_name);
571}
572
573static gboolean
574gtk_keyval_trigger_print_label (GtkShortcutTrigger *trigger,
575 GdkDisplay *display,
576 GString *string)
577{
578 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: trigger);
579
580 gtk_accelerator_print_label (gstring: string, accelerator_key: self->keyval, accelerator_mods: self->modifiers);
581
582 return TRUE;
583}
584
585static void
586gtk_keyval_trigger_set_property (GObject *gobject,
587 guint prop_id,
588 const GValue *value,
589 GParamSpec *pspec)
590{
591 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: gobject);
592
593 switch (prop_id)
594 {
595 case KEYVAL_PROP_KEYVAL:
596 {
597 guint keyval = g_value_get_uint (value);
598
599 /* We store keyvals as lower key */
600 if (keyval == GDK_KEY_ISO_Left_Tab)
601 self->keyval = GDK_KEY_Tab;
602 else
603 self->keyval = gdk_keyval_to_lower (keyval);
604 }
605 break;
606
607 case KEYVAL_PROP_MODIFIERS:
608 self->modifiers = g_value_get_flags (value);
609 break;
610
611 default:
612 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
613 }
614}
615
616static void
617gtk_keyval_trigger_get_property (GObject *gobject,
618 guint prop_id,
619 GValue *value,
620 GParamSpec *pspec)
621{
622 GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (ptr: gobject);
623
624 switch (prop_id)
625 {
626 case KEYVAL_PROP_KEYVAL:
627 g_value_set_uint (value, v_uint: self->keyval);
628 break;
629
630 case KEYVAL_PROP_MODIFIERS:
631 g_value_set_flags (value, v_flags: self->modifiers);
632 break;
633
634 default:
635 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
636 }
637}
638
639static void
640gtk_keyval_trigger_class_init (GtkKeyvalTriggerClass *klass)
641{
642 GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (ptr: klass);
643 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
644
645 gobject_class->set_property = gtk_keyval_trigger_set_property;
646 gobject_class->get_property = gtk_keyval_trigger_get_property;
647
648 trigger_class->trigger = gtk_keyval_trigger_trigger;
649 trigger_class->hash = gtk_keyval_trigger_hash;
650 trigger_class->compare = gtk_keyval_trigger_compare;
651 trigger_class->print = gtk_keyval_trigger_print;
652 trigger_class->print_label = gtk_keyval_trigger_print_label;
653
654 /**
655 * GtkKeyvalTrigger:keyval: (attributes org.gtk.Property.get=gtk_keyval_trigger_get_keyval)
656 *
657 * The key value for the trigger.
658 */
659 keyval_props[KEYVAL_PROP_KEYVAL] =
660 g_param_spec_uint (I_("keyval"),
661 P_("Key value"),
662 P_("The key value for the trigger"),
663 minimum: 0, G_MAXINT,
664 default_value: 0,
665 G_PARAM_STATIC_STRINGS |
666 G_PARAM_CONSTRUCT_ONLY |
667 G_PARAM_READWRITE);
668
669 /**
670 * GtkKeyvalTrigger:modifiers: (attributes org.gtk.Property.get=gtk_keyval_trigger_get_modifiers)
671 *
672 * The key modifiers for the trigger.
673 */
674 keyval_props[KEYVAL_PROP_MODIFIERS] =
675 g_param_spec_flags (I_("modifiers"),
676 P_("Modifiers"),
677 P_("The key modifiers for the trigger"),
678 flags_type: GDK_TYPE_MODIFIER_TYPE,
679 default_value: 0,
680 G_PARAM_STATIC_STRINGS |
681 G_PARAM_CONSTRUCT_ONLY |
682 G_PARAM_READWRITE);
683
684 g_object_class_install_properties (oclass: gobject_class, n_pspecs: KEYVAL_N_PROPS, pspecs: keyval_props);
685}
686
687static void
688gtk_keyval_trigger_init (GtkKeyvalTrigger *self)
689{
690}
691
692/**
693 * gtk_keyval_trigger_new:
694 * @keyval: The keyval to trigger for
695 * @modifiers: the modifiers that need to be present
696 *
697 * Creates a `GtkShortcutTrigger` that will trigger whenever
698 * the key with the given @keyval and @modifiers is pressed.
699 *
700 * Returns: A new `GtkShortcutTrigger`
701 */
702GtkShortcutTrigger *
703gtk_keyval_trigger_new (guint keyval,
704 GdkModifierType modifiers)
705{
706 return g_object_new (GTK_TYPE_KEYVAL_TRIGGER,
707 first_property_name: "keyval", keyval,
708 "modifiers", modifiers,
709 NULL);
710}
711
712/**
713 * gtk_keyval_trigger_get_modifiers: (attributes org.gtk.Method.get_property=modifiers)
714 * @self: a keyval `GtkShortcutTrigger`
715 *
716 * Gets the modifiers that must be present to succeed
717 * triggering @self.
718 *
719 * Returns: the modifiers
720 */
721GdkModifierType
722gtk_keyval_trigger_get_modifiers (GtkKeyvalTrigger *self)
723{
724 g_return_val_if_fail (GTK_IS_KEYVAL_TRIGGER (self), 0);
725
726 return self->modifiers;
727}
728
729/**
730 * gtk_keyval_trigger_get_keyval: (attributes org.gtk.Method.get_property=keyval)
731 * @self: a keyval `GtkShortcutTrigger`
732 *
733 * Gets the keyval that must be pressed to succeed
734 * triggering @self.
735 *
736 * Returns: the keyval
737 */
738guint
739gtk_keyval_trigger_get_keyval (GtkKeyvalTrigger *self)
740{
741 g_return_val_if_fail (GTK_IS_KEYVAL_TRIGGER (self), 0);
742
743 return self->keyval;
744}
745
746/*** GTK_MNEMONIC_TRIGGER ***/
747
748struct _GtkMnemonicTrigger
749{
750 GtkShortcutTrigger parent_instance;
751
752 guint keyval;
753};
754
755struct _GtkMnemonicTriggerClass
756{
757 GtkShortcutTriggerClass parent_class;
758};
759
760G_DEFINE_TYPE (GtkMnemonicTrigger, gtk_mnemonic_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
761
762enum
763{
764 MNEMONIC_PROP_KEYVAL = 1,
765 MNEMONIC_N_PROPS
766};
767
768static GParamSpec *mnemonic_props[MNEMONIC_N_PROPS];
769
770static GdkKeyMatch
771gtk_mnemonic_trigger_trigger (GtkShortcutTrigger *trigger,
772 GdkEvent *event,
773 gboolean enable_mnemonics)
774{
775 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: trigger);
776 guint keyval;
777
778 if (!enable_mnemonics)
779 return GDK_KEY_MATCH_NONE;
780
781 if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
782 return GDK_KEY_MATCH_NONE;
783
784 /* XXX: This needs to deal with groups */
785 keyval = gdk_key_event_get_keyval (event);
786
787 if (keyval == GDK_KEY_ISO_Left_Tab)
788 keyval = GDK_KEY_Tab;
789 else
790 keyval = gdk_keyval_to_lower (keyval);
791
792 if (keyval != self->keyval)
793 return GDK_KEY_MATCH_NONE;
794
795 return GDK_KEY_MATCH_EXACT;
796}
797
798static guint
799gtk_mnemonic_trigger_hash (GtkShortcutTrigger *trigger)
800{
801 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: trigger);
802
803 return (self->keyval << 8)
804 | GTK_SHORTCUT_TRIGGER_HASH_MNEMONIC;
805}
806
807static int
808gtk_mnemonic_trigger_compare (GtkShortcutTrigger *trigger1,
809 GtkShortcutTrigger *trigger2)
810{
811 GtkMnemonicTrigger *self1 = GTK_MNEMONIC_TRIGGER (ptr: trigger1);
812 GtkMnemonicTrigger *self2 = GTK_MNEMONIC_TRIGGER (ptr: trigger2);
813
814 return self1->keyval - self2->keyval;
815}
816
817static void
818gtk_mnemonic_trigger_print (GtkShortcutTrigger *trigger,
819 GString *string)
820{
821 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: trigger);
822 const char *keyval_str;
823
824 keyval_str = gdk_keyval_name (keyval: self->keyval);
825 if (keyval_str == NULL)
826 keyval_str = "???";
827
828 g_string_append (string, val: "<Mnemonic>");
829 g_string_append (string, val: keyval_str);
830}
831
832static gboolean
833gtk_mnemonic_trigger_print_label (GtkShortcutTrigger *trigger,
834 GdkDisplay *display,
835 GString *string)
836{
837 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: trigger);
838 const char *keyval_str;
839
840 keyval_str = gdk_keyval_name (keyval: self->keyval);
841 if (keyval_str == NULL)
842 return FALSE;
843
844 g_string_append (string, val: keyval_str);
845
846 return TRUE;
847}
848
849static void
850gtk_mnemonic_trigger_set_property (GObject *gobject,
851 guint prop_id,
852 const GValue *value,
853 GParamSpec *pspec)
854{
855 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: gobject);
856
857 switch (prop_id)
858 {
859 case MNEMONIC_PROP_KEYVAL:
860 {
861 guint keyval = g_value_get_uint (value);
862
863 /* We store keyvals as lower key */
864 if (keyval == GDK_KEY_ISO_Left_Tab)
865 self->keyval = GDK_KEY_Tab;
866 else
867 self->keyval = gdk_keyval_to_lower (keyval);
868 }
869 break;
870
871 default:
872 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
873 }
874}
875
876static void
877gtk_mnemonic_trigger_get_property (GObject *gobject,
878 guint prop_id,
879 GValue *value,
880 GParamSpec *pspec)
881{
882 GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (ptr: gobject);
883
884 switch (prop_id)
885 {
886 case MNEMONIC_PROP_KEYVAL:
887 g_value_set_uint (value, v_uint: self->keyval);
888 break;
889
890 default:
891 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
892 }
893}
894
895static void
896gtk_mnemonic_trigger_class_init (GtkMnemonicTriggerClass *klass)
897{
898 GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (ptr: klass);
899 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
900
901 gobject_class->set_property = gtk_mnemonic_trigger_set_property;
902 gobject_class->get_property = gtk_mnemonic_trigger_get_property;
903
904 trigger_class->trigger = gtk_mnemonic_trigger_trigger;
905 trigger_class->hash = gtk_mnemonic_trigger_hash;
906 trigger_class->compare = gtk_mnemonic_trigger_compare;
907 trigger_class->print = gtk_mnemonic_trigger_print;
908 trigger_class->print_label = gtk_mnemonic_trigger_print_label;
909
910 /**
911 * GtkMnemonicTrigger:keyval: (attributes org.gtk.Property.get=gtk_mnemonic_trigger_get_keyval)
912 *
913 * The key value for the trigger.
914 */
915 mnemonic_props[KEYVAL_PROP_KEYVAL] =
916 g_param_spec_uint (I_("keyval"),
917 P_("Key value"),
918 P_("The key value for the trigger"),
919 minimum: 0, G_MAXINT,
920 default_value: 0,
921 G_PARAM_STATIC_STRINGS |
922 G_PARAM_CONSTRUCT_ONLY |
923 G_PARAM_READWRITE);
924
925 g_object_class_install_properties (oclass: gobject_class, n_pspecs: MNEMONIC_N_PROPS, pspecs: mnemonic_props);
926}
927
928static void
929gtk_mnemonic_trigger_init (GtkMnemonicTrigger *self)
930{
931}
932
933/**
934 * gtk_mnemonic_trigger_new:
935 * @keyval: The keyval to trigger for
936 *
937 * Creates a `GtkShortcutTrigger` that will trigger whenever the key with
938 * the given @keyval is pressed and mnemonics have been activated.
939 *
940 * Mnemonics are activated by calling code when a key event with the right
941 * modifiers is detected.
942 *
943 * Returns: (transfer full) (type GtkMnemonicTrigger): A new `GtkShortcutTrigger`
944 */
945GtkShortcutTrigger *
946gtk_mnemonic_trigger_new (guint keyval)
947{
948 return g_object_new (GTK_TYPE_MNEMONIC_TRIGGER,
949 first_property_name: "keyval", keyval,
950 NULL);
951}
952
953/**
954 * gtk_mnemonic_trigger_get_keyval: (attributes org.gtk.Method.get_property=keyval)
955 * @self: a mnemonic `GtkShortcutTrigger`
956 *
957 * Gets the keyval that must be pressed to succeed triggering @self.
958 *
959 * Returns: the keyval
960 */
961guint
962gtk_mnemonic_trigger_get_keyval (GtkMnemonicTrigger *self)
963{
964 g_return_val_if_fail (GTK_IS_MNEMONIC_TRIGGER (self), 0);
965
966 return self->keyval;
967}
968
969/*** GTK_ALTERNATIVE_TRIGGER ***/
970
971struct _GtkAlternativeTrigger
972{
973 GtkShortcutTrigger parent_instance;
974
975 GtkShortcutTrigger *first;
976 GtkShortcutTrigger *second;
977};
978
979struct _GtkAlternativeTriggerClass
980{
981 GtkShortcutTriggerClass parent_class;
982};
983
984G_DEFINE_TYPE (GtkAlternativeTrigger, gtk_alternative_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
985
986enum
987{
988 ALTERNATIVE_PROP_FIRST = 1,
989 ALTERNATIVE_PROP_SECOND,
990 ALTERNATIVE_N_PROPS
991};
992
993static GParamSpec *alternative_props[ALTERNATIVE_N_PROPS];
994
995static void
996gtk_alternative_trigger_dispose (GObject *gobject)
997{
998 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: gobject);
999
1000 g_clear_object (&self->first);
1001 g_clear_object (&self->second);
1002
1003 G_OBJECT_CLASS (gtk_alternative_trigger_parent_class)->dispose (gobject);
1004}
1005
1006static GdkKeyMatch
1007gtk_alternative_trigger_trigger (GtkShortcutTrigger *trigger,
1008 GdkEvent *event,
1009 gboolean enable_mnemonics)
1010{
1011 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: trigger);
1012
1013 return MAX (gtk_shortcut_trigger_trigger (self->first, event, enable_mnemonics),
1014 gtk_shortcut_trigger_trigger (self->second, event, enable_mnemonics));
1015}
1016
1017static guint
1018gtk_alternative_trigger_hash (GtkShortcutTrigger *trigger)
1019{
1020 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: trigger);
1021 guint result;
1022
1023 result = gtk_shortcut_trigger_hash (trigger: self->first);
1024 result <<= 5;
1025
1026 result |= gtk_shortcut_trigger_hash (trigger: self->second);
1027 result <<= 5;
1028
1029 return result | GTK_SHORTCUT_TRIGGER_HASH_ALTERNATIVE;
1030}
1031
1032static int
1033gtk_alternative_trigger_compare (GtkShortcutTrigger *trigger1,
1034 GtkShortcutTrigger *trigger2)
1035{
1036 GtkAlternativeTrigger *self1 = GTK_ALTERNATIVE_TRIGGER (ptr: trigger1);
1037 GtkAlternativeTrigger *self2 = GTK_ALTERNATIVE_TRIGGER (ptr: trigger2);
1038 int cmp;
1039
1040 cmp = gtk_shortcut_trigger_compare (trigger1: self1->first, trigger2: self2->first);
1041 if (cmp != 0)
1042 return cmp;
1043
1044 return gtk_shortcut_trigger_compare (trigger1: self1->second, trigger2: self2->second);
1045}
1046
1047static void
1048gtk_alternative_trigger_print (GtkShortcutTrigger *trigger,
1049 GString *string)
1050{
1051 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: trigger);
1052
1053 gtk_shortcut_trigger_print (self: self->first, string);
1054 g_string_append (string, val: "|");
1055 gtk_shortcut_trigger_print (self: self->second, string);
1056}
1057
1058static gboolean
1059gtk_alternative_trigger_print_label (GtkShortcutTrigger *trigger,
1060 GdkDisplay *display,
1061 GString *string)
1062{
1063 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: trigger);
1064
1065 if (gtk_shortcut_trigger_print_label (self: self->first, display, string))
1066 {
1067 g_string_append (string, val: ", ");
1068 if (!gtk_shortcut_trigger_print_label (self: self->second, display, string))
1069 g_string_truncate (string, len: string->len - 2);
1070 return TRUE;
1071 }
1072 else
1073 {
1074 return gtk_shortcut_trigger_print_label (self: self->second, display, string);
1075 }
1076}
1077
1078static void
1079gtk_alternative_trigger_set_property (GObject *gobject,
1080 guint prop_id,
1081 const GValue *value,
1082 GParamSpec *pspec)
1083{
1084 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: gobject);
1085
1086 switch (prop_id)
1087 {
1088 case ALTERNATIVE_PROP_FIRST:
1089 self->first = g_value_dup_object (value);
1090 break;
1091
1092 case ALTERNATIVE_PROP_SECOND:
1093 self->second = g_value_dup_object (value);
1094 break;
1095
1096 default:
1097 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1098 }
1099}
1100
1101static void
1102gtk_alternative_trigger_get_property (GObject *gobject,
1103 guint prop_id,
1104 GValue *value,
1105 GParamSpec *pspec)
1106{
1107 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: gobject);
1108
1109 switch (prop_id)
1110 {
1111 case ALTERNATIVE_PROP_FIRST:
1112 g_value_set_object (value, v_object: self->first);
1113 break;
1114
1115 case ALTERNATIVE_PROP_SECOND:
1116 g_value_set_object (value, v_object: self->second);
1117 break;
1118
1119 default:
1120 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1121 }
1122}
1123
1124static void
1125gtk_alternative_trigger_constructed (GObject *gobject)
1126{
1127 GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (ptr: gobject);
1128
1129 if (self->first == NULL || self->second == NULL)
1130 {
1131 g_critical ("Invalid alternative trigger, disabling");
1132 self->first = g_object_ref (gtk_never_trigger_get ());
1133 self->second = g_object_ref (gtk_never_trigger_get ());
1134 }
1135
1136 G_OBJECT_CLASS (gtk_alternative_trigger_parent_class)->constructed (gobject);
1137}
1138
1139static void
1140gtk_alternative_trigger_class_init (GtkAlternativeTriggerClass *klass)
1141{
1142 GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (ptr: klass);
1143 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1144
1145 gobject_class->constructed = gtk_alternative_trigger_constructed;
1146 gobject_class->set_property = gtk_alternative_trigger_set_property;
1147 gobject_class->get_property = gtk_alternative_trigger_get_property;
1148 gobject_class->dispose = gtk_alternative_trigger_dispose;
1149
1150 trigger_class->trigger = gtk_alternative_trigger_trigger;
1151 trigger_class->hash = gtk_alternative_trigger_hash;
1152 trigger_class->compare = gtk_alternative_trigger_compare;
1153 trigger_class->print = gtk_alternative_trigger_print;
1154 trigger_class->print_label = gtk_alternative_trigger_print_label;
1155
1156 /**
1157 * GtkAlternativeTrigger:first: (attributes org.gtk.Property.get=gtk_alternative_trigger_get_first)
1158 *
1159 * The first `GtkShortcutTrigger` to check.
1160 */
1161 alternative_props[ALTERNATIVE_PROP_FIRST] =
1162 g_param_spec_object (I_("first"),
1163 P_("First"),
1164 P_("The first trigger to check"),
1165 GTK_TYPE_SHORTCUT_TRIGGER,
1166 G_PARAM_STATIC_STRINGS |
1167 G_PARAM_CONSTRUCT_ONLY |
1168 G_PARAM_READWRITE);
1169
1170 /**
1171 * GtkAlternativeTrigger:second: (attributes org.gtk.Property.get=gtk_alternative_trigger_get_second)
1172 *
1173 * The second `GtkShortcutTrigger` to check.
1174 */
1175 alternative_props[ALTERNATIVE_PROP_SECOND] =
1176 g_param_spec_object (I_("second"),
1177 P_("Second"),
1178 P_("The second trigger to check"),
1179 GTK_TYPE_SHORTCUT_TRIGGER,
1180 G_PARAM_STATIC_STRINGS |
1181 G_PARAM_CONSTRUCT_ONLY |
1182 G_PARAM_READWRITE);
1183
1184 g_object_class_install_properties (oclass: gobject_class, n_pspecs: ALTERNATIVE_N_PROPS, pspecs: alternative_props);
1185}
1186
1187static void
1188gtk_alternative_trigger_init (GtkAlternativeTrigger *self)
1189{
1190}
1191
1192/**
1193 * gtk_alternative_trigger_new:
1194 * @first: (transfer full): The first trigger that may trigger
1195 * @second: (transfer full): The second trigger that may trigger
1196 *
1197 * Creates a `GtkShortcutTrigger` that will trigger whenever
1198 * either of the two given triggers gets triggered.
1199 *
1200 * Note that nesting is allowed, so if you want more than two
1201 * alternative, create a new alternative trigger for each option.
1202 *
1203 * Returns: a new `GtkShortcutTrigger`
1204 */
1205GtkShortcutTrigger *
1206gtk_alternative_trigger_new (GtkShortcutTrigger *first,
1207 GtkShortcutTrigger *second)
1208{
1209 GtkShortcutTrigger *res;
1210
1211 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (first), NULL);
1212 g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (second), NULL);
1213
1214 res = g_object_new (GTK_TYPE_ALTERNATIVE_TRIGGER,
1215 first_property_name: "first", first,
1216 "second", second,
1217 NULL);
1218
1219 g_object_unref (object: first);
1220 g_object_unref (object: second);
1221
1222 return res;
1223}
1224
1225/**
1226 * gtk_alternative_trigger_get_first: (attributes org.gtk.Method.get_property=first)
1227 * @self: an alternative `GtkShortcutTrigger`
1228 *
1229 * Gets the first of the two alternative triggers that may
1230 * trigger @self.
1231 *
1232 * [method@Gtk.AlternativeTrigger.get_second] will return
1233 * the other one.
1234 *
1235 * Returns: (transfer none): the first alternative trigger
1236 */
1237GtkShortcutTrigger *
1238gtk_alternative_trigger_get_first (GtkAlternativeTrigger *self)
1239{
1240 g_return_val_if_fail (GTK_IS_ALTERNATIVE_TRIGGER (self), NULL);
1241
1242 return self->first;
1243}
1244
1245/**
1246 * gtk_alternative_trigger_get_second: (attributes org.gtk.Method.get_property=second)
1247 * @self: an alternative `GtkShortcutTrigger`
1248 *
1249 * Gets the second of the two alternative triggers that may
1250 * trigger @self.
1251 *
1252 * [method@Gtk.AlternativeTrigger.get_first] will return
1253 * the other one.
1254 *
1255 * Returns: (transfer none): the second alternative trigger
1256 */
1257GtkShortcutTrigger *
1258gtk_alternative_trigger_get_second (GtkAlternativeTrigger *self)
1259{
1260 g_return_val_if_fail (GTK_IS_ALTERNATIVE_TRIGGER (self), NULL);
1261
1262 return self->second;
1263}
1264

source code of gtk/gtk/gtkshortcuttrigger.c