1 /* dzl-shortcut-chord.c
2  *
3  * Copyright (C) 2017 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-chord"
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "shortcuts/dzl-shortcut-chord.h"
27 #include "shortcuts/dzl-shortcut-private.h"
28 #include "util/dzl-macros.h"
29 
30 #define MAX_CHORD_SIZE 4
31 #define CHORD_MAGIC    0x83316672
32 
33 G_DEFINE_BOXED_TYPE (DzlShortcutChord, dzl_shortcut_chord,
34                      dzl_shortcut_chord_copy, dzl_shortcut_chord_free)
35 G_DEFINE_POINTER_TYPE (DzlShortcutChordTable, dzl_shortcut_chord_table)
36 
37 typedef struct
38 {
39   guint           keyval;
40   GdkModifierType modifier;
41 } DzlShortcutKey;
42 
43 struct _DzlShortcutChord
44 {
45   DzlShortcutKey keys[MAX_CHORD_SIZE];
46   guint magic;
47 };
48 
49 typedef struct
50 {
51   DzlShortcutChord chord;
52   gpointer data;
53 } DzlShortcutChordTableEntry;
54 
55 struct _DzlShortcutChordTable
56 {
57   DzlShortcutChordTableEntry *entries;
58   GDestroyNotify              destroy;
59   guint                       len;
60   guint                       size;
61 };
62 
63 static inline gboolean
IS_SHORTCUT_CHORD(const DzlShortcutChord * chord)64 IS_SHORTCUT_CHORD (const DzlShortcutChord *chord)
65 {
66   return chord != NULL && chord->magic == CHORD_MAGIC;
67 }
68 
69 static GdkModifierType
sanitize_modifier_mask(GdkModifierType mods)70 sanitize_modifier_mask (GdkModifierType mods)
71 {
72   mods &= gtk_accelerator_get_default_mod_mask ();
73   mods &= ~GDK_LOCK_MASK;
74 
75   return mods;
76 }
77 
78 static gint
dzl_shortcut_chord_compare(const DzlShortcutChord * a,const DzlShortcutChord * b)79 dzl_shortcut_chord_compare (const DzlShortcutChord *a,
80                             const DzlShortcutChord *b)
81 {
82   g_assert (IS_SHORTCUT_CHORD (a));
83   g_assert (IS_SHORTCUT_CHORD (b));
84 
85   return memcmp (a, b, sizeof *a);
86 }
87 
88 static gboolean
dzl_shortcut_chord_is_valid(const DzlShortcutChord * self)89 dzl_shortcut_chord_is_valid (const DzlShortcutChord *self)
90 {
91   g_assert (IS_SHORTCUT_CHORD (self));
92 
93   /* Ensure we got a valid first key at least */
94   if (self->keys[0].keyval == 0 && self->keys[0].modifier == 0)
95     return FALSE;
96 
97   return TRUE;
98 }
99 
100 DzlShortcutChord *
dzl_shortcut_chord_new_from_event(const GdkEventKey * key)101 dzl_shortcut_chord_new_from_event (const GdkEventKey *key)
102 {
103   DzlShortcutChord *self;
104 
105   g_return_val_if_fail (key != NULL, NULL);
106 
107   self = g_slice_new0 (DzlShortcutChord);
108   self->magic = CHORD_MAGIC;
109 
110   self->keys[0].keyval = gdk_keyval_to_lower (key->keyval);
111   self->keys[0].modifier = sanitize_modifier_mask (key->state);
112 
113   if ((key->state & GDK_SHIFT_MASK) != 0 &&
114       self->keys[0].keyval == key->keyval)
115     self->keys[0].modifier &= ~GDK_SHIFT_MASK;
116 
117   if ((key->state & GDK_LOCK_MASK) == 0 &&
118       self->keys[0].keyval != key->keyval)
119     self->keys[0].modifier |= GDK_SHIFT_MASK;
120 
121   if (!dzl_shortcut_chord_is_valid (self))
122     g_clear_pointer (&self, dzl_shortcut_chord_free);
123 
124   return self;
125 }
126 
127 DzlShortcutChord *
dzl_shortcut_chord_new_from_string(const gchar * accelerator)128 dzl_shortcut_chord_new_from_string (const gchar *accelerator)
129 {
130   DzlShortcutChord *self;
131   g_auto(GStrv) parts = NULL;
132 
133   g_return_val_if_fail (accelerator != NULL, NULL);
134 
135   /* We might have a single key, or chord defined */
136   parts = g_strsplit (accelerator, "|", 0);
137 
138   /* Make sure we won't overflow the keys array */
139   if (g_strv_length (parts) > G_N_ELEMENTS (self->keys))
140     return NULL;
141 
142   self = g_slice_new0 (DzlShortcutChord);
143   self->magic = CHORD_MAGIC;
144 
145   /* Parse each section from the accelerator */
146   for (guint i = 0; parts[i]; i++)
147     gtk_accelerator_parse (parts[i], &self->keys[i].keyval, &self->keys[i].modifier);
148 
149   /* Ensure we got a valid first key at least */
150   if (!dzl_shortcut_chord_is_valid (self))
151     g_clear_pointer (&self, dzl_shortcut_chord_free);
152 
153   return self;
154 }
155 
156 gboolean
dzl_shortcut_chord_append_event(DzlShortcutChord * self,const GdkEventKey * key)157 dzl_shortcut_chord_append_event (DzlShortcutChord  *self,
158                                  const GdkEventKey *key)
159 {
160   guint i;
161 
162   g_return_val_if_fail (IS_SHORTCUT_CHORD (self), FALSE);
163   g_return_val_if_fail (key != NULL, FALSE);
164 
165   for (i = 0; i < G_N_ELEMENTS (self->keys); i++)
166     {
167       /* We might have just a state (control, etc), and we want
168        * to consume that if we've gotten another key here. So we
169        * only check against key.keyval.
170        */
171       if (self->keys[i].keyval == 0)
172         {
173           self->keys[i].keyval = gdk_keyval_to_lower (key->keyval);
174           self->keys[i].modifier = sanitize_modifier_mask (key->state);
175 
176           if ((key->state & GDK_LOCK_MASK) == 0 &&
177               self->keys[i].keyval != key->keyval)
178             self->keys[i].modifier |= GDK_SHIFT_MASK;
179 
180           return TRUE;
181         }
182     }
183 
184   return FALSE;
185 }
186 
187 static inline guint
dzl_shortcut_chord_count_keys(const DzlShortcutChord * self)188 dzl_shortcut_chord_count_keys (const DzlShortcutChord *self)
189 {
190   guint count = 0;
191 
192   g_assert (IS_SHORTCUT_CHORD (self));
193 
194   for (guint i = 0; i < G_N_ELEMENTS (self->keys); i++)
195     {
196       if (self->keys[i].keyval != 0)
197         count++;
198       else
199         break;
200     }
201 
202   return count;
203 }
204 
205 DzlShortcutMatch
dzl_shortcut_chord_match(const DzlShortcutChord * self,const DzlShortcutChord * other)206 dzl_shortcut_chord_match (const DzlShortcutChord *self,
207                           const DzlShortcutChord *other)
208 {
209   guint self_count = 0;
210   guint other_count = 0;
211 
212   g_return_val_if_fail (IS_SHORTCUT_CHORD (self), DZL_SHORTCUT_MATCH_NONE);
213   g_return_val_if_fail (other != NULL, DZL_SHORTCUT_MATCH_NONE);
214 
215   self_count = dzl_shortcut_chord_count_keys (self);
216   other_count = dzl_shortcut_chord_count_keys (other);
217 
218   if (self_count > other_count)
219     return DZL_SHORTCUT_MATCH_NONE;
220 
221   if (0 == memcmp (self->keys, other->keys, sizeof (DzlShortcutKey) * self_count))
222     return self_count == other_count ? DZL_SHORTCUT_MATCH_EQUAL : DZL_SHORTCUT_MATCH_PARTIAL;
223 
224   return DZL_SHORTCUT_MATCH_NONE;
225 }
226 
227 gchar *
dzl_shortcut_chord_to_string(const DzlShortcutChord * self)228 dzl_shortcut_chord_to_string (const DzlShortcutChord *self)
229 {
230   GString *str;
231 
232   g_assert (IS_SHORTCUT_CHORD (self));
233 
234   if (self == NULL || self->keys[0].keyval == 0)
235     return NULL;
236 
237   str = g_string_new (NULL);
238 
239   for (guint i = 0; i < G_N_ELEMENTS (self->keys); i++)
240     {
241       const DzlShortcutKey *key = &self->keys[i];
242       g_autofree gchar *name = NULL;
243 
244       if (key->keyval == 0 && key->modifier == 0)
245         break;
246 
247       name = gtk_accelerator_name (key->keyval, key->modifier);
248 
249       if (i != 0)
250         g_string_append_c (str, '|');
251 
252       g_string_append (str, name);
253     }
254 
255   return g_string_free (str, FALSE);
256 }
257 
258 gchar *
dzl_shortcut_chord_get_label(const DzlShortcutChord * self)259 dzl_shortcut_chord_get_label (const DzlShortcutChord *self)
260 {
261   GString *str;
262 
263   if (self == NULL || self->keys[0].keyval == 0)
264     return NULL;
265 
266   g_return_val_if_fail (IS_SHORTCUT_CHORD (self), NULL);
267 
268   str = g_string_new (NULL);
269 
270   for (guint i = 0; i < G_N_ELEMENTS (self->keys); i++)
271     {
272       const DzlShortcutKey *key = &self->keys[i];
273       g_autofree gchar *name = NULL;
274 
275       if (key->keyval == 0 && key->modifier == 0)
276         break;
277 
278       name = gtk_accelerator_get_label (key->keyval, key->modifier);
279 
280       if (i != 0)
281         g_string_append_c (str, ' ');
282 
283       g_string_append (str, name);
284     }
285 
286   return g_string_free (str, FALSE);
287 }
288 
289 DzlShortcutChord *
dzl_shortcut_chord_copy(const DzlShortcutChord * self)290 dzl_shortcut_chord_copy (const DzlShortcutChord *self)
291 {
292   if (self == NULL)
293     return NULL;
294 
295   return g_slice_dup (DzlShortcutChord, self);
296 }
297 
298 guint
dzl_shortcut_chord_hash(gconstpointer data)299 dzl_shortcut_chord_hash (gconstpointer data)
300 {
301   const DzlShortcutChord *self = data;
302   guint hash = 0;
303 
304   g_assert (IS_SHORTCUT_CHORD (self));
305 
306   for (guint i = 0; i < G_N_ELEMENTS (self->keys); i++)
307     {
308       const DzlShortcutKey *key = &self->keys[i];
309 
310       hash ^= key->keyval;
311       hash ^= key->modifier;
312     }
313 
314   return hash;
315 }
316 
317 gboolean
dzl_shortcut_chord_equal(gconstpointer data1,gconstpointer data2)318 dzl_shortcut_chord_equal (gconstpointer data1,
319                           gconstpointer data2)
320 {
321   if (data1 == data2)
322     return TRUE;
323   else if (data1 == NULL || data2 == NULL)
324     return FALSE;
325 
326   return 0 == memcmp (((const DzlShortcutChord *)data1)->keys,
327                       ((const DzlShortcutChord *)data2)->keys,
328                       sizeof (DzlShortcutChord));
329 }
330 
331 void
dzl_shortcut_chord_free(DzlShortcutChord * self)332 dzl_shortcut_chord_free (DzlShortcutChord *self)
333 {
334   g_assert (!self || IS_SHORTCUT_CHORD (self));
335 
336   if (self != NULL)
337     {
338       self->magic = 0xAAAAAAAA;
339       g_slice_free (DzlShortcutChord, self);
340     }
341 }
342 
343 GType
dzl_shortcut_match_get_type(void)344 dzl_shortcut_match_get_type (void)
345 {
346   static GType type_id;
347 
348   if (g_once_init_enter (&type_id))
349     {
350       static GEnumValue values[] = {
351         { DZL_SHORTCUT_MATCH_NONE, "DZL_SHORTCUT_MATCH_NONE", "none" },
352         { DZL_SHORTCUT_MATCH_EQUAL, "DZL_SHORTCUT_MATCH_EQUAL", "equal" },
353         { DZL_SHORTCUT_MATCH_PARTIAL, "DZL_SHORTCUT_MATCH_PARTIAL", "partial" },
354         { 0 }
355       };
356       GType _type_id = g_enum_register_static ("DzlShortcutMatch", values);
357       g_once_init_leave (&type_id, _type_id);
358     }
359 
360   return type_id;
361 }
362 
363 static gint
dzl_shortcut_chord_table_sort(gconstpointer a,gconstpointer b)364 dzl_shortcut_chord_table_sort (gconstpointer a,
365                                gconstpointer b)
366 {
367   const DzlShortcutChordTableEntry *keya = a;
368   const DzlShortcutChordTableEntry *keyb = b;
369 
370   g_assert (IS_SHORTCUT_CHORD (a));
371   g_assert (IS_SHORTCUT_CHORD (b));
372 
373   return dzl_shortcut_chord_compare (&keya->chord, &keyb->chord);
374 }
375 
376 /**
377  * dzl_shortcut_chord_table_new: (skip)
378  */
379 DzlShortcutChordTable *
dzl_shortcut_chord_table_new(void)380 dzl_shortcut_chord_table_new (void)
381 {
382   DzlShortcutChordTable *table;
383 
384   table = g_slice_new0 (DzlShortcutChordTable);
385   table->len = 0;
386   table->size = 4;
387   table->destroy = NULL;
388   table->entries = g_new0 (DzlShortcutChordTableEntry, table->size);
389 
390   return table;
391 }
392 
393 void
dzl_shortcut_chord_table_free(DzlShortcutChordTable * self)394 dzl_shortcut_chord_table_free (DzlShortcutChordTable *self)
395 {
396   if (self != NULL)
397     {
398       if (self->destroy != NULL)
399         {
400           for (guint i = 0; i < self->len; i++)
401             self->destroy (self->entries[i].data);
402         }
403       g_free (self->entries);
404       g_slice_free (DzlShortcutChordTable, self);
405     }
406 }
407 
408 void
dzl_shortcut_chord_table_add(DzlShortcutChordTable * self,const DzlShortcutChord * chord,gpointer data)409 dzl_shortcut_chord_table_add (DzlShortcutChordTable  *self,
410                               const DzlShortcutChord *chord,
411                               gpointer                data)
412 {
413   g_return_if_fail (self != NULL);
414   g_return_if_fail (chord != NULL);
415 
416   if (self->len == self->size)
417     {
418       self->size *= 2;
419       self->entries = g_renew (DzlShortcutChordTableEntry, self->entries, self->size);
420     }
421 
422   self->entries[self->len].chord = *chord;
423   self->entries[self->len].data = data;
424 
425   self->len++;
426 
427   qsort (self->entries,
428          self->len,
429          sizeof (DzlShortcutChordTableEntry),
430          dzl_shortcut_chord_table_sort);
431 }
432 
433 static void
dzl_shortcut_chord_table_remove_index(DzlShortcutChordTable * self,guint position)434 dzl_shortcut_chord_table_remove_index (DzlShortcutChordTable *self,
435                                        guint                  position)
436 {
437   DzlShortcutChordTableEntry *entry;
438   gpointer data;
439 
440   g_assert (self != NULL);
441   g_assert (position < self->len);
442 
443   entry = &self->entries[position];
444   data = g_steal_pointer (&entry->data);
445 
446   if (position + 1 < self->len)
447     memmove ((gpointer)entry,
448              entry + 1,
449              sizeof *entry * (self->len - position - 1));
450 
451   self->len--;
452 
453   if (self->destroy != NULL)
454     self->destroy (data);
455 }
456 
457 gboolean
dzl_shortcut_chord_table_remove(DzlShortcutChordTable * self,const DzlShortcutChord * chord)458 dzl_shortcut_chord_table_remove (DzlShortcutChordTable  *self,
459                                  const DzlShortcutChord *chord)
460 {
461   g_return_val_if_fail (self != NULL, FALSE);
462 
463   if (chord == NULL)
464     return FALSE;
465 
466   for (guint i = 0; i < self->len; i++)
467     {
468       const DzlShortcutChordTableEntry *ele = &self->entries[i];
469 
470       if (dzl_shortcut_chord_equal (&ele->chord, chord))
471         {
472           dzl_shortcut_chord_table_remove_index (self, i);
473           return TRUE;
474         }
475     }
476 
477   return FALSE;
478 }
479 
480 gboolean
dzl_shortcut_chord_table_remove_data(DzlShortcutChordTable * self,gpointer data)481 dzl_shortcut_chord_table_remove_data (DzlShortcutChordTable *self,
482                                       gpointer               data)
483 {
484   g_return_val_if_fail (self != NULL, FALSE);
485 
486   for (guint i = 0; i < self->len; i++)
487     {
488       const DzlShortcutChordTableEntry *ele = &self->entries[i];
489 
490       if (ele->data == data)
491         {
492           dzl_shortcut_chord_table_remove_index (self, i);
493           return TRUE;
494         }
495     }
496 
497   return FALSE;
498 }
499 
500 const DzlShortcutChord *
dzl_shortcut_chord_table_lookup_data(DzlShortcutChordTable * self,gpointer data)501 dzl_shortcut_chord_table_lookup_data (DzlShortcutChordTable *self,
502                                       gpointer               data)
503 {
504   if (self == NULL)
505     return NULL;
506 
507   for (guint i = 0; i < self->len; i++)
508     {
509       const DzlShortcutChordTableEntry *ele = &self->entries[i];
510 
511       if (ele->data == data)
512         return &ele->chord;
513     }
514 
515   return NULL;
516 }
517 
518 static gint
dzl_shortcut_chord_find_partial(gconstpointer a,gconstpointer b)519 dzl_shortcut_chord_find_partial (gconstpointer a,
520                                  gconstpointer b)
521 {
522   const DzlShortcutChord *key = a;
523   const DzlShortcutChordTableEntry *element = b;
524 
525   /*
526    * We are only looking for a partial match here so that we can walk backwards
527    * after the bsearch to the first partial match.
528    */
529   if (dzl_shortcut_chord_match (key, &element->chord) != DZL_SHORTCUT_MATCH_NONE)
530     return 0;
531 
532   return dzl_shortcut_chord_compare (key, &element->chord);
533 }
534 
535 DzlShortcutMatch
dzl_shortcut_chord_table_lookup(DzlShortcutChordTable * self,const DzlShortcutChord * chord,gpointer * data)536 dzl_shortcut_chord_table_lookup (DzlShortcutChordTable  *self,
537                                  const DzlShortcutChord *chord,
538                                  gpointer               *data)
539 {
540   const DzlShortcutChordTableEntry *match;
541 
542   if (data != NULL)
543     *data = NULL;
544 
545   if (self == NULL)
546     return DZL_SHORTCUT_MATCH_NONE;
547 
548   if (chord == NULL)
549     return DZL_SHORTCUT_MATCH_NONE;
550 
551   if (self->len == 0)
552     return DZL_SHORTCUT_MATCH_NONE;
553 
554   /*
555    * This function works by performing a binary search to locate ourself
556    * somewhere within a match zone of the array. Once we are there, we walk
557    * back to the first item that is a partial match.  After that, we walk
558    * through every potential match looking for an exact match until we reach a
559    * non-partial-match or the end of the array.
560    *
561    * Based on our findings, we return the appropriate DzlShortcutMatch.
562    */
563 
564   match = bsearch (chord, self->entries, self->len, sizeof (DzlShortcutChordTableEntry),
565                    dzl_shortcut_chord_find_partial);
566 
567   if (match != NULL)
568     {
569       const DzlShortcutChordTableEntry *begin = self->entries;
570       const DzlShortcutChordTableEntry *end = self->entries + self->len;
571       DzlShortcutMatch ret = DZL_SHORTCUT_MATCH_PARTIAL;
572 
573       /* Find the first patial match */
574       while ((match - 1) >= begin &&
575              dzl_shortcut_chord_match (chord, &(match - 1)->chord) != DZL_SHORTCUT_MATCH_NONE)
576         match--;
577 
578       g_assert (match >= begin);
579 
580       /* Now walk forward to see if we have an exact match */
581       while (DZL_SHORTCUT_MATCH_NONE != (ret = dzl_shortcut_chord_match (chord, &match->chord)))
582         {
583           if (ret == DZL_SHORTCUT_MATCH_EQUAL)
584             {
585               if (data != NULL)
586                 *data = match->data;
587               return DZL_SHORTCUT_MATCH_EQUAL;
588             }
589 
590           match++;
591 
592           g_assert (match <= end);
593 
594           if (ret == 0 || match == end)
595             break;
596         }
597 
598       return DZL_SHORTCUT_MATCH_PARTIAL;
599     }
600 
601   return DZL_SHORTCUT_MATCH_NONE;
602 }
603 
604 void
dzl_shortcut_chord_table_set_free_func(DzlShortcutChordTable * self,GDestroyNotify destroy)605 dzl_shortcut_chord_table_set_free_func (DzlShortcutChordTable *self,
606                                         GDestroyNotify         destroy)
607 {
608   g_return_if_fail (self != NULL);
609 
610   self->destroy = destroy;
611 }
612 
613 guint
dzl_shortcut_chord_table_size(const DzlShortcutChordTable * self)614 dzl_shortcut_chord_table_size (const DzlShortcutChordTable *self)
615 {
616   /* I know this is confusing, but len is the number of items in the
617    * table (which consumers think of as size), and @size is the allocated
618    * size of the ^2 growing array.
619    */
620   return self ? self->len : 0;
621 }
622 
623 void
dzl_shortcut_chord_table_printf(const DzlShortcutChordTable * self)624 dzl_shortcut_chord_table_printf (const DzlShortcutChordTable *self)
625 {
626   if (self == NULL)
627     return;
628 
629   for (guint i = 0; i < self->len; i++)
630     {
631       const DzlShortcutChordTableEntry *entry = &self->entries[i];
632       g_autofree gchar *str = dzl_shortcut_chord_to_string (&entry->chord);
633 
634       g_print ("%s\n", str);
635     }
636 }
637 
638 void
_dzl_shortcut_chord_table_iter_init(DzlShortcutChordTableIter * iter,DzlShortcutChordTable * table)639 _dzl_shortcut_chord_table_iter_init (DzlShortcutChordTableIter *iter,
640                                      DzlShortcutChordTable     *table)
641 {
642   g_return_if_fail (iter != NULL);
643 
644   iter->table = table;
645   iter->position = 0;
646 }
647 
648 gboolean
_dzl_shortcut_chord_table_iter_next(DzlShortcutChordTableIter * iter,const DzlShortcutChord ** chord,gpointer * value)649 _dzl_shortcut_chord_table_iter_next (DzlShortcutChordTableIter  *iter,
650                                      const DzlShortcutChord    **chord,
651                                      gpointer                   *value)
652 {
653   g_return_val_if_fail (iter != NULL, FALSE);
654 
655   /*
656    * Be safe against NULL tables which we allow in
657    * _dzl_shortcut_chord_table_iter_init() for convenience.
658    */
659   if (iter->table == NULL)
660     return FALSE;
661 
662   if (iter->position < iter->table->len)
663     {
664       *chord = &iter->table->entries[iter->position].chord;
665       *value = iter->table->entries[iter->position].data;
666       iter->position++;
667       return TRUE;
668     }
669 
670   return FALSE;
671 }
672 
673 void
_dzl_shortcut_chord_table_iter_steal(DzlShortcutChordTableIter * iter)674 _dzl_shortcut_chord_table_iter_steal (DzlShortcutChordTableIter  *iter)
675 {
676   g_return_if_fail (iter != NULL);
677   g_return_if_fail (iter->table != NULL);
678 
679   if (iter->position > 0 && iter->position < iter->table->len)
680     {
681       dzl_shortcut_chord_table_remove_index (iter->table, --iter->position);
682       return;
683     }
684 
685   g_warning ("Attempt to steal item from table that does not exist");
686 }
687 
688 gboolean
dzl_shortcut_chord_has_modifier(const DzlShortcutChord * self)689 dzl_shortcut_chord_has_modifier (const DzlShortcutChord *self)
690 {
691   g_return_val_if_fail (self != NULL, FALSE);
692 
693   return self->keys[0].modifier != 0;
694 }
695 
696 guint
dzl_shortcut_chord_get_length(const DzlShortcutChord * self)697 dzl_shortcut_chord_get_length (const DzlShortcutChord *self)
698 {
699   if (self != NULL)
700     {
701       for (guint i = 0; i < G_N_ELEMENTS (self->keys); i++)
702         {
703           if (self->keys[i].keyval == 0)
704             return i;
705         }
706 
707       return G_N_ELEMENTS (self->keys);
708     }
709 
710   return 0;
711 }
712 
713 void
dzl_shortcut_chord_get_nth_key(const DzlShortcutChord * self,guint nth,guint * keyval,GdkModifierType * modifier)714 dzl_shortcut_chord_get_nth_key (const DzlShortcutChord *self,
715                                 guint                   nth,
716                                 guint                  *keyval,
717                                 GdkModifierType        *modifier)
718 {
719   if (nth < G_N_ELEMENTS (self->keys))
720     {
721       if (keyval)
722         *keyval = self->keys[nth].keyval;
723       if (modifier)
724         *modifier = self->keys[nth].modifier;
725     }
726   else
727     {
728       if (keyval)
729         *keyval = 0;
730       if (modifier)
731         *modifier = 0;
732     }
733 }
734 
735 /**
736  * dzl_shortcut_chord_table_foreach:
737  * @self: a #DzlShortcutChordTable
738  * @foreach_func: (scope call) (closure foreach_data): A callback for each chord
739  * @foreach_data: user data for @foreach_func
740  *
741  * This function will call @foreach_func for each chord in the table.
742  */
743 void
dzl_shortcut_chord_table_foreach(const DzlShortcutChordTable * self,DzlShortcutChordTableForeach foreach_func,gpointer foreach_data)744 dzl_shortcut_chord_table_foreach (const DzlShortcutChordTable  *self,
745                                   DzlShortcutChordTableForeach  foreach_func,
746                                   gpointer                      foreach_data)
747 {
748   g_return_if_fail (foreach_func != NULL);
749 
750   if (self == NULL)
751     return;
752 
753   /*
754    * Walk backwards just in case the caller somehow thinks it is okay to
755    * remove items while iterating the list. We don't officially support that
756    * (which is why self is const), but this is just defensive.
757    */
758 
759   for (guint i = self->len; i > 0; i--)
760     {
761       const DzlShortcutChordTableEntry *entry = &self->entries[i-1];
762 
763       foreach_func (&entry->chord, entry->data, foreach_data);
764     }
765 }
766