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 | */ |
50 | gboolean |
51 | gtk_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 | |
102 | static inline gboolean |
103 | is_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 | |
112 | static inline gboolean |
113 | is_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 | |
122 | static inline gboolean |
123 | is_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 | |
133 | static inline gboolean |
134 | is_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 | |
144 | static inline gboolean |
145 | is_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 | |
156 | static inline gboolean |
157 | is_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 | |
170 | static inline gboolean |
171 | is_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 | |
181 | static inline gboolean |
182 | is_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 | |
193 | static inline gboolean |
194 | is_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 | |
205 | static inline gboolean |
206 | is_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 | |
219 | static inline gboolean |
220 | is_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 | */ |
255 | gboolean |
256 | gtk_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 | |
466 | out: |
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 | */ |
509 | gboolean |
510 | gtk_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 | */ |
533 | char * |
534 | gtk_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 | */ |
570 | char * |
571 | gtk_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 | */ |
652 | char * |
653 | gtk_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 | */ |
682 | static void |
683 | append_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. */ |
708 | static gboolean |
709 | append_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 | |
788 | static void |
789 | append_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 | */ |
808 | char * |
809 | gtk_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 | |
821 | void |
822 | gtk_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 | */ |
995 | GdkModifierType |
996 | gtk_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 | |