1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2000 Red Hat, Inc. |
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 | #include "config.h" |
19 | |
20 | #include <gdk/gdk.h> |
21 | |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | |
25 | #include "gtkprivate.h" |
26 | #include "gtkaccelgroup.h" |
27 | #include "gtkimcontextsimple.h" |
28 | #include "gtksettings.h" |
29 | #include "gtkwidget.h" |
30 | #include "gtkdebug.h" |
31 | #include "gtkintl.h" |
32 | #include "gtkcomposetable.h" |
33 | #include "gtkimmoduleprivate.h" |
34 | |
35 | #include "gtkimcontextsimpleseqs.h" |
36 | |
37 | #include "gdk/gdkeventsprivate.h" |
38 | #include "gdk/gdkprofilerprivate.h" |
39 | |
40 | /** |
41 | * GtkIMContextSimple: |
42 | * |
43 | * `GtkIMContextSimple` is an input method supporting table-based input methods. |
44 | * |
45 | * ## Compose sequences |
46 | * |
47 | * `GtkIMContextSimple` reads compose sequences from the first of the |
48 | * following files that is found: ~/.config/gtk-4.0/Compose, ~/.XCompose, |
49 | * /usr/share/X11/locale/$locale/Compose (for locales that have a nontrivial |
50 | * Compose file). The syntax of these files is described in the Compose(5) |
51 | * manual page. |
52 | * |
53 | * If none of these files is found, `GtkIMContextSimple` uses a built-in table |
54 | * of compose sequences that is derived from the X11 Compose files. |
55 | * |
56 | * Note that compose sequences typically start with the Compose_key, which is |
57 | * often not available as a dedicated key on keyboards. Keyboard layouts may |
58 | * map this keysym to other keys, such as the right Control key. |
59 | * |
60 | * ## Unicode characters |
61 | * |
62 | * `GtkIMContextSimple` also supports numeric entry of Unicode characters |
63 | * by typing <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>u</kbd>, followed by a |
64 | * hexadecimal Unicode codepoint. |
65 | * |
66 | * For example, |
67 | * |
68 | * Ctrl-Shift-u 1 2 3 Enter |
69 | * |
70 | * yields U+0123 LATIN SMALL LETTER G WITH CEDILLA, i.e. ģ. |
71 | * |
72 | * ## Dead keys |
73 | * |
74 | * `GtkIMContextSimple` supports dead keys. For example, typing |
75 | * |
76 | * dead_acute a |
77 | * |
78 | * yields U+00E! LATIN SMALL LETTER_A WITH ACUTE, i.e. á. Note that this |
79 | * depends on the keyboard layout including dead keys. |
80 | */ |
81 | |
82 | struct _GtkIMContextSimplePrivate |
83 | { |
84 | guint *compose_buffer; |
85 | int compose_buffer_len; |
86 | GString *tentative_match; |
87 | int tentative_match_len; |
88 | |
89 | guint in_hex_sequence : 1; |
90 | guint in_compose_sequence : 1; |
91 | guint modifiers_dropped : 1; |
92 | }; |
93 | |
94 | #include "gtk/compose/gtkcomposedata.h" |
95 | |
96 | GtkComposeTable builtin_compose_table = { |
97 | NULL, |
98 | NULL, |
99 | MAX_SEQ_LEN, |
100 | N_INDEX_SIZE, |
101 | DATA_SIZE, |
102 | N_CHARS, |
103 | 0 |
104 | }; |
105 | |
106 | static void |
107 | init_builtin_table (void) |
108 | { |
109 | GBytes *bytes; |
110 | |
111 | bytes = g_resources_lookup_data (path: "/org/gtk/libgtk/compose/sequences" , lookup_flags: 0, NULL); |
112 | builtin_compose_table.data = (guint16 *) g_bytes_get_data (bytes, NULL); |
113 | g_bytes_unref (bytes); |
114 | |
115 | bytes = g_resources_lookup_data (path: "/org/gtk/libgtk/compose/chars" , lookup_flags: 0, NULL); |
116 | builtin_compose_table.char_data = (char *) g_bytes_get_data (bytes, NULL); |
117 | g_bytes_unref (bytes); |
118 | } |
119 | |
120 | G_LOCK_DEFINE_STATIC (global_tables); |
121 | static GSList *global_tables; |
122 | |
123 | static const guint gtk_compose_ignore[] = { |
124 | 0, /* Yes, XKB will send us key press events with NoSymbol :( */ |
125 | GDK_KEY_Overlay1_Enable, |
126 | GDK_KEY_Overlay2_Enable, |
127 | GDK_KEY_Shift_L, |
128 | GDK_KEY_Shift_R, |
129 | GDK_KEY_Control_L, |
130 | GDK_KEY_Control_R, |
131 | GDK_KEY_Caps_Lock, |
132 | GDK_KEY_Shift_Lock, |
133 | GDK_KEY_Meta_L, |
134 | GDK_KEY_Meta_R, |
135 | GDK_KEY_Alt_L, |
136 | GDK_KEY_Alt_R, |
137 | GDK_KEY_Super_L, |
138 | GDK_KEY_Super_R, |
139 | GDK_KEY_Hyper_L, |
140 | GDK_KEY_Hyper_R, |
141 | GDK_KEY_Mode_switch, |
142 | GDK_KEY_ISO_Level3_Shift, |
143 | GDK_KEY_ISO_Level3_Latch, |
144 | GDK_KEY_ISO_Level5_Shift, |
145 | GDK_KEY_ISO_Level5_Latch |
146 | }; |
147 | |
148 | static void gtk_im_context_simple_finalize (GObject *obj); |
149 | static gboolean gtk_im_context_simple_filter_keypress (GtkIMContext *context, |
150 | GdkEvent *key); |
151 | static void gtk_im_context_simple_reset (GtkIMContext *context); |
152 | static void gtk_im_context_simple_get_preedit_string (GtkIMContext *context, |
153 | char **str, |
154 | PangoAttrList **attrs, |
155 | int *cursor_pos); |
156 | |
157 | static void init_compose_table_async (GCancellable *cancellable, |
158 | GAsyncReadyCallback callback, |
159 | gpointer user_data); |
160 | |
161 | G_DEFINE_TYPE_WITH_CODE (GtkIMContextSimple, gtk_im_context_simple, GTK_TYPE_IM_CONTEXT, |
162 | G_ADD_PRIVATE (GtkIMContextSimple) |
163 | gtk_im_module_ensure_extension_point (); |
164 | g_io_extension_point_implement (GTK_IM_MODULE_EXTENSION_POINT_NAME, |
165 | g_define_type_id, |
166 | "gtk-im-context-simple" , |
167 | G_MININT)) |
168 | |
169 | static void |
170 | gtk_im_context_simple_class_init (GtkIMContextSimpleClass *class) |
171 | { |
172 | GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class); |
173 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
174 | |
175 | im_context_class->filter_keypress = gtk_im_context_simple_filter_keypress; |
176 | im_context_class->reset = gtk_im_context_simple_reset; |
177 | im_context_class->get_preedit_string = gtk_im_context_simple_get_preedit_string; |
178 | gobject_class->finalize = gtk_im_context_simple_finalize; |
179 | |
180 | init_builtin_table (); |
181 | init_compose_table_async (NULL, NULL, NULL); |
182 | } |
183 | |
184 | static char * |
185 | get_x11_compose_file_dir (void) |
186 | { |
187 | char * compose_file_dir; |
188 | |
189 | #if defined (X11_DATA_PREFIX) |
190 | compose_file_dir = g_strdup (X11_DATA_PREFIX "/share/X11/locale" ); |
191 | #else |
192 | compose_file_dir = g_build_filename (_gtk_get_datadir (), "X11" , "locale" , NULL); |
193 | #endif |
194 | |
195 | return compose_file_dir; |
196 | } |
197 | |
198 | static int |
199 | gtk_compose_table_find (gconstpointer data1, |
200 | gconstpointer data2) |
201 | { |
202 | const GtkComposeTable *compose_table = (const GtkComposeTable *) data1; |
203 | guint32 hash = (guint32) GPOINTER_TO_INT (data2); |
204 | return compose_table->id != hash; |
205 | } |
206 | |
207 | static gboolean |
208 | add_compose_table_from_file (const char *compose_file) |
209 | { |
210 | guint hash; |
211 | gboolean ret = FALSE; |
212 | |
213 | G_LOCK (global_tables); |
214 | |
215 | hash = g_str_hash (v: compose_file); |
216 | if (!g_slist_find_custom (list: global_tables, GINT_TO_POINTER (hash), func: gtk_compose_table_find)) |
217 | { |
218 | GtkComposeTable *table; |
219 | |
220 | table = gtk_compose_table_new_with_file (compose_file); |
221 | |
222 | if (table) |
223 | { |
224 | ret = TRUE; |
225 | global_tables = g_slist_prepend (list: global_tables, data: table); |
226 | } |
227 | } |
228 | |
229 | G_UNLOCK (global_tables); |
230 | |
231 | return ret; |
232 | } |
233 | |
234 | static void |
235 | add_builtin_compose_table (void) |
236 | { |
237 | G_LOCK (global_tables); |
238 | |
239 | global_tables = g_slist_prepend (list: global_tables, data: &builtin_compose_table); |
240 | |
241 | G_UNLOCK (global_tables); |
242 | } |
243 | |
244 | static void |
245 | add_compose_table_from_data (guint16 *data, |
246 | int max_seq_len, |
247 | int n_seqs) |
248 | { |
249 | guint hash; |
250 | |
251 | G_LOCK (global_tables); |
252 | |
253 | hash = gtk_compose_table_data_hash (data, max_seq_len, n_seqs); |
254 | if (!g_slist_find_custom (list: global_tables, GINT_TO_POINTER (hash), func: gtk_compose_table_find)) |
255 | { |
256 | GtkComposeTable *table; |
257 | |
258 | table = gtk_compose_table_new_with_data (data, max_seq_len, n_seqs); |
259 | |
260 | if (table) |
261 | global_tables = g_slist_prepend (list: global_tables, data: table); |
262 | } |
263 | |
264 | G_UNLOCK (global_tables); |
265 | } |
266 | |
267 | static void |
268 | gtk_im_context_simple_init_compose_table (void) |
269 | { |
270 | char *path = NULL; |
271 | const char *home; |
272 | const char *locale; |
273 | char **langs = NULL; |
274 | char **lang = NULL; |
275 | const char * const sys_langs[] = { "el_gr" , "fi_fi" , "pt_br" , NULL }; |
276 | const char * const *sys_lang = NULL; |
277 | |
278 | path = g_build_filename (first_element: g_get_user_config_dir (), "gtk-4.0" , "Compose" , NULL); |
279 | if (g_file_test (filename: path, test: G_FILE_TEST_EXISTS)) |
280 | { |
281 | if (add_compose_table_from_file (compose_file: path)) |
282 | { |
283 | g_free (mem: path); |
284 | return; |
285 | } |
286 | } |
287 | g_clear_pointer (&path, g_free); |
288 | |
289 | home = g_get_home_dir (); |
290 | if (home == NULL) |
291 | return; |
292 | |
293 | path = g_build_filename (first_element: home, ".XCompose" , NULL); |
294 | if (g_file_test (filename: path, test: G_FILE_TEST_EXISTS)) |
295 | { |
296 | if (add_compose_table_from_file (compose_file: path)) |
297 | { |
298 | g_free (mem: path); |
299 | return; |
300 | } |
301 | } |
302 | g_clear_pointer (&path, g_free); |
303 | |
304 | locale = g_getenv (variable: "LC_CTYPE" ); |
305 | if (locale == NULL) |
306 | locale = g_getenv (variable: "LANG" ); |
307 | if (locale == NULL) |
308 | locale = "C" ; |
309 | |
310 | /* FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=751826 */ |
311 | langs = g_get_locale_variants (locale); |
312 | |
313 | for (lang = langs; *lang; lang++) |
314 | { |
315 | if (g_str_has_prefix (str: *lang, prefix: "en_US" )) |
316 | break; |
317 | if (**lang == 'C') |
318 | break; |
319 | |
320 | /* Other languages just include en_us compose table. */ |
321 | for (sys_lang = sys_langs; *sys_lang; sys_lang++) |
322 | { |
323 | if (g_ascii_strncasecmp (s1: *lang, s2: *sys_lang, n: strlen (s: *sys_lang)) == 0) |
324 | { |
325 | char *x11_compose_file_dir = get_x11_compose_file_dir (); |
326 | path = g_build_filename (first_element: x11_compose_file_dir, *lang, "Compose" , NULL); |
327 | g_free (mem: x11_compose_file_dir); |
328 | break; |
329 | } |
330 | } |
331 | |
332 | if (path == NULL) |
333 | continue; |
334 | |
335 | if (g_file_test (filename: path, test: G_FILE_TEST_EXISTS)) |
336 | break; |
337 | g_clear_pointer (&path, g_free); |
338 | } |
339 | |
340 | g_strfreev (str_array: langs); |
341 | |
342 | if (path != NULL && |
343 | add_compose_table_from_file (compose_file: path)) |
344 | { |
345 | g_free (mem: path); |
346 | return; |
347 | } |
348 | g_clear_pointer (&path, g_free); |
349 | |
350 | add_builtin_compose_table (); |
351 | } |
352 | |
353 | static void |
354 | init_compose_table_thread_cb (GTask *task, |
355 | gpointer source_object, |
356 | gpointer task_data, |
357 | GCancellable *cancellable) |
358 | { |
359 | gint64 before G_GNUC_UNUSED; |
360 | |
361 | before = GDK_PROFILER_CURRENT_TIME; |
362 | |
363 | if (g_task_return_error_if_cancelled (task)) |
364 | return; |
365 | |
366 | gtk_im_context_simple_init_compose_table (); |
367 | |
368 | gdk_profiler_end_mark (before, "im compose table load (thread)" , NULL); |
369 | } |
370 | |
371 | static void |
372 | init_compose_table_async (GCancellable *cancellable, |
373 | GAsyncReadyCallback callback, |
374 | gpointer user_data) |
375 | { |
376 | GTask *task = g_task_new (NULL, cancellable, callback, callback_data: user_data); |
377 | g_task_set_source_tag (task, init_compose_table_async); |
378 | g_task_run_in_thread (task, task_func: init_compose_table_thread_cb); |
379 | g_object_unref (object: task); |
380 | } |
381 | |
382 | static void |
383 | gtk_im_context_simple_init (GtkIMContextSimple *context_simple) |
384 | { |
385 | GtkIMContextSimplePrivate *priv; |
386 | |
387 | priv = context_simple->priv = gtk_im_context_simple_get_instance_private (self: context_simple); |
388 | |
389 | priv->compose_buffer_len = builtin_compose_table.max_seq_len + 1; |
390 | priv->compose_buffer = g_new0 (guint, priv->compose_buffer_len); |
391 | priv->tentative_match = g_string_new (init: "" ); |
392 | priv->tentative_match_len = 0; |
393 | } |
394 | |
395 | static void |
396 | gtk_im_context_simple_finalize (GObject *obj) |
397 | { |
398 | GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (obj); |
399 | GtkIMContextSimplePrivate *priv = context_simple->priv;; |
400 | |
401 | g_free (mem: priv->compose_buffer); |
402 | g_string_free (string: priv->tentative_match, TRUE); |
403 | |
404 | G_OBJECT_CLASS (gtk_im_context_simple_parent_class)->finalize (obj); |
405 | } |
406 | |
407 | /** |
408 | * gtk_im_context_simple_new: |
409 | * |
410 | * Creates a new `GtkIMContextSimple`. |
411 | * |
412 | * Returns: a new `GtkIMContextSimple` |
413 | */ |
414 | GtkIMContext * |
415 | gtk_im_context_simple_new (void) |
416 | { |
417 | return g_object_new (GTK_TYPE_IM_CONTEXT_SIMPLE, NULL); |
418 | } |
419 | |
420 | static void |
421 | gtk_im_context_simple_commit_string (GtkIMContextSimple *context_simple, |
422 | const char *str) |
423 | { |
424 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
425 | |
426 | if (priv->in_hex_sequence || |
427 | priv->tentative_match_len > 0 || |
428 | priv->compose_buffer[0] != 0) |
429 | { |
430 | g_string_set_size (string: priv->tentative_match, len: 0); |
431 | priv->tentative_match_len = 0; |
432 | priv->in_hex_sequence = FALSE; |
433 | priv->in_compose_sequence = FALSE; |
434 | priv->compose_buffer[0] = 0; |
435 | |
436 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
437 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
438 | } |
439 | |
440 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "commit" , str); |
441 | } |
442 | |
443 | static void |
444 | gtk_im_context_simple_commit_char (GtkIMContextSimple *context_simple, |
445 | gunichar ch) |
446 | { |
447 | char buf[8] = { 0, }; |
448 | |
449 | g_unichar_to_utf8 (c: ch, outbuf: buf); |
450 | |
451 | gtk_im_context_simple_commit_string (context_simple, str: buf); |
452 | } |
453 | |
454 | /* In addition to the table-driven sequences, we allow Unicode hex |
455 | * codes to be entered. The method chosen here is similar to the |
456 | * one recommended in ISO 14755, but not exactly the same, since we |
457 | * don’t want to steal 16 valuable key combinations. |
458 | * |
459 | * A hex Unicode sequence must be started with Ctrl-Shift-U, followed |
460 | * by a sequence of hex digits entered with Ctrl-Shift still held. |
461 | * Releasing one of the modifiers or pressing space while the modifiers |
462 | * are still held commits the character. It is possible to erase |
463 | * digits using backspace. |
464 | * |
465 | * As an extension to the above, we also allow to start the sequence |
466 | * with Ctrl-Shift-U, then release the modifiers before typing any |
467 | * digits, and enter the digits without modifiers. |
468 | */ |
469 | |
470 | static gboolean |
471 | check_hex (GtkIMContextSimple *context_simple, |
472 | int n_compose) |
473 | { |
474 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
475 | /* See if this is a hex sequence, return TRUE if so */ |
476 | int i; |
477 | GString *str; |
478 | gulong n; |
479 | char *nptr = NULL; |
480 | char buf[7]; |
481 | |
482 | g_string_set_size (string: priv->tentative_match, len: 0); |
483 | priv->tentative_match_len = 0; |
484 | |
485 | str = g_string_new (NULL); |
486 | |
487 | i = 0; |
488 | while (i < n_compose) |
489 | { |
490 | gunichar ch; |
491 | |
492 | ch = gdk_keyval_to_unicode (keyval: priv->compose_buffer[i]); |
493 | |
494 | if (ch == 0) |
495 | return FALSE; |
496 | |
497 | if (!g_unichar_isxdigit (c: ch)) |
498 | return FALSE; |
499 | |
500 | buf[g_unichar_to_utf8 (c: ch, outbuf: buf)] = '\0'; |
501 | |
502 | g_string_append (string: str, val: buf); |
503 | |
504 | ++i; |
505 | } |
506 | |
507 | n = strtoul (nptr: str->str, endptr: &nptr, base: 16); |
508 | |
509 | /* If strtoul fails it probably means non-latin digits were used; |
510 | * we should in principle handle that, but we probably don't. |
511 | */ |
512 | if (nptr - str->str < str->len) |
513 | { |
514 | g_string_free (string: str, TRUE); |
515 | return FALSE; |
516 | } |
517 | else |
518 | g_string_free (string: str, TRUE); |
519 | |
520 | if (g_unichar_validate (ch: n)) |
521 | { |
522 | g_string_set_size (string: priv->tentative_match, len: 0); |
523 | g_string_append_unichar (string: priv->tentative_match, wc: n); |
524 | priv->tentative_match_len = n_compose; |
525 | } |
526 | |
527 | return TRUE; |
528 | } |
529 | |
530 | static void |
531 | beep_surface (GdkSurface *surface) |
532 | { |
533 | GdkDisplay *display = gdk_surface_get_display (surface); |
534 | gboolean beep; |
535 | |
536 | g_object_get (object: gtk_settings_get_for_display (display), |
537 | first_property_name: "gtk-error-bell" , &beep, |
538 | NULL); |
539 | |
540 | if (beep) |
541 | gdk_surface_beep (surface); |
542 | } |
543 | |
544 | static inline gboolean |
545 | is_dead_key (guint keysym) |
546 | { |
547 | return GDK_KEY_dead_grave <= keysym && keysym <= GDK_KEY_dead_greek; |
548 | } |
549 | |
550 | static void |
551 | append_dead_key (GString *string, |
552 | guint keysym) |
553 | { |
554 | /* Sadly, not all the dead keysyms have spacing mark equivalents |
555 | * in Unicode. For those that don't, we use NBSP + the non-spacing |
556 | * mark as an approximation. |
557 | */ |
558 | switch (keysym) |
559 | { |
560 | #define CASE(keysym, unicode, sp) \ |
561 | case GDK_KEY_dead_##keysym: \ |
562 | if (sp) \ |
563 | g_string_append_unichar (string, 0xA0); \ |
564 | g_string_append_unichar (string, unicode); \ |
565 | break; |
566 | |
567 | CASE (grave, 0x60, 0); |
568 | CASE (acute, 0xb4, 0); |
569 | CASE (circumflex, 0x5e, 0); |
570 | CASE (tilde, 0x7e, 0); |
571 | CASE (macron, 0xaf, 0); |
572 | CASE (breve, 0x2d8, 0); |
573 | CASE (abovedot, 0x307, 1); |
574 | CASE (diaeresis, 0xa8, 0); |
575 | CASE (abovering, 0x2da, 0); |
576 | CASE (hook, 0x2c0, 0); |
577 | CASE (doubleacute, 0x2dd, 0); |
578 | CASE (caron, 0x2c7, 0); |
579 | CASE (cedilla, 0xb8, 0); |
580 | CASE (ogonek, 0x2db, 0); |
581 | CASE (iota, 0x37a, 0); |
582 | CASE (voiced_sound, 0x3099, 1); |
583 | CASE (semivoiced_sound, 0x309a, 1); |
584 | CASE (belowdot, 0x323, 1); |
585 | CASE (horn, 0x31b, 1); |
586 | CASE (stroke, 0x335, 1); |
587 | CASE (abovecomma, 0x2bc, 0); |
588 | CASE (abovereversedcomma, 0x2bd, 1); |
589 | CASE (doublegrave, 0x30f, 1); |
590 | CASE (belowring, 0x2f3, 0); |
591 | CASE (belowmacron, 0x2cd, 0); |
592 | CASE (belowcircumflex, 0x32d, 1); |
593 | CASE (belowtilde, 0x330, 1); |
594 | CASE (belowbreve, 0x32e, 1); |
595 | CASE (belowdiaeresis, 0x324, 1); |
596 | CASE (invertedbreve, 0x32f, 1); |
597 | CASE (belowcomma, 0x326, 1); |
598 | CASE (lowline, 0x5f, 0); |
599 | CASE (aboveverticalline, 0x2c8, 0); |
600 | CASE (belowverticalline, 0x2cc, 0); |
601 | CASE (longsolidusoverlay, 0x338, 1); |
602 | CASE (a, 0x363, 1); |
603 | CASE (A, 0x363, 1); |
604 | CASE (e, 0x364, 1); |
605 | CASE (E, 0x364, 1); |
606 | CASE (i, 0x365, 1); |
607 | CASE (I, 0x365, 1); |
608 | CASE (o, 0x366, 1); |
609 | CASE (O, 0x366, 1); |
610 | CASE (u, 0x367, 1); |
611 | CASE (U, 0x367, 1); |
612 | CASE (small_schwa, 0x1dea, 1); |
613 | CASE (capital_schwa, 0x1dea, 1); |
614 | #undef CASE |
615 | default: |
616 | g_string_append_unichar (string, wc: gdk_keyval_to_unicode (keyval: keysym)); |
617 | } |
618 | } |
619 | |
620 | static gboolean |
621 | no_sequence_matches (GtkIMContextSimple *context_simple, |
622 | int n_compose, |
623 | GdkEvent *event) |
624 | { |
625 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
626 | GtkIMContext *context; |
627 | gunichar ch; |
628 | guint keyval; |
629 | |
630 | context = GTK_IM_CONTEXT (context_simple); |
631 | |
632 | priv->in_compose_sequence = FALSE; |
633 | |
634 | /* No compose sequences found, check first if we have a partial |
635 | * match pending. |
636 | */ |
637 | if (priv->tentative_match_len > 0) |
638 | { |
639 | int len = priv->tentative_match_len; |
640 | int i; |
641 | guint *compose_buffer; |
642 | char *str; |
643 | |
644 | compose_buffer = alloca (sizeof (guint) * priv->compose_buffer_len); |
645 | |
646 | memcpy (dest: compose_buffer, src: priv->compose_buffer, n: sizeof (guint) * priv->compose_buffer_len); |
647 | |
648 | str = g_strdup (str: priv->tentative_match->str); |
649 | gtk_im_context_simple_commit_string (context_simple, str); |
650 | g_free (mem: str); |
651 | |
652 | for (i = 0; i < n_compose - len - 1; i++) |
653 | { |
654 | GdkTranslatedKey translated; |
655 | translated.keyval = compose_buffer[len + i]; |
656 | translated.consumed = 0; |
657 | translated.layout = 0; |
658 | translated.level = 0; |
659 | GdkEvent *tmp_event = gdk_key_event_new (type: GDK_KEY_PRESS, |
660 | surface: gdk_event_get_surface (event), |
661 | device: gdk_event_get_device (event), |
662 | time: gdk_event_get_time (event), |
663 | keycode: compose_buffer[len + i], |
664 | modifiers: gdk_event_get_modifier_state (event), |
665 | FALSE, |
666 | translated: &translated, |
667 | no_lock: &translated); |
668 | |
669 | gtk_im_context_filter_keypress (context, event: tmp_event); |
670 | gdk_event_unref (event: tmp_event); |
671 | } |
672 | |
673 | return gtk_im_context_filter_keypress (context, event); |
674 | } |
675 | else |
676 | { |
677 | int i; |
678 | |
679 | for (i = 0; i < n_compose && is_dead_key (keysym: priv->compose_buffer[i]); i++) |
680 | ; |
681 | |
682 | if (n_compose > 1 && i >= n_compose - 1) |
683 | { |
684 | GString *s; |
685 | |
686 | s = g_string_new (init: "" ); |
687 | |
688 | if (i == n_compose - 1) |
689 | { |
690 | /* dead keys are never *really* dead */ |
691 | for (int j = 0; j < i; j++) |
692 | append_dead_key (string: s, keysym: priv->compose_buffer[j]); |
693 | |
694 | ch = gdk_keyval_to_unicode (keyval: priv->compose_buffer[i]); |
695 | if (ch != 0 && ch != ' ' && !g_unichar_iscntrl (c: ch)) |
696 | g_string_append_unichar (string: s, wc: ch); |
697 | |
698 | gtk_im_context_simple_commit_string (context_simple, str: s->str); |
699 | } |
700 | else |
701 | { |
702 | append_dead_key (string: s, keysym: priv->compose_buffer[0]); |
703 | gtk_im_context_simple_commit_string (context_simple, str: s->str); |
704 | |
705 | for (i = 1; i < n_compose; i++) |
706 | priv->compose_buffer[i - 1] = priv->compose_buffer[i]; |
707 | priv->compose_buffer[n_compose - 1] = 0; |
708 | |
709 | priv->in_compose_sequence = TRUE; |
710 | |
711 | g_signal_emit_by_name (instance: context, detailed_signal: "preedit-start" ); |
712 | g_signal_emit_by_name (instance: context, detailed_signal: "preedit-changed" ); |
713 | } |
714 | |
715 | g_string_free (string: s, TRUE); |
716 | |
717 | return TRUE; |
718 | } |
719 | |
720 | priv->compose_buffer[0] = 0; |
721 | if (n_compose > 1) /* Invalid sequence */ |
722 | { |
723 | beep_surface (surface: gdk_event_get_surface (event)); |
724 | g_signal_emit_by_name (instance: context, detailed_signal: "preedit-changed" ); |
725 | g_signal_emit_by_name (instance: context, detailed_signal: "preedit-end" ); |
726 | return TRUE; |
727 | } |
728 | |
729 | keyval = gdk_key_event_get_keyval (event); |
730 | ch = gdk_keyval_to_unicode (keyval); |
731 | if (ch != 0 && !g_unichar_iscntrl (c: ch)) |
732 | { |
733 | gtk_im_context_simple_commit_char (context_simple, ch); |
734 | return TRUE; |
735 | } |
736 | else |
737 | return FALSE; |
738 | } |
739 | return FALSE; |
740 | } |
741 | |
742 | static gboolean |
743 | is_hex_keyval (guint keyval) |
744 | { |
745 | gunichar ch = gdk_keyval_to_unicode (keyval); |
746 | |
747 | return g_unichar_isxdigit (c: ch); |
748 | } |
749 | |
750 | static guint |
751 | canonical_hex_keyval (GdkEvent *event) |
752 | { |
753 | guint keyval, event_keyval; |
754 | guint *keyvals = NULL; |
755 | int n_vals = 0; |
756 | int i; |
757 | |
758 | event_keyval = gdk_key_event_get_keyval (event); |
759 | |
760 | /* See if the keyval is already a hex digit */ |
761 | if (is_hex_keyval (keyval: event_keyval)) |
762 | return event_keyval; |
763 | |
764 | /* See if this key would have generated a hex keyval in |
765 | * any other state, and return that hex keyval if so |
766 | */ |
767 | gdk_display_map_keycode (display: gdk_event_get_display (event), |
768 | keycode: gdk_key_event_get_keycode (event), |
769 | NULL, |
770 | keyvals: &keyvals, n_entries: &n_vals); |
771 | |
772 | keyval = 0; |
773 | i = 0; |
774 | while (i < n_vals) |
775 | { |
776 | if (is_hex_keyval (keyval: keyvals[i])) |
777 | { |
778 | keyval = keyvals[i]; |
779 | break; |
780 | } |
781 | |
782 | ++i; |
783 | } |
784 | |
785 | g_free (mem: keyvals); |
786 | |
787 | if (keyval) |
788 | return keyval; |
789 | else |
790 | /* No way to make it a hex digit |
791 | */ |
792 | return 0; |
793 | } |
794 | |
795 | static gboolean |
796 | gtk_im_context_simple_filter_keypress (GtkIMContext *context, |
797 | GdkEvent *event) |
798 | { |
799 | GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context); |
800 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
801 | GdkSurface *surface = gdk_event_get_surface (event: (GdkEvent *) event); |
802 | GSList *tmp_list; |
803 | int n_compose = 0; |
804 | GdkModifierType hex_mod_mask; |
805 | gboolean have_hex_mods; |
806 | gboolean is_hex_start; |
807 | gboolean is_hex_end; |
808 | gboolean is_backspace; |
809 | gboolean is_escape; |
810 | guint hex_keyval; |
811 | int i; |
812 | gboolean compose_finish; |
813 | gboolean compose_match; |
814 | guint keyval, state; |
815 | |
816 | while (priv->compose_buffer[n_compose] != 0 && n_compose < priv->compose_buffer_len) |
817 | n_compose++; |
818 | |
819 | keyval = gdk_key_event_get_keyval (event); |
820 | state = gdk_event_get_modifier_state (event); |
821 | |
822 | if (gdk_event_get_event_type (event) == GDK_KEY_RELEASE) |
823 | { |
824 | if (priv->in_hex_sequence && |
825 | (keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R || |
826 | keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R)) |
827 | { |
828 | if (priv->tentative_match->len > 0) |
829 | { |
830 | char *str = g_strdup (str: priv->tentative_match->str); |
831 | gtk_im_context_simple_commit_string (context_simple, str); |
832 | g_free (mem: str); |
833 | |
834 | return TRUE; |
835 | } |
836 | else if (n_compose == 0) |
837 | { |
838 | priv->modifiers_dropped = TRUE; |
839 | |
840 | return TRUE; |
841 | } |
842 | else if (priv->in_hex_sequence) |
843 | { |
844 | /* invalid hex sequence */ |
845 | beep_surface (surface); |
846 | |
847 | g_string_set_size (string: priv->tentative_match, len: 0); |
848 | priv->in_hex_sequence = FALSE; |
849 | priv->compose_buffer[0] = 0; |
850 | |
851 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
852 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
853 | |
854 | return TRUE; |
855 | } |
856 | } |
857 | |
858 | if (priv->in_hex_sequence || priv->in_compose_sequence) |
859 | return TRUE; /* Don't leak random key events during preedit */ |
860 | |
861 | return FALSE; |
862 | } |
863 | |
864 | /* Ignore modifier key presses */ |
865 | for (i = 0; i < G_N_ELEMENTS (gtk_compose_ignore); i++) |
866 | if (keyval == gtk_compose_ignore[i]) |
867 | { |
868 | if (priv->in_hex_sequence || priv->in_compose_sequence) |
869 | return TRUE; /* Don't leak random key events during preedit */ |
870 | |
871 | return FALSE; |
872 | } |
873 | |
874 | hex_mod_mask = GDK_CONTROL_MASK|GDK_SHIFT_MASK; |
875 | |
876 | if (priv->in_hex_sequence && priv->modifiers_dropped) |
877 | have_hex_mods = TRUE; |
878 | else |
879 | have_hex_mods = (state & (hex_mod_mask)) == hex_mod_mask; |
880 | is_hex_start = keyval == GDK_KEY_U; |
881 | is_hex_end = (keyval == GDK_KEY_space || |
882 | keyval == GDK_KEY_KP_Space || |
883 | keyval == GDK_KEY_Return || |
884 | keyval == GDK_KEY_ISO_Enter || |
885 | keyval == GDK_KEY_KP_Enter); |
886 | is_backspace = keyval == GDK_KEY_BackSpace; |
887 | is_escape = keyval == GDK_KEY_Escape; |
888 | hex_keyval = canonical_hex_keyval (event); |
889 | |
890 | /* If we are already in a non-hex sequence, or |
891 | * this keystroke is not hex modifiers + hex digit, don't filter |
892 | * key events with accelerator modifiers held down. We only treat |
893 | * Control and Alt as accel modifiers here, since Super, Hyper and |
894 | * Meta are often co-located with Mode_Switch, Multi_Key or |
895 | * ISO_Level3_Switch. |
896 | */ |
897 | if (!have_hex_mods || |
898 | (n_compose > 0 && !priv->in_hex_sequence) || |
899 | (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start) || |
900 | (priv->in_hex_sequence && !hex_keyval && |
901 | !is_hex_start && !is_hex_end && !is_escape && !is_backspace)) |
902 | { |
903 | GdkModifierType no_text_input_mask; |
904 | GdkModifierType consumed_modifiers = 0; |
905 | |
906 | no_text_input_mask = GDK_ALT_MASK|GDK_CONTROL_MASK; |
907 | |
908 | #ifdef G_OS_WIN32 |
909 | /* On Win32, even Ctrl + Alt could be text input because AltGr = Ctrl |
910 | * + Alt. For example, Ctrl + Alt + e = € on a German keyboard. The |
911 | * GdkEvent's state, however, reports *all* modifiers that were |
912 | * active at the time the key was pressed, including the ones that |
913 | * were consumed to generate the keyval. So we cannot just assume |
914 | * that any key event containing Ctrl or Alt is a keybinding. We have |
915 | * to first check if those modifiers were actually used to generate |
916 | * the keyval. If so, then the keypress is regular input and we |
917 | * should not exit here. |
918 | */ |
919 | consumed_modifiers = gdk_key_event_get_consumed_modifiers (event); |
920 | #endif |
921 | |
922 | if (priv->in_hex_sequence && priv->modifiers_dropped && |
923 | (keyval == GDK_KEY_Return || |
924 | keyval == GDK_KEY_ISO_Enter || |
925 | keyval == GDK_KEY_KP_Enter)) |
926 | { |
927 | return FALSE; |
928 | } |
929 | |
930 | if (state & no_text_input_mask & ~consumed_modifiers) |
931 | { |
932 | if (priv->in_hex_sequence || priv->in_compose_sequence) |
933 | return TRUE; /* Don't leak random key events during preedit */ |
934 | |
935 | return FALSE; |
936 | } |
937 | } |
938 | |
939 | /* Handle backspace */ |
940 | if (priv->in_hex_sequence && have_hex_mods && is_backspace) |
941 | { |
942 | if (n_compose > 0) |
943 | { |
944 | n_compose--; |
945 | priv->compose_buffer[n_compose] = 0; |
946 | check_hex (context_simple, n_compose); |
947 | } |
948 | else |
949 | { |
950 | priv->in_hex_sequence = FALSE; |
951 | } |
952 | |
953 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
954 | |
955 | if (!priv->in_hex_sequence) |
956 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
957 | |
958 | return TRUE; |
959 | } |
960 | |
961 | if (!priv->in_hex_sequence && n_compose > 0 && is_backspace) |
962 | { |
963 | n_compose--; |
964 | priv->compose_buffer[n_compose] = 0; |
965 | |
966 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
967 | |
968 | if (n_compose == 0) |
969 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
970 | |
971 | return TRUE; |
972 | } |
973 | |
974 | /* Check for hex sequence restart */ |
975 | if (priv->in_hex_sequence && have_hex_mods && is_hex_start) |
976 | { |
977 | if (priv->tentative_match->len > 0) |
978 | { |
979 | char *str = g_strdup (str: priv->tentative_match->str); |
980 | gtk_im_context_simple_commit_string (context_simple, str); |
981 | g_free (mem: str); |
982 | } |
983 | else |
984 | { |
985 | /* invalid hex sequence */ |
986 | if (n_compose > 0) |
987 | beep_surface (surface); |
988 | |
989 | g_string_set_size (string: priv->tentative_match, len: 0); |
990 | priv->in_hex_sequence = FALSE; |
991 | priv->compose_buffer[0] = 0; |
992 | } |
993 | } |
994 | |
995 | /* Check for hex sequence start */ |
996 | if (!priv->in_hex_sequence && have_hex_mods && is_hex_start) |
997 | { |
998 | priv->compose_buffer[0] = 0; |
999 | priv->in_hex_sequence = TRUE; |
1000 | priv->modifiers_dropped = FALSE; |
1001 | g_string_set_size (string: priv->tentative_match, len: 0); |
1002 | |
1003 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-start" ); |
1004 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1005 | |
1006 | return TRUE; |
1007 | } |
1008 | |
1009 | if (is_escape) |
1010 | { |
1011 | if (priv->in_hex_sequence || priv->in_compose_sequence) |
1012 | { |
1013 | gtk_im_context_simple_reset (context); |
1014 | return TRUE; |
1015 | } |
1016 | |
1017 | return FALSE; |
1018 | } |
1019 | |
1020 | if (priv->in_hex_sequence) |
1021 | { |
1022 | if (hex_keyval && n_compose < 6) |
1023 | priv->compose_buffer[n_compose++] = hex_keyval; |
1024 | else if (!is_hex_end) |
1025 | { |
1026 | /* non-hex character in hex sequence, or sequence too long */ |
1027 | beep_surface (surface); |
1028 | return TRUE; |
1029 | } |
1030 | } |
1031 | else |
1032 | { |
1033 | if (n_compose + 1 == priv->compose_buffer_len) |
1034 | { |
1035 | priv->compose_buffer_len += 1; |
1036 | priv->compose_buffer = g_renew (guint, priv->compose_buffer, priv->compose_buffer_len); |
1037 | } |
1038 | |
1039 | priv->compose_buffer[n_compose++] = keyval; |
1040 | } |
1041 | |
1042 | priv->compose_buffer[n_compose] = 0; |
1043 | |
1044 | if (priv->in_hex_sequence) |
1045 | { |
1046 | /* If the modifiers are still held down, consider the sequence again */ |
1047 | if (have_hex_mods) |
1048 | { |
1049 | /* space or return ends the sequence, and we eat the key */ |
1050 | if (n_compose > 0 && is_hex_end) |
1051 | { |
1052 | if (priv->tentative_match->len > 0) |
1053 | { |
1054 | char *str = g_strdup (str: priv->tentative_match->str); |
1055 | gtk_im_context_simple_commit_string (context_simple, str); |
1056 | g_free (mem: str); |
1057 | |
1058 | return TRUE; |
1059 | } |
1060 | else |
1061 | { |
1062 | /* invalid hex sequence */ |
1063 | beep_surface (surface); |
1064 | |
1065 | g_string_set_size (string: priv->tentative_match, len: 0); |
1066 | priv->in_hex_sequence = FALSE; |
1067 | priv->compose_buffer[0] = 0; |
1068 | } |
1069 | } |
1070 | else if (!check_hex (context_simple, n_compose)) |
1071 | beep_surface (surface); |
1072 | |
1073 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1074 | |
1075 | if (!priv->in_hex_sequence) |
1076 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
1077 | |
1078 | return TRUE; |
1079 | } |
1080 | } |
1081 | else /* Then, check for compose sequences */ |
1082 | { |
1083 | gboolean success = FALSE; |
1084 | int prefix = 0; |
1085 | GString *output; |
1086 | |
1087 | output = g_string_new (init: "" ); |
1088 | |
1089 | G_LOCK (global_tables); |
1090 | |
1091 | tmp_list = global_tables; |
1092 | while (tmp_list) |
1093 | { |
1094 | if (gtk_compose_table_check (table: (GtkComposeTable *)tmp_list->data, |
1095 | compose_buffer: priv->compose_buffer, n_compose, |
1096 | compose_finish: &compose_finish, compose_match: &compose_match, |
1097 | output)) |
1098 | { |
1099 | if (!priv->in_compose_sequence) |
1100 | { |
1101 | priv->in_compose_sequence = TRUE; |
1102 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-start" ); |
1103 | } |
1104 | |
1105 | if (compose_finish) |
1106 | { |
1107 | if (compose_match) |
1108 | gtk_im_context_simple_commit_string (context_simple, str: output->str); |
1109 | } |
1110 | else |
1111 | { |
1112 | if (compose_match) |
1113 | { |
1114 | g_string_assign (string: priv->tentative_match, rval: output->str); |
1115 | priv->tentative_match_len = n_compose; |
1116 | } |
1117 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1118 | } |
1119 | |
1120 | success = TRUE; |
1121 | break; |
1122 | } |
1123 | else |
1124 | { |
1125 | int table_prefix; |
1126 | |
1127 | gtk_compose_table_get_prefix (table: (GtkComposeTable *)tmp_list->data, |
1128 | compose_buffer: priv->compose_buffer, n_compose, |
1129 | prefix: &table_prefix); |
1130 | |
1131 | prefix = MAX (prefix, table_prefix); |
1132 | } |
1133 | |
1134 | tmp_list = tmp_list->next; |
1135 | } |
1136 | |
1137 | G_UNLOCK (global_tables); |
1138 | |
1139 | if (success) |
1140 | { |
1141 | g_string_free (string: output, TRUE); |
1142 | return TRUE; |
1143 | } |
1144 | |
1145 | if (gtk_check_algorithmically (compose_buffer: priv->compose_buffer, n_compose, output)) |
1146 | { |
1147 | if (!priv->in_compose_sequence) |
1148 | { |
1149 | priv->in_compose_sequence = TRUE; |
1150 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-start" ); |
1151 | } |
1152 | |
1153 | if (output->len > 0) |
1154 | gtk_im_context_simple_commit_string (context_simple, str: output->str); |
1155 | else |
1156 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1157 | |
1158 | g_string_free (string: output, TRUE); |
1159 | |
1160 | return TRUE; |
1161 | } |
1162 | |
1163 | g_string_free (string: output, TRUE); |
1164 | |
1165 | /* If we get here, no Compose sequence matched. |
1166 | * Only beep if we were in a sequence before. |
1167 | */ |
1168 | if (prefix > 0) |
1169 | { |
1170 | for (i = prefix; i < n_compose; i++) |
1171 | priv->compose_buffer[i] = 0; |
1172 | |
1173 | beep_surface (surface: gdk_event_get_surface (event)); |
1174 | |
1175 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1176 | |
1177 | return TRUE; |
1178 | } |
1179 | } |
1180 | |
1181 | /* The current compose_buffer doesn't match anything */ |
1182 | return no_sequence_matches (context_simple, n_compose, event: (GdkEvent *)event); |
1183 | } |
1184 | |
1185 | static void |
1186 | gtk_im_context_simple_reset (GtkIMContext *context) |
1187 | { |
1188 | GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context); |
1189 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
1190 | |
1191 | priv->compose_buffer[0] = 0; |
1192 | |
1193 | if (priv->tentative_match->len > 0 || |
1194 | priv->in_hex_sequence || |
1195 | priv->in_compose_sequence) |
1196 | { |
1197 | priv->in_hex_sequence = FALSE; |
1198 | priv->in_compose_sequence = FALSE; |
1199 | g_string_set_size (string: priv->tentative_match, len: 0); |
1200 | priv->tentative_match_len = 0; |
1201 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-changed" ); |
1202 | g_signal_emit_by_name (instance: context_simple, detailed_signal: "preedit-end" ); |
1203 | } |
1204 | } |
1205 | |
1206 | static void |
1207 | gtk_im_context_simple_get_preedit_string (GtkIMContext *context, |
1208 | char **str, |
1209 | PangoAttrList **attrs, |
1210 | int *cursor_pos) |
1211 | { |
1212 | GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context); |
1213 | GtkIMContextSimplePrivate *priv = context_simple->priv; |
1214 | GString *s; |
1215 | int i; |
1216 | |
1217 | s = g_string_new (init: "" ); |
1218 | |
1219 | if (priv->in_hex_sequence) |
1220 | { |
1221 | g_string_append_c (s, 'u'); |
1222 | |
1223 | for (i = 0; priv->compose_buffer[i]; i++) |
1224 | g_string_append_unichar (string: s, wc: gdk_keyval_to_unicode (keyval: priv->compose_buffer[i])); |
1225 | } |
1226 | else if (priv->in_compose_sequence) |
1227 | { |
1228 | if (priv->tentative_match_len > 0 && priv->compose_buffer[0] != 0) |
1229 | { |
1230 | g_string_append (string: s, val: priv->tentative_match->str); |
1231 | } |
1232 | else |
1233 | { |
1234 | for (i = 0; priv->compose_buffer[i]; i++) |
1235 | { |
1236 | if (priv->compose_buffer[i] == GDK_KEY_Multi_key) |
1237 | { |
1238 | /* We only show the Compose key visibly when it is the |
1239 | * only glyph in the preedit, or when the sequence contains |
1240 | * multiple Compose keys, or when it occurs in the |
1241 | * middle of the sequence. Sadly, the official character, |
1242 | * U+2384, COMPOSITION SYMBOL, is bit too distracting, so |
1243 | * we use U+00B7, MIDDLE DOT. |
1244 | */ |
1245 | if (priv->compose_buffer[1] == 0 || i > 0 || |
1246 | priv->compose_buffer[i + 1] == GDK_KEY_Multi_key) |
1247 | g_string_append (string: s, val: "·" ); |
1248 | } |
1249 | else |
1250 | { |
1251 | gunichar ch; |
1252 | |
1253 | if (is_dead_key (keysym: priv->compose_buffer[i])) |
1254 | { |
1255 | append_dead_key (string: s, keysym: priv->compose_buffer[i]); |
1256 | } |
1257 | else |
1258 | { |
1259 | ch = gdk_keyval_to_unicode (keyval: priv->compose_buffer[i]); |
1260 | if (ch) |
1261 | g_string_append_unichar (string: s, wc: ch); |
1262 | } |
1263 | } |
1264 | } |
1265 | } |
1266 | } |
1267 | |
1268 | if (cursor_pos) |
1269 | *cursor_pos = g_utf8_strlen (p: s->str, max: s->len); |
1270 | |
1271 | if (attrs) |
1272 | { |
1273 | *attrs = pango_attr_list_new (); |
1274 | |
1275 | if (s->len) |
1276 | { |
1277 | PangoAttribute *attr; |
1278 | |
1279 | attr = pango_attr_underline_new (underline: PANGO_UNDERLINE_SINGLE); |
1280 | attr->start_index = 0; |
1281 | attr->end_index = s->len; |
1282 | pango_attr_list_insert (list: *attrs, attr); |
1283 | |
1284 | attr = pango_attr_fallback_new (TRUE); |
1285 | attr->start_index = 0; |
1286 | attr->end_index = s->len; |
1287 | pango_attr_list_insert (list: *attrs, attr); |
1288 | } |
1289 | } |
1290 | |
1291 | if (str) |
1292 | *str = g_string_free (string: s, FALSE); |
1293 | } |
1294 | |
1295 | /** |
1296 | * gtk_im_context_simple_add_table: (skip) |
1297 | * @context_simple: A `GtkIMContextSimple` |
1298 | * @data: (array): the table |
1299 | * @max_seq_len: Maximum length of a sequence in the table |
1300 | * @n_seqs: number of sequences in the table |
1301 | * |
1302 | * Adds an additional table to search to the input context. |
1303 | * Each row of the table consists of @max_seq_len key symbols |
1304 | * followed by two #guint16 interpreted as the high and low |
1305 | * words of a #gunicode value. Tables are searched starting |
1306 | * from the last added. |
1307 | * |
1308 | * The table must be sorted in dictionary order on the |
1309 | * numeric value of the key symbol fields. (Values beyond |
1310 | * the length of the sequence should be zero.) |
1311 | * |
1312 | * Deprecated: 4.4: Use gtk_im_context_simple_add_compose_file() |
1313 | */ |
1314 | void |
1315 | gtk_im_context_simple_add_table (GtkIMContextSimple *context_simple, |
1316 | guint16 *data, |
1317 | int max_seq_len, |
1318 | int n_seqs) |
1319 | { |
1320 | g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple)); |
1321 | |
1322 | add_compose_table_from_data (data, max_seq_len, n_seqs); |
1323 | } |
1324 | |
1325 | /** |
1326 | * gtk_im_context_simple_add_compose_file: |
1327 | * @context_simple: A `GtkIMContextSimple` |
1328 | * @compose_file: The path of compose file |
1329 | * |
1330 | * Adds an additional table from the X11 compose file. |
1331 | */ |
1332 | void |
1333 | gtk_im_context_simple_add_compose_file (GtkIMContextSimple *context_simple, |
1334 | const char *compose_file) |
1335 | { |
1336 | g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple)); |
1337 | |
1338 | add_compose_table_from_file (compose_file); |
1339 | } |
1340 | |