1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1998, 2001 Tim Janik
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26#include <string.h>
27#include <stdlib.h>
28
29#include "gtkaccelgroup.h"
30#include "gtkaccelgroupprivate.h"
31#include "gtkintl.h"
32#include "gtkmarshalers.h"
33#include "gtkprivate.h"
34
35/* --- functions --- */
36/**
37 * gtk_accelerator_valid:
38 * @keyval: a GDK keyval
39 * @modifiers: modifier mask
40 *
41 * Determines whether a given keyval and modifier mask constitute
42 * a valid keyboard accelerator.
43 *
44 * For example, the %GDK_KEY_a keyval plus %GDK_CONTROL_MASK mark is valid,
45 * and matches the “Ctrl+a” accelerator. But, you can't, for instance, use
46 * the %GDK_KEY_Control_L keyval as an accelerator.
47 *
48 * Returns: %TRUE if the accelerator is valid
49 */
50gboolean
51gtk_accelerator_valid (guint keyval,
52 GdkModifierType modifiers)
53{
54 static const guint invalid_accelerator_vals[] = {
55 GDK_KEY_Shift_L, GDK_KEY_Shift_R, GDK_KEY_Shift_Lock,
56 GDK_KEY_Caps_Lock, GDK_KEY_ISO_Lock, GDK_KEY_Control_L,
57 GDK_KEY_Control_R, GDK_KEY_Meta_L, GDK_KEY_Meta_R,
58 GDK_KEY_Alt_L, GDK_KEY_Alt_R, GDK_KEY_Super_L, GDK_KEY_Super_R,
59 GDK_KEY_Hyper_L, GDK_KEY_Hyper_R, GDK_KEY_ISO_Level3_Shift,
60 GDK_KEY_ISO_Next_Group, GDK_KEY_ISO_Prev_Group,
61 GDK_KEY_ISO_First_Group, GDK_KEY_ISO_Last_Group,
62 GDK_KEY_Mode_switch, GDK_KEY_Num_Lock, GDK_KEY_Multi_key,
63 GDK_KEY_Scroll_Lock, GDK_KEY_Sys_Req,
64 GDK_KEY_Tab, GDK_KEY_ISO_Left_Tab, GDK_KEY_KP_Tab,
65 GDK_KEY_First_Virtual_Screen, GDK_KEY_Prev_Virtual_Screen,
66 GDK_KEY_Next_Virtual_Screen, GDK_KEY_Last_Virtual_Screen,
67 GDK_KEY_Terminate_Server, GDK_KEY_AudibleBell_Enable,
68 0
69 };
70 static const guint invalid_unmodified_vals[] = {
71 GDK_KEY_Up, GDK_KEY_Down, GDK_KEY_Left, GDK_KEY_Right,
72 GDK_KEY_KP_Up, GDK_KEY_KP_Down, GDK_KEY_KP_Left, GDK_KEY_KP_Right,
73 0
74 };
75 const guint *ac_val;
76
77 modifiers &= GDK_MODIFIER_MASK;
78
79 if (keyval <= 0xFF)
80 return keyval >= 0x20;
81
82 ac_val = invalid_accelerator_vals;
83 while (*ac_val)
84 {
85 if (keyval == *ac_val++)
86 return FALSE;
87 }
88
89 if (!modifiers)
90 {
91 ac_val = invalid_unmodified_vals;
92 while (*ac_val)
93 {
94 if (keyval == *ac_val++)
95 return FALSE;
96 }
97 }
98
99 return TRUE;
100}
101
102static inline gboolean
103is_alt (const char *string)
104{
105 return ((string[0] == '<') &&
106 (string[1] == 'a' || string[1] == 'A') &&
107 (string[2] == 'l' || string[2] == 'L') &&
108 (string[3] == 't' || string[3] == 'T') &&
109 (string[4] == '>'));
110}
111
112static inline gboolean
113is_ctl (const char *string)
114{
115 return ((string[0] == '<') &&
116 (string[1] == 'c' || string[1] == 'C') &&
117 (string[2] == 't' || string[2] == 'T') &&
118 (string[3] == 'l' || string[3] == 'L') &&
119 (string[4] == '>'));
120}
121
122static inline gboolean
123is_ctrl (const char *string)
124{
125 return ((string[0] == '<') &&
126 (string[1] == 'c' || string[1] == 'C') &&
127 (string[2] == 't' || string[2] == 'T') &&
128 (string[3] == 'r' || string[3] == 'R') &&
129 (string[4] == 'l' || string[4] == 'L') &&
130 (string[5] == '>'));
131}
132
133static inline gboolean
134is_shft (const char *string)
135{
136 return ((string[0] == '<') &&
137 (string[1] == 's' || string[1] == 'S') &&
138 (string[2] == 'h' || string[2] == 'H') &&
139 (string[3] == 'f' || string[3] == 'F') &&
140 (string[4] == 't' || string[4] == 'T') &&
141 (string[5] == '>'));
142}
143
144static inline gboolean
145is_shift (const char *string)
146{
147 return ((string[0] == '<') &&
148 (string[1] == 's' || string[1] == 'S') &&
149 (string[2] == 'h' || string[2] == 'H') &&
150 (string[3] == 'i' || string[3] == 'I') &&
151 (string[4] == 'f' || string[4] == 'F') &&
152 (string[5] == 't' || string[5] == 'T') &&
153 (string[6] == '>'));
154}
155
156static inline gboolean
157is_control (const char *string)
158{
159 return ((string[0] == '<') &&
160 (string[1] == 'c' || string[1] == 'C') &&
161 (string[2] == 'o' || string[2] == 'O') &&
162 (string[3] == 'n' || string[3] == 'N') &&
163 (string[4] == 't' || string[4] == 'T') &&
164 (string[5] == 'r' || string[5] == 'R') &&
165 (string[6] == 'o' || string[6] == 'O') &&
166 (string[7] == 'l' || string[7] == 'L') &&
167 (string[8] == '>'));
168}
169
170static inline gboolean
171is_meta (const char *string)
172{
173 return ((string[0] == '<') &&
174 (string[1] == 'm' || string[1] == 'M') &&
175 (string[2] == 'e' || string[2] == 'E') &&
176 (string[3] == 't' || string[3] == 'T') &&
177 (string[4] == 'a' || string[4] == 'A') &&
178 (string[5] == '>'));
179}
180
181static inline gboolean
182is_super (const char *string)
183{
184 return ((string[0] == '<') &&
185 (string[1] == 's' || string[1] == 'S') &&
186 (string[2] == 'u' || string[2] == 'U') &&
187 (string[3] == 'p' || string[3] == 'P') &&
188 (string[4] == 'e' || string[4] == 'E') &&
189 (string[5] == 'r' || string[5] == 'R') &&
190 (string[6] == '>'));
191}
192
193static inline gboolean
194is_hyper (const char *string)
195{
196 return ((string[0] == '<') &&
197 (string[1] == 'h' || string[1] == 'H') &&
198 (string[2] == 'y' || string[2] == 'Y') &&
199 (string[3] == 'p' || string[3] == 'P') &&
200 (string[4] == 'e' || string[4] == 'E') &&
201 (string[5] == 'r' || string[5] == 'R') &&
202 (string[6] == '>'));
203}
204
205static inline gboolean
206is_primary (const char *string)
207{
208 return ((string[0] == '<') &&
209 (string[1] == 'p' || string[1] == 'P') &&
210 (string[2] == 'r' || string[2] == 'R') &&
211 (string[3] == 'i' || string[3] == 'I') &&
212 (string[4] == 'm' || string[4] == 'M') &&
213 (string[5] == 'a' || string[5] == 'A') &&
214 (string[6] == 'r' || string[6] == 'R') &&
215 (string[7] == 'y' || string[7] == 'Y') &&
216 (string[8] == '>'));
217}
218
219static inline gboolean
220is_keycode (const char *string)
221{
222 return (string[0] == '0' &&
223 string[1] == 'x' &&
224 g_ascii_isxdigit (string[2]) &&
225 g_ascii_isxdigit (string[3]));
226}
227
228/**
229 * gtk_accelerator_parse_with_keycode:
230 * @accelerator: string representing an accelerator
231 * @display: (nullable): the `GdkDisplay` to look up @accelerator_codes in
232 * @accelerator_key: (out) (optional): return location for accelerator keyval
233 * @accelerator_codes: (out) (array zero-terminated=1) (transfer full) (optional):
234 * return location for accelerator keycodes
235 * @accelerator_mods: (out) (optional): return location for accelerator
236 * modifier mask
237 *
238 * Parses a string representing an accelerator.
239 *
240 * This is similar to [func@Gtk.accelerator_parse] but handles keycodes as
241 * well. This is only useful for system-level components, applications should
242 * use [func@Gtk.accelerator_parse] instead.
243 *
244 * If @accelerator_codes is given and the result stored in it is non-%NULL,
245 * the result must be freed with g_free().
246 *
247 * If a keycode is present in the accelerator and no @accelerator_codes
248 * is given, the parse will fail.
249 *
250 * If the parse fails, @accelerator_key, @accelerator_mods and
251 * @accelerator_codes will be set to 0 (zero).
252 *
253 * Returns: %TRUE if parsing succeeded
254 */
255gboolean
256gtk_accelerator_parse_with_keycode (const char *accelerator,
257 GdkDisplay *display,
258 guint *accelerator_key,
259 guint **accelerator_codes,
260 GdkModifierType *accelerator_mods)
261{
262 guint keyval;
263 GdkModifierType mods;
264 int len;
265 gboolean error;
266
267 if (accelerator_key)
268 *accelerator_key = 0;
269 if (accelerator_mods)
270 *accelerator_mods = 0;
271 if (accelerator_codes)
272 *accelerator_codes = NULL;
273
274 g_return_val_if_fail (accelerator != NULL, FALSE);
275
276 if (!display)
277 display = gdk_display_get_default ();
278
279 error = FALSE;
280 keyval = 0;
281 mods = 0;
282 len = strlen (s: accelerator);
283 while (len)
284 {
285 if (*accelerator == '<')
286 {
287 if (len >= 9 && is_primary (string: accelerator))
288 {
289 accelerator += 9;
290 len -= 9;
291 mods |= GDK_CONTROL_MASK;
292 }
293 else if (len >= 9 && is_control (string: accelerator))
294 {
295 accelerator += 9;
296 len -= 9;
297 mods |= GDK_CONTROL_MASK;
298 }
299 else if (len >= 7 && is_shift (string: accelerator))
300 {
301 accelerator += 7;
302 len -= 7;
303 mods |= GDK_SHIFT_MASK;
304 }
305 else if (len >= 6 && is_shft (string: accelerator))
306 {
307 accelerator += 6;
308 len -= 6;
309 mods |= GDK_SHIFT_MASK;
310 }
311 else if (len >= 6 && is_ctrl (string: accelerator))
312 {
313 accelerator += 6;
314 len -= 6;
315 mods |= GDK_CONTROL_MASK;
316 }
317 else if (len >= 5 && is_ctl (string: accelerator))
318 {
319 accelerator += 5;
320 len -= 5;
321 mods |= GDK_CONTROL_MASK;
322 }
323 else if (len >= 5 && is_alt (string: accelerator))
324 {
325 accelerator += 5;
326 len -= 5;
327 mods |= GDK_ALT_MASK;
328 }
329 else if (len >= 6 && is_meta (string: accelerator))
330 {
331 accelerator += 6;
332 len -= 6;
333 mods |= GDK_META_MASK;
334 }
335 else if (len >= 7 && is_hyper (string: accelerator))
336 {
337 accelerator += 7;
338 len -= 7;
339 mods |= GDK_HYPER_MASK;
340 }
341 else if (len >= 7 && is_super (string: accelerator))
342 {
343 accelerator += 7;
344 len -= 7;
345 mods |= GDK_SUPER_MASK;
346 }
347 else
348 {
349 char last_ch;
350
351 last_ch = *accelerator;
352 while (last_ch && last_ch != '>')
353 {
354 accelerator += 1;
355 len -= 1;
356 last_ch = *accelerator;
357 }
358 }
359 }
360 else
361 {
362 if (len >= 4 && is_keycode (string: accelerator))
363 {
364 char keystring[5];
365 char *endptr;
366 int tmp_keycode;
367
368 memcpy (dest: keystring, src: accelerator, n: 4);
369 keystring [4] = '\000';
370
371 tmp_keycode = strtol (nptr: keystring, endptr: &endptr, base: 16);
372
373 if (endptr == NULL || *endptr != '\000')
374 {
375 error = TRUE;
376 goto out;
377 }
378 else if (accelerator_codes != NULL)
379 {
380 /* 0x00 is an invalid keycode too. */
381 if (tmp_keycode == 0)
382 {
383 error = TRUE;
384 goto out;
385 }
386 else
387 {
388 *accelerator_codes = g_new0 (guint, 2);
389 (*accelerator_codes)[0] = tmp_keycode;
390 }
391 }
392 else
393 {
394 /* There was a keycode in the string, but
395 * we cannot store it, so we have an error */
396 error = TRUE;
397 goto out;
398 }
399 }
400 else
401 {
402 keyval = gdk_keyval_from_name (keyval_name: accelerator);
403 if (keyval == GDK_KEY_VoidSymbol)
404 {
405 error = TRUE;
406 goto out;
407 }
408 }
409
410 if (keyval && accelerator_codes != NULL)
411 {
412 GdkKeymapKey *keys;
413 int n_keys, i, j;
414
415 if (!gdk_display_map_keyval (display, keyval, keys: &keys, n_keys: &n_keys))
416 {
417 /* Not in keymap */
418 error = TRUE;
419 goto out;
420 }
421 else
422 {
423 *accelerator_codes = g_new0 (guint, n_keys + 1);
424
425 /* Prefer level-0 group-0 keys to modified keys */
426 for (i = 0, j = 0; i < n_keys; ++i)
427 {
428 if (keys[i].level == 0 && keys[i].group == 0)
429 (*accelerator_codes)[j++] = keys[i].keycode;
430 }
431
432 /* No level-0 group-0 keys? Find in the whole group-0 */
433 if (j == 0)
434 {
435 for (i = 0, j = 0; i < n_keys; ++i)
436 {
437 if (keys[i].group == 0)
438 (*accelerator_codes)[j++] = keys[i].keycode;
439 }
440 }
441
442 /* Still nothing? Try in other groups */
443 if (j == 0)
444 {
445 for (i = 0, j = 0; i < n_keys; ++i)
446 (*accelerator_codes)[j++] = keys[i].keycode;
447 }
448
449 if (j == 0)
450 {
451 g_free (mem: *accelerator_codes);
452 *accelerator_codes = NULL;
453 /* Not in keymap */
454 error = TRUE;
455 goto out;
456 }
457 g_free (mem: keys);
458 }
459 }
460
461 accelerator += len;
462 len -= len;
463 }
464 }
465
466out:
467 if (error)
468 keyval = mods = 0;
469
470 if (accelerator_key)
471 *accelerator_key = gdk_keyval_to_lower (keyval);
472 if (accelerator_mods)
473 *accelerator_mods = mods;
474
475 return !error;
476}
477
478/**
479 * gtk_accelerator_parse:
480 * @accelerator: string representing an accelerator
481 * @accelerator_key: (out) (optional): return location for accelerator keyval
482 * @accelerator_mods: (out) (optional): return location for accelerator
483 * modifier mask
484 *
485 * Parses a string representing an accelerator.
486 *
487 * The format looks like “`<Control>a`” or “`<Shift><Alt>F1`”.
488 *
489 * The parser is fairly liberal and allows lower or upper case, and also
490 * abbreviations such as “`<Ctl>`” and “`<Ctrl>`”.
491 *
492 * Key names are parsed using [func@Gdk.keyval_from_name]. For character keys
493 * the name is not the symbol, but the lowercase name, e.g. one would use
494 * “`<Ctrl>minus`” instead of “`<Ctrl>-`”.
495 *
496 * Modifiers are enclosed in angular brackets `<>`, and match the
497 * [flags@Gdk.ModifierType] mask:
498 *
499 * - `<Shift>` for `GDK_SHIFT_MASK`
500 * - `<Ctrl>` for `GDK_CONTROL_MASK`
501 * - `<Alt>` for `GDK_ALT_MASK`
502 * - `<Meta>` for `GDK_META_MASK`
503 * - `<Super>` for `GDK_SUPER_MASK`
504 * - `<Hyper>` for `GDK_HYPER_MASK`
505 *
506 * If the parse operation fails, @accelerator_key and @accelerator_mods will
507 * be set to 0 (zero).
508 */
509gboolean
510gtk_accelerator_parse (const char *accelerator,
511 guint *accelerator_key,
512 GdkModifierType *accelerator_mods)
513{
514 return gtk_accelerator_parse_with_keycode (accelerator, NULL, accelerator_key, NULL, accelerator_mods);
515}
516
517/**
518 * gtk_accelerator_name_with_keycode:
519 * @display: (nullable): a `GdkDisplay` or %NULL to use the default display
520 * @accelerator_key: accelerator keyval
521 * @keycode: accelerator keycode
522 * @accelerator_mods: accelerator modifier mask
523 *
524 * Converts an accelerator keyval and modifier mask
525 * into a string parseable by gtk_accelerator_parse_with_keycode().
526 *
527 * This is similar to [func@Gtk.accelerator_name] but handling keycodes.
528 * This is only useful for system-level components, applications
529 * should use [func@Gtk.accelerator_name] instead.
530 *
531 * Returns: a newly allocated accelerator name.
532 */
533char *
534gtk_accelerator_name_with_keycode (GdkDisplay *display,
535 guint accelerator_key,
536 guint keycode,
537 GdkModifierType accelerator_mods)
538{
539 char *gtk_name;
540
541 gtk_name = gtk_accelerator_name (accelerator_key, accelerator_mods);
542
543 if (!accelerator_key)
544 {
545 char *name;
546 name = g_strdup_printf (format: "%s0x%02x", gtk_name, keycode);
547 g_free (mem: gtk_name);
548 return name;
549 }
550
551 return gtk_name;
552}
553
554/**
555 * gtk_accelerator_name:
556 * @accelerator_key: accelerator keyval
557 * @accelerator_mods: accelerator modifier mask
558 *
559 * Converts an accelerator keyval and modifier mask into a string
560 * parseable by gtk_accelerator_parse().
561 *
562 * For example, if you pass in %GDK_KEY_q and %GDK_CONTROL_MASK,
563 * this function returns `<Control>q`.
564 *
565 * If you need to display accelerators in the user interface,
566 * see [func@Gtk.accelerator_get_label].
567 *
568 * Returns: (transfer full): a newly-allocated accelerator name
569 */
570char *
571gtk_accelerator_name (guint accelerator_key,
572 GdkModifierType accelerator_mods)
573{
574#define TXTLEN(s) sizeof (s) - 1
575 static const struct {
576 guint mask;
577 const char *text;
578 gsize text_len;
579 } mask_text[] = {
580 { GDK_SHIFT_MASK, "<Shift>", TXTLEN ("<Shift>") },
581 { GDK_CONTROL_MASK, "<Control>", TXTLEN ("<Control>") },
582 { GDK_ALT_MASK, "<Alt>", TXTLEN ("<Alt>") },
583 { GDK_META_MASK, "<Meta>", TXTLEN ("<Meta>") },
584 { GDK_SUPER_MASK, "<Super>", TXTLEN ("<Super>") },
585 { GDK_HYPER_MASK, "<Hyper>", TXTLEN ("<Hyper>") }
586 };
587#undef TXTLEN
588 GdkModifierType saved_mods;
589 guint l;
590 guint name_len;
591 const char *keyval_name;
592 char *accelerator;
593 int i;
594
595 accelerator_mods &= GDK_MODIFIER_MASK;
596
597 keyval_name = gdk_keyval_name (keyval: gdk_keyval_to_lower (keyval: accelerator_key));
598 if (!keyval_name)
599 keyval_name = "";
600
601 name_len = strlen (s: keyval_name);
602
603 saved_mods = accelerator_mods;
604 for (i = 0; i < G_N_ELEMENTS (mask_text); i++)
605 {
606 if (accelerator_mods & mask_text[i].mask)
607 name_len += mask_text[i].text_len;
608 }
609
610 if (name_len == 0)
611 return g_strdup (str: keyval_name);
612
613 name_len += 1; /* NUL byte */
614 accelerator = g_new (char, name_len);
615
616 accelerator_mods = saved_mods;
617 l = 0;
618 for (i = 0; i < G_N_ELEMENTS (mask_text); i++)
619 {
620 if (accelerator_mods & mask_text[i].mask)
621 {
622 strcpy (dest: accelerator + l, src: mask_text[i].text);
623 l += mask_text[i].text_len;
624 }
625 }
626
627 strcpy (dest: accelerator + l, src: keyval_name);
628 accelerator[name_len - 1] = '\0';
629
630 return accelerator;
631}
632
633/**
634 * gtk_accelerator_get_label_with_keycode:
635 * @display: (nullable): a `GdkDisplay` or %NULL to use the default display
636 * @accelerator_key: accelerator keyval
637 * @keycode: accelerator keycode
638 * @accelerator_mods: accelerator modifier mask
639 *
640 * Converts an accelerator keyval and modifier mask
641 * into a string that can be displayed to the user.
642 *
643 * The string may be translated.
644 *
645 * This function is similar to [func@Gtk.accelerator_get_label],
646 * but handling keycodes. This is only useful for system-level
647 * components, applications should use [func@Gtk.accelerator_get_label]
648 * instead.
649 *
650 * Returns: (transfer full): a newly-allocated string representing the accelerator
651 */
652char *
653gtk_accelerator_get_label_with_keycode (GdkDisplay *display,
654 guint accelerator_key,
655 guint keycode,
656 GdkModifierType accelerator_mods)
657{
658 char *gtk_label;
659
660 gtk_label = gtk_accelerator_get_label (accelerator_key, accelerator_mods);
661
662 if (!accelerator_key)
663 {
664 char *label;
665 label = g_strdup_printf (format: "%s0x%02x", gtk_label, keycode);
666 g_free (mem: gtk_label);
667 return label;
668 }
669
670 return gtk_label;
671}
672
673/* Underscores in key names are better displayed as spaces
674 * E.g., Page_Up should be “Page Up”.
675 *
676 * Some keynames also have prefixes that are not suitable
677 * for display, e.g XF86AudioMute, so strip those out, too.
678 *
679 * This function is only called on untranslated keynames,
680 * so no need to be UTF-8 safe.
681 */
682static void
683append_without_underscores (GString *s,
684 const char *str)
685{
686 const char *p;
687
688 if (g_str_has_prefix (str, prefix: "XF86"))
689 p = str + 4;
690 else if (g_str_has_prefix (str, prefix: "ISO_"))
691 p = str + 4;
692 else
693 p = str;
694
695 for ( ; *p; p++)
696 {
697 if (*p == '_')
698 g_string_append_c (s, ' ');
699 else
700 g_string_append_c (s, *p);
701 }
702}
703
704/* On Mac, if the key has symbolic representation (e.g. arrow keys),
705 * append it to gstring and return TRUE; otherwise return FALSE.
706 * See http://docs.info.apple.com/article.html?path=Mac/10.5/en/cdb_symbs.html
707 * for the list of special keys. */
708static gboolean
709append_keyval_symbol (guint accelerator_key,
710 GString *gstring)
711{
712#ifdef GDK_WINDOWING_MACOS
713 switch (accelerator_key)
714 {
715 case GDK_KEY_Return:
716 /* U+21A9 LEFTWARDS ARROW WITH HOOK */
717 g_string_append (gstring, "\xe2\x86\xa9");
718 return TRUE;
719
720 case GDK_KEY_ISO_Enter:
721 /* U+2324 UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS */
722 g_string_append (gstring, "\xe2\x8c\xa4");
723 return TRUE;
724
725 case GDK_KEY_Left:
726 /* U+2190 LEFTWARDS ARROW */
727 g_string_append (gstring, "\xe2\x86\x90");
728 return TRUE;
729
730 case GDK_KEY_Up:
731 /* U+2191 UPWARDS ARROW */
732 g_string_append (gstring, "\xe2\x86\x91");
733 return TRUE;
734
735 case GDK_KEY_Right:
736 /* U+2192 RIGHTWARDS ARROW */
737 g_string_append (gstring, "\xe2\x86\x92");
738 return TRUE;
739
740 case GDK_KEY_Down:
741 /* U+2193 DOWNWARDS ARROW */
742 g_string_append (gstring, "\xe2\x86\x93");
743 return TRUE;
744
745 case GDK_KEY_Page_Up:
746 /* U+21DE UPWARDS ARROW WITH DOUBLE STROKE */
747 g_string_append (gstring, "\xe2\x87\x9e");
748 return TRUE;
749
750 case GDK_KEY_Page_Down:
751 /* U+21DF DOWNWARDS ARROW WITH DOUBLE STROKE */
752 g_string_append (gstring, "\xe2\x87\x9f");
753 return TRUE;
754
755 case GDK_KEY_Home:
756 /* U+2196 NORTH WEST ARROW */
757 g_string_append (gstring, "\xe2\x86\x96");
758 return TRUE;
759
760 case GDK_KEY_End:
761 /* U+2198 SOUTH EAST ARROW */
762 g_string_append (gstring, "\xe2\x86\x98");
763 return TRUE;
764
765 case GDK_KEY_Escape:
766 /* U+238B BROKEN CIRCLE WITH NORTHWEST ARROW */
767 g_string_append (gstring, "\xe2\x8e\x8b");
768 return TRUE;
769
770 case GDK_KEY_BackSpace:
771 /* U+232B ERASE TO THE LEFT */
772 g_string_append (gstring, "\xe2\x8c\xab");
773 return TRUE;
774
775 case GDK_KEY_Delete:
776 /* U+2326 ERASE TO THE RIGHT */
777 g_string_append (gstring, "\xe2\x8c\xa6");
778 return TRUE;
779
780 default:
781 return FALSE;
782 }
783#else /* !GDK_WINDOWING_MACOS */
784 return FALSE;
785#endif
786}
787
788static void
789append_separator (GString *string)
790{
791#ifndef GDK_WINDOWING_MACOS
792 g_string_append (string, val: "+");
793#else
794 /* no separator on quartz */
795#endif
796}
797
798/**
799 * gtk_accelerator_get_label:
800 * @accelerator_key: accelerator keyval
801 * @accelerator_mods: accelerator modifier mask
802 *
803 * Converts an accelerator keyval and modifier mask into a string
804 * which can be used to represent the accelerator to the user.
805 *
806 * Returns: (transfer full): a newly-allocated string representing the accelerator
807 */
808char *
809gtk_accelerator_get_label (guint accelerator_key,
810 GdkModifierType accelerator_mods)
811{
812 GString *gstring;
813
814 gstring = g_string_new (NULL);
815
816 gtk_accelerator_print_label (gstring, accelerator_key, accelerator_mods);
817
818 return g_string_free (string: gstring, FALSE);
819}
820
821void
822gtk_accelerator_print_label (GString *gstring,
823 guint accelerator_key,
824 GdkModifierType accelerator_mods)
825{
826 gboolean seen_mod = FALSE;
827 gunichar ch;
828
829 if (accelerator_mods & GDK_SHIFT_MASK)
830 {
831#ifndef GDK_WINDOWING_MACOS
832 /* This is the text that should appear next to menu accelerators
833 * that use the shift key. If the text on this key isn't typically
834 * translated on keyboards used for your language, don't translate
835 * this.
836 */
837 g_string_append (string: gstring, C_("keyboard label", "Shift"));
838#else
839 /* U+21E7 UPWARDS WHITE ARROW */
840 g_string_append (gstring, "⇧");
841#endif
842 seen_mod = TRUE;
843 }
844
845 if (accelerator_mods & GDK_CONTROL_MASK)
846 {
847 if (seen_mod)
848 append_separator (string: gstring);
849
850#ifndef GDK_WINDOWING_MACOS
851 /* This is the text that should appear next to menu accelerators
852 * that use the control key. If the text on this key isn't typically
853 * translated on keyboards used for your language, don't translate
854 * this.
855 */
856 g_string_append (string: gstring, C_("keyboard label", "Ctrl"));
857#else
858 /* U+2303 UP ARROWHEAD */
859 g_string_append (gstring, "⌃");
860#endif
861 seen_mod = TRUE;
862 }
863
864 if (accelerator_mods & GDK_ALT_MASK)
865 {
866 if (seen_mod)
867 append_separator (string: gstring);
868
869#ifndef GDK_WINDOWING_MACOS
870 /* This is the text that should appear next to menu accelerators
871 * that use the alt key. If the text on this key isn't typically
872 * translated on keyboards used for your language, don't translate
873 * this.
874 */
875 g_string_append (string: gstring, C_("keyboard label", "Alt"));
876#else
877 /* U+2325 OPTION KEY */
878 g_string_append (gstring, "⌥");
879#endif
880 seen_mod = TRUE;
881 }
882
883 if (accelerator_mods & GDK_SUPER_MASK)
884 {
885 if (seen_mod)
886 append_separator (string: gstring);
887
888 /* This is the text that should appear next to menu accelerators
889 * that use the super key. If the text on this key isn't typically
890 * translated on keyboards used for your language, don't translate
891 * this.
892 */
893 g_string_append (string: gstring, C_("keyboard label", "Super"));
894 seen_mod = TRUE;
895 }
896
897 if (accelerator_mods & GDK_HYPER_MASK)
898 {
899 if (seen_mod)
900 append_separator (string: gstring);
901
902 /* This is the text that should appear next to menu accelerators
903 * that use the hyper key. If the text on this key isn't typically
904 * translated on keyboards used for your language, don't translate
905 * this.
906 */
907 g_string_append (string: gstring, C_("keyboard label", "Hyper"));
908 seen_mod = TRUE;
909 }
910
911 if (accelerator_mods & GDK_META_MASK)
912 {
913 if (seen_mod)
914 append_separator (string: gstring);
915
916#ifndef GDK_WINDOWING_MACOS
917 /* This is the text that should appear next to menu accelerators
918 * that use the meta key. If the text on this key isn't typically
919 * translated on keyboards used for your language, don't translate
920 * this.
921 */
922 g_string_append (string: gstring, C_("keyboard label", "Meta"));
923#else
924 g_string_append (gstring, "⌘");
925#endif
926 seen_mod = TRUE;
927 }
928
929 ch = gdk_keyval_to_unicode (keyval: accelerator_key);
930 if (ch && (ch == ' ' || g_unichar_isgraph (c: ch)))
931 {
932 if (seen_mod)
933 append_separator (string: gstring);
934
935 if (accelerator_key >= GDK_KEY_KP_Space &&
936 accelerator_key <= GDK_KEY_KP_Equal)
937 {
938 /* Translators: "KP" means "numeric key pad". This string will
939 * be used in accelerators such as "Ctrl+Shift+KP 1" in menus,
940 * and therefore the translation needs to be very short.
941 */
942 g_string_append (string: gstring, C_("keyboard label", "KP"));
943 g_string_append (string: gstring, val: " ");
944 }
945
946 switch (ch)
947 {
948 case ' ':
949 g_string_append (string: gstring, C_("keyboard label", "Space"));
950 break;
951 case '\\':
952 g_string_append (string: gstring, C_("keyboard label", "Backslash"));
953 break;
954 default:
955 g_string_append_unichar (string: gstring, wc: g_unichar_toupper (c: ch));
956 break;
957 }
958 }
959 else if (!append_keyval_symbol (accelerator_key, gstring))
960 {
961 const char *tmp;
962
963 tmp = gdk_keyval_name (keyval: gdk_keyval_to_lower (keyval: accelerator_key));
964 if (tmp != NULL)
965 {
966 if (seen_mod)
967 append_separator (string: gstring);
968
969 if (tmp[0] != 0 && tmp[1] == 0)
970 g_string_append_c (gstring, g_ascii_toupper (tmp[0]));
971 else
972 {
973 const char *str;
974 str = g_dpgettext2 (GETTEXT_PACKAGE, context: "keyboard label", msgid: tmp);
975 if (str == tmp)
976 append_without_underscores (s: gstring, str: tmp);
977 else
978 g_string_append (string: gstring, val: str);
979 }
980 }
981 }
982}
983
984/**
985 * gtk_accelerator_get_default_mod_mask:
986 *
987 * Gets the modifier mask.
988 *
989 * The modifier mask determines which modifiers are considered significant
990 * for keyboard accelerators. This includes all keyboard modifiers except
991 * for %GDK_LOCK_MASK.
992 *
993 * Returns: the modifier mask for accelerators
994 */
995GdkModifierType
996gtk_accelerator_get_default_mod_mask (void)
997{
998 return GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_ALT_MASK|
999 GDK_SUPER_MASK|GDK_HYPER_MASK|GDK_META_MASK;
1000}
1001

source code of gtk/gtk/gtkaccelgroup.c