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