summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2021-02-02 17:29:14 +0000
committerMatthias Clasen <mclasen@redhat.com>2021-02-02 17:29:14 +0000
commite9b06b63467a594f917e94da657ceb984eb10e4c (patch)
tree81fe2c729427702455578d8c0e13fd4033ca97e5
parent86b437a1b6f160c24e57e60403b996bb3e29bd04 (diff)
parent949c7831871b396ef71db7b1360e3027ad79ccf7 (diff)
downloadgtk+-e9b06b63467a594f917e94da657ceb984eb10e4c.tar.gz
Merge branch 'im-context-work' into 'master'
Some im context work Closes #1004, #186, and #3521 See merge request GNOME/gtk!3143
-rw-r--r--docs/reference/gtk/gtk4-sections.txt1
-rw-r--r--gtk/gtkcomposetable.c748
-rw-r--r--gtk/gtkcomposetable.h35
-rw-r--r--gtk/gtkimcontextsimple.c578
-rw-r--r--gtk/gtkimcontextsimple.h7
-rw-r--r--gtk/gtkimcontextsimpleprivate.h42
-rw-r--r--testsuite/gtk/compose/basic1
-rw-r--r--testsuite/gtk/compose/basic.expected3
-rw-r--r--testsuite/gtk/compose/codepoint1
-rw-r--r--testsuite/gtk/compose/codepoint.expected3
-rw-r--r--testsuite/gtk/compose/hex1
-rw-r--r--testsuite/gtk/compose/hex.expected3
-rw-r--r--testsuite/gtk/compose/long1
-rw-r--r--testsuite/gtk/compose/long.expected3
-rw-r--r--testsuite/gtk/compose/match3
-rw-r--r--testsuite/gtk/compose/multi3
-rw-r--r--testsuite/gtk/compose/multi.expected5
-rw-r--r--testsuite/gtk/compose/octal1
-rw-r--r--testsuite/gtk/compose/octal.expected3
-rw-r--r--testsuite/gtk/compose/strings4
-rw-r--r--testsuite/gtk/compose/strings.expected6
-rw-r--r--testsuite/gtk/composetable.c320
-rw-r--r--testsuite/gtk/meson.build7
23 files changed, 1086 insertions, 693 deletions
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index d51b9d0e9f..302d1ad0be 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -1831,7 +1831,6 @@ GtkIMContextSimple
gtk_im_context_simple_new
gtk_im_context_simple_add_table
gtk_im_context_simple_add_compose_file
-GTK_MAX_COMPOSE_LEN
<SUBSECTION Standard>
GTK_IM_CONTEXT_SIMPLE
GTK_IS_IM_CONTEXT_SIMPLE
diff --git a/gtk/gtkcomposetable.c b/gtk/gtkcomposetable.c
index 901a7ecbf9..a07f5da06f 100644
--- a/gtk/gtkcomposetable.c
+++ b/gtk/gtkcomposetable.c
@@ -26,16 +26,17 @@
#include "gtkcomposetable.h"
#include "gtkimcontextsimple.h"
-#include "gtkimcontextsimpleprivate.h"
-
#define GTK_COMPOSE_TABLE_MAGIC "GtkComposeTable"
-#define GTK_COMPOSE_TABLE_VERSION (1)
+#define GTK_COMPOSE_TABLE_VERSION (2)
+
+/* Maximum length of sequences we parse */
+
+#define MAX_COMPOSE_LEN 20
typedef struct {
- gunichar *sequence;
- gunichar value[2];
- char *comment;
+ gunichar *sequence;
+ char *value;
} GtkComposeData;
@@ -43,7 +44,7 @@ static void
gtk_compose_data_free (GtkComposeData *compose_data)
{
g_free (compose_data->sequence);
- g_free (compose_data->comment);
+ g_free (compose_data->value);
g_slice_free (GtkComposeData, compose_data);
}
@@ -76,58 +77,82 @@ parse_compose_value (GtkComposeData *compose_data,
const char *val,
const char *line)
{
- char **words = g_strsplit (val, "\"", 3);
- gunichar uch;
-
- if (g_strv_length (words) < 3)
+ char *word;
+ const char *p;
+ gsize len;
+ GString *value;
+ gunichar ch;
+ char *endp;
+
+ len = strlen (val);
+ if (val[0] != '"' || val[len - 1] != '"')
{
g_warning ("Need to double-quote the value: %s: %s", val, line);
goto fail;
}
- uch = g_utf8_get_char (words[1]);
+ word = g_strndup (val + 1, len - 2);
- if (uch == 0)
- {
- g_warning ("Invalid value: %s: %s", val, line);
- goto fail;
- }
- else if (uch == '\\')
- {
- uch = words[1][1];
+ value = g_string_new ("");
- /* The escaped string "\"" is separated with '\\' and '"'. */
- if (uch == '\0' && words[2][0] == '"')
- uch = '"';
- /* The escaped octal */
- else if (uch >= '0' && uch <= '8')
- uch = g_ascii_strtoll(words[1] + 1, NULL, 8);
- /* If we need to handle other escape sequences. */
- else if (uch != '\\')
+ p = word;
+ while (*p)
+ {
+ if (*p == '\\')
{
- g_warning ("Invalid escape sequence: %s: %s", val, line);
+ if (p[1] == '"')
+ {
+ g_string_append_c (value, '"');
+ p += 2;
+ }
+ else if (p[1] == '\\')
+ {
+ g_string_append_c (value, '\\');
+ p += 2;
+ }
+ else if (p[1] >= '0' && p[1] < '8')
+ {
+ ch = g_ascii_strtoll (p + 1, &endp, 8);
+ if (ch == 0)
+ {
+ g_warning ("Invalid escape sequence: %s: %s", val, line);
+ goto fail;
+ }
+ g_string_append_unichar (value, ch);
+ p = endp;
+ }
+ else if (p[1] == 'x' || p[1] == 'X')
+ {
+ ch = g_ascii_strtoll (p + 2, &endp, 16);
+ if (ch == 0)
+ {
+ g_warning ("Invalid escape sequence: %s: %s", val, line);
+ goto fail;
+ }
+ g_string_append_unichar (value, ch);
+ p = endp;
+ }
+ else
+ {
+ g_warning ("Invalid escape sequence: %s: %s", val, line);
+ goto fail;
+ }
+ }
+ else
+ {
+ ch = g_utf8_get_char (p);
+ g_string_append_unichar (value, ch);
+ p = g_utf8_next_char (p);
}
}
- if (g_utf8_get_char (g_utf8_next_char (words[1])) > 0)
- {
- g_warning ("GTK supports to output one char only: %s: %s", val, line);
- goto fail;
- }
-
- compose_data->value[1] = uch;
+ compose_data->value = g_string_free (value, FALSE);
- if (uch == '"')
- compose_data->comment = g_strdup (g_strstrip (words[2] + 1));
- else
- compose_data->comment = g_strdup (g_strstrip (words[2]));
-
- g_strfreev (words);
+ g_free (word);
return TRUE;
fail:
- g_strfreev (words);
return FALSE;
}
@@ -189,10 +214,10 @@ parse_compose_sequence (GtkComposeData *compose_data,
}
g_strfreev (words);
- if (0 == n || n > GTK_MAX_COMPOSE_LEN)
+ if (0 == n || n > MAX_COMPOSE_LEN)
{
- g_warning ("The max length of compose sequences is %d: %s",
- GTK_MAX_COMPOSE_LEN, line);
+ g_warning ("Suspicious compose sequence length (%d). Are you sure this is right?: %s",
+ n, line);
return FALSE;
}
@@ -214,7 +239,10 @@ parse_compose_line (GList **compose_list,
return;
if (g_str_has_prefix (line, "include "))
- return;
+ {
+ g_warning ("include in Compose files not supported: %s", line);
+ return;
+ }
components = g_strsplit (line, ":", 2);
@@ -244,6 +272,8 @@ fail:
gtk_compose_data_free (compose_data);
}
+extern const GtkComposeTableCompact gtk_compose_table_compact;
+
static GList *
gtk_compose_list_parse_file (const char *compose_file)
{
@@ -279,18 +309,19 @@ gtk_compose_list_check_duplicated (GList *compose_list)
for (list = compose_list; list != NULL; list = list->next)
{
- static guint16 keysyms[GTK_MAX_COMPOSE_LEN + 1];
+ static guint16 keysyms[MAX_COMPOSE_LEN + 1];
int i;
int n_compose = 0;
gboolean compose_finish;
gunichar output_char;
+ char buf[8] = { 0, };
compose_data = list->data;
- for (i = 0; i < GTK_MAX_COMPOSE_LEN + 1; i++)
+ for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
keysyms[i] = 0;
- for (i = 0; i < GTK_MAX_COMPOSE_LEN + 1; i++)
+ for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
gunichar codepoint = compose_data->sequence[i];
keysyms[i] = (guint16) codepoint;
@@ -301,20 +332,21 @@ gtk_compose_list_check_duplicated (GList *compose_list)
n_compose++;
}
- if (gtk_check_compact_table (&gtk_compose_table_compact,
- keysyms,
- n_compose,
- &compose_finish,
- NULL,
- &output_char) &&
+ if (gtk_compose_table_compact_check (&gtk_compose_table_compact,
+ keysyms, n_compose,
+ &compose_finish,
+ NULL,
+ &output_char) &&
compose_finish)
{
- if (compose_data->value[1] == output_char)
+ g_unichar_to_utf8 (output_char, buf);
+ if (strcmp (compose_data->value, buf) == 0)
removed_list = g_list_prepend (removed_list, compose_data);
}
else if (gtk_check_algorithmically (keysyms, n_compose, &output_char))
{
- if (compose_data->value[1] == output_char)
+ g_unichar_to_utf8 (output_char, buf);
+ if (strcmp (compose_data->value, buf) == 0)
removed_list = g_list_prepend (removed_list, compose_data);
}
}
@@ -343,7 +375,7 @@ gtk_compose_list_check_uint16 (GList *compose_list)
int i;
compose_data = list->data;
- for (i = 0; i < GTK_MAX_COMPOSE_LEN; i++)
+ for (i = 0; i < MAX_COMPOSE_LEN; i++)
{
gunichar codepoint = compose_data->sequence[i];
@@ -384,7 +416,7 @@ gtk_compose_list_format_for_gtk (GList *compose_list,
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
- for (i = 0; i < GTK_MAX_COMPOSE_LEN + 1; i++)
+ for (i = 0; i < MAX_COMPOSE_LEN + 1; i++)
{
codepoint = compose_data->sequence[i];
if (codepoint == 0)
@@ -401,17 +433,6 @@ gtk_compose_list_format_for_gtk (GList *compose_list,
if (p_n_index_stride)
*p_n_index_stride = max_compose_len + 2;
- for (list = compose_list; list != NULL; list = list->next)
- {
- compose_data = list->data;
- codepoint = compose_data->value[1];
- if (codepoint > 0xffff)
- {
- compose_data->value[0] = codepoint / 0x10000;
- compose_data->value[1] = codepoint - codepoint / 0x10000 * 0x10000;
- }
- }
-
return compose_list;
}
@@ -436,61 +457,6 @@ gtk_compose_data_compare (gpointer a,
return 0;
}
-static void
-gtk_compose_list_print (GList *compose_list,
- int max_compose_len,
- int n_index_stride)
-{
- GList *list;
- int i, j;
- GtkComposeData *compose_data;
- int total_size = 0;
- gunichar upper;
- gunichar lower;
- const char *comment;
- const char *keyval;
-
- for (list = compose_list; list != NULL; list = list->next)
- {
- compose_data = list->data;
- g_printf (" ");
-
- for (i = 0; i < max_compose_len; i++)
- {
- if (compose_data->sequence[i] == 0)
- {
- for (j = i; j < max_compose_len; j++)
- {
- if (j == max_compose_len - 1)
- g_printf ("0,\n");
- else
- g_printf ("0, ");
- }
- break;
- }
-
- keyval = gdk_keyval_name (compose_data->sequence[i]);
- if (i == max_compose_len - 1)
- g_printf ("%s,\n", keyval ? keyval : "(null)");
- else
- g_printf ("%s, ", keyval ? keyval : "(null)");
- }
- upper = compose_data->value[0];
- lower = compose_data->value[1];
- comment = compose_data->comment;
-
- if (list == g_list_last (compose_list))
- g_printf (" %#06X, %#06X /* %s */\n", upper, lower, comment);
- else
- g_printf (" %#06X, %#06X, /* %s */\n", upper, lower, comment);
-
- total_size += n_index_stride;
- }
-
- g_printerr ("TOTAL_SIZE: %d\nMAX_COMPOSE_LEN: %d\nN_INDEX_STRIDE: %d\n",
- total_size, max_compose_len, n_index_stride);
-}
-
/* Implemented from g_str_hash() */
static guint32
gtk_compose_table_data_hash (gconstpointer v, int length)
@@ -546,6 +512,7 @@ gtk_compose_table_serialize (GtkComposeTable *compose_table,
guint16 max_seq_len = compose_table->max_seq_len;
guint16 index_stride = max_seq_len + 2;
guint16 n_seqs = compose_table->n_seqs;
+ guint16 n_chars = compose_table->n_chars;
guint32 i;
g_return_val_if_fail (compose_table != NULL, NULL);
@@ -553,44 +520,34 @@ gtk_compose_table_serialize (GtkComposeTable *compose_table,
g_return_val_if_fail (index_stride > 0, NULL);
length = strlen (header);
- total_length = length + sizeof (guint16) * (3 + index_stride * n_seqs);
+ total_length = length + sizeof (guint16) * (4 + index_stride * n_seqs) + n_chars;
if (count)
*count = total_length;
- p = contents = g_slice_alloc (total_length);
+ p = contents = g_malloc (total_length);
memcpy (p, header, length);
p += length;
- /* Copy by byte for endian */
-#define BYTE_COPY_FROM_BUF(element) \
- bytes = GUINT16_TO_BE ((element)); \
- memcpy (p, &bytes, length); \
- p += length; \
- if (p - contents > total_length) \
- { \
- g_warning ("data size %lld is bigger than %" G_GSIZE_FORMAT, \
- (long long) (p - contents), total_length); \
- g_free (contents); \
- if (count) \
- { \
- *count = 0; \
- } \
- return NULL; \
- }
-
- length = sizeof (guint16);
-
- BYTE_COPY_FROM_BUF (version);
- BYTE_COPY_FROM_BUF (max_seq_len);
- BYTE_COPY_FROM_BUF (n_seqs);
+#define APPEND_GUINT16(elt) \
+ bytes = GUINT16_TO_BE (elt); \
+ memcpy (p, &bytes, sizeof (guint16)); \
+ p += sizeof (guint16);
+
+ APPEND_GUINT16 (version);
+ APPEND_GUINT16 (max_seq_len);
+ APPEND_GUINT16 (n_seqs);
+ APPEND_GUINT16 (n_chars);
for (i = 0; i < (guint32) index_stride * n_seqs; i++)
{
- BYTE_COPY_FROM_BUF (compose_table->data[i]);
+ APPEND_GUINT16 (compose_table->data[i]);
}
-#undef BYTE_COPY_FROM_BUF
+ if (compose_table->n_chars > 0)
+ memcpy (p, compose_table->char_data, compose_table->n_chars);
+
+#undef APPEND_GUINT16
return contents;
}
@@ -614,16 +571,17 @@ gtk_compose_table_load_cache (const char *compose_file)
GStatBuf original_buf;
GStatBuf cache_buf;
gsize total_length;
- gsize length;
GError *error = NULL;
guint16 bytes;
guint16 version;
guint16 max_seq_len;
guint16 index_stride;
guint16 n_seqs;
+ guint16 n_chars;
guint32 i;
guint16 *gtk_compose_seqs = NULL;
GtkComposeTable *retval;
+ char *char_data = NULL;
hash = g_str_hash (compose_file);
if ((path = gtk_compose_hash_get_cache_path (hash)) == NULL)
@@ -642,16 +600,10 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
- /* Copy by byte for endian */
-#define BYTE_COPY_TO_BUF(element) \
- memcpy (&bytes, p, length); \
- element = GUINT16_FROM_BE (bytes); \
- p += length; \
- if (p - contents > total_length) \
- { \
- g_warning ("Broken cache content %s in %s", path, #element); \
- goto out_load_cache; \
- }
+#define GET_GUINT16(elt) \
+ memcpy (&bytes, p, sizeof (guint16)); \
+ elt = GUINT16_FROM_BE (bytes); \
+ p += sizeof (guint16);
p = contents;
if (g_ascii_strncasecmp (p, GTK_COMPOSE_TABLE_MAGIC,
@@ -660,6 +612,7 @@ gtk_compose_table_load_cache (const char *compose_file)
g_warning ("The file is not a GtkComposeTable cache file %s", path);
goto out_load_cache;
}
+
p += strlen (GTK_COMPOSE_TABLE_MAGIC);
if (p - contents > total_length)
{
@@ -667,9 +620,7 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
- length = sizeof (guint16);
-
- BYTE_COPY_TO_BUF (version);
+ GET_GUINT16 (version);
if (version != GTK_COMPOSE_TABLE_VERSION)
{
g_warning ("cache version is different %u != %u",
@@ -677,8 +628,9 @@ gtk_compose_table_load_cache (const char *compose_file)
goto out_load_cache;
}
- BYTE_COPY_TO_BUF (max_seq_len);
- BYTE_COPY_TO_BUF (n_seqs);
+ GET_GUINT16 (max_seq_len);
+ GET_GUINT16 (n_seqs);
+ GET_GUINT16 (n_chars);
if (max_seq_len == 0 || n_seqs == 0)
{
@@ -691,13 +643,22 @@ gtk_compose_table_load_cache (const char *compose_file)
for (i = 0; i < (guint32) index_stride * n_seqs; i++)
{
- BYTE_COPY_TO_BUF (gtk_compose_seqs[i]);
+ GET_GUINT16 (gtk_compose_seqs[i]);
+ }
+
+ if (n_chars > 0)
+ {
+ char_data = g_new (char, n_chars + 1);
+ memcpy (char_data, p, n_chars);
+ char_data[n_chars] = '\0';
}
retval = g_new0 (GtkComposeTable, 1);
retval->data = gtk_compose_seqs;
retval->max_seq_len = max_seq_len;
retval->n_seqs = n_seqs;
+ retval->char_data = char_data;
+ retval->n_chars = n_chars;
retval->id = hash;
g_free (contents);
@@ -705,10 +666,11 @@ gtk_compose_table_load_cache (const char *compose_file)
return retval;
-#undef BYTE_COPY_TO_BUF
+#undef GET_GUINT16
out_load_cache:
g_free (gtk_compose_seqs);
+ g_free (char_data);
g_free (contents);
g_free (path);
return NULL;
@@ -739,7 +701,7 @@ gtk_compose_table_save_cache (GtkComposeTable *compose_table)
}
out_save_cache:
- g_slice_free1 (length, contents);
+ g_free (contents);
g_free (path);
}
@@ -756,6 +718,8 @@ gtk_compose_table_new_with_list (GList *compose_list,
GList *list;
GtkComposeData *compose_data;
GtkComposeTable *retval = NULL;
+ gunichar codepoint;
+ GString *char_data;
g_return_val_if_fail (compose_list != NULL, NULL);
@@ -763,6 +727,8 @@ gtk_compose_table_new_with_list (GList *compose_list,
gtk_compose_seqs = g_new0 (guint16, length * n_index_stride);
+ char_data = g_string_new ("");
+
for (list = compose_list; list != NULL; list = list->next)
{
compose_data = list->data;
@@ -776,8 +742,24 @@ gtk_compose_table_new_with_list (GList *compose_list,
}
gtk_compose_seqs[n++] = (guint16) compose_data->sequence[i];
}
- gtk_compose_seqs[n++] = (guint16) compose_data->value[0];
- gtk_compose_seqs[n++] = (guint16) compose_data->value[1];
+
+ if (g_utf8_strlen (compose_data->value, -1) > 1)
+ {
+ if (char_data->len > 0)
+ g_string_append_c (char_data, 0);
+
+ codepoint = char_data->len | (1 << 31);
+
+ g_string_append (char_data, compose_data->value);
+ }
+ else
+ {
+ codepoint = g_utf8_get_char (compose_data->value);
+ g_assert ((codepoint & (1 << 31)) == 0);
+ }
+
+ gtk_compose_seqs[n++] = (codepoint & 0xffff0000) >> 16;
+ gtk_compose_seqs[n++] = codepoint & 0xffff;
}
retval = g_new0 (GtkComposeTable, 1);
@@ -785,6 +767,8 @@ gtk_compose_table_new_with_list (GList *compose_list,
retval->max_seq_len = max_compose_len;
retval->n_seqs = length;
retval->id = hash;
+ retval->n_chars = char_data->len;
+ retval->char_data = g_string_free (char_data, FALSE);
return retval;
}
@@ -816,9 +800,6 @@ gtk_compose_table_new_with_file (const char *compose_file)
return NULL;
}
- if (g_getenv ("GTK_COMPOSE_TABLE_PRINT") != NULL)
- gtk_compose_list_print (compose_list, max_compose_len, n_index_stride);
-
compose_table = gtk_compose_table_new_with_list (compose_list,
max_compose_len,
n_index_stride,
@@ -841,9 +822,10 @@ gtk_compose_table_list_add_array (GSList *compose_tables,
guint16 *gtk_compose_seqs = NULL;
g_return_val_if_fail (data != NULL, compose_tables);
- g_return_val_if_fail (max_seq_len <= GTK_MAX_COMPOSE_LEN, compose_tables);
+ g_return_val_if_fail (max_seq_len >= 0, compose_tables);
+ g_return_val_if_fail (n_seqs >= 0, compose_tables);
- n_index_stride = MIN (max_seq_len, GTK_MAX_COMPOSE_LEN) + 2;
+ n_index_stride = max_seq_len + 2;
if (!g_size_checked_mul (&length, n_index_stride, n_seqs))
{
g_critical ("Overflow in the compose sequences");
@@ -864,12 +846,14 @@ gtk_compose_table_list_add_array (GSList *compose_tables,
compose_table->max_seq_len = max_seq_len;
compose_table->n_seqs = n_seqs;
compose_table->id = hash;
+ compose_table->char_data = NULL;
+ compose_table->n_chars = 0;
return g_slist_prepend (compose_tables, compose_table);
}
GSList *
-gtk_compose_table_list_add_file (GSList *compose_tables,
+gtk_compose_table_list_add_file (GSList *compose_tables,
const char *compose_file)
{
guint32 hash;
@@ -891,3 +875,409 @@ gtk_compose_table_list_add_file (GSList *compose_tables,
gtk_compose_table_save_cache (compose_table);
return g_slist_prepend (compose_tables, compose_table);
}
+
+static int
+compare_seq (const void *key, const void *value)
+{
+ int i = 0;
+ const guint16 *keysyms = key;
+ const guint16 *seq = value;
+
+ while (keysyms[i])
+ {
+ if (keysyms[i] < seq[i])
+ return -1;
+ else if (keysyms[i] > seq[i])
+ return 1;
+
+ i++;
+ }
+
+ return 0;
+}
+
+/*
+ * gtk_compose_table_check:
+ * @table: the table to check
+ * @compose_buffer: the key vals to match
+ * @n_compose: number of non-zero key vals in @compose_buffer
+ * @compose_finish: (out): return location for whether there may be longer matches
+ * @compose_match: (out): return location for whether there is a match
+ * @output: (out) (caller-allocates): return location for the match values
+ *
+ * Looks for matches for a key sequence in @table.
+ *
+ * Returns: %TRUE if there were any matches, %FALSE otherwise
+ */
+gboolean
+gtk_compose_table_check (const GtkComposeTable *table,
+ const guint16 *compose_buffer,
+ int n_compose,
+ gboolean *compose_finish,
+ gboolean *compose_match,
+ GString *output)
+{
+ int row_stride = table->max_seq_len + 2;
+ guint16 *seq;
+
+ *compose_finish = FALSE;
+ *compose_match = FALSE;
+
+ g_string_set_size (output, 0);
+
+ /* Will never match, if the sequence in the compose buffer is longer
+ * than the sequences in the table. Further, compare_seq (key, val)
+ * will overrun val if key is longer than val.
+ */
+ if (n_compose > table->max_seq_len)
+ return FALSE;
+
+ seq = bsearch (compose_buffer,
+ table->data, table->n_seqs,
+ sizeof (guint16) * row_stride,
+ compare_seq);
+
+ if (seq)
+ {
+ guint16 *prev_seq;
+
+ /* Back up to the first sequence that matches to make sure
+ * we find the exact match if there is one.
+ */
+ while (seq > table->data)
+ {
+ prev_seq = seq - row_stride;
+ if (compare_seq (compose_buffer, prev_seq) != 0)
+ break;
+ seq = prev_seq;
+ }
+
+ if (n_compose == table->max_seq_len ||
+ seq[n_compose] == 0) /* complete sequence */
+ {
+ guint16 *next_seq;
+ gunichar value;
+
+ value = (seq[table->max_seq_len] << 16) | seq[table->max_seq_len + 1];
+ if ((value & (1 << 31)) != 0)
+ g_string_append (output, &table->char_data[value & ~(1 << 31)]);
+ else
+ g_string_append_unichar (output, value);
+
+ *compose_match = TRUE;
+
+ /* We found a tentative match. See if there are any longer
+ * sequences containing this subsequence
+ */
+ next_seq = seq + row_stride;
+ if (next_seq < table->data + row_stride * table->n_seqs)
+ {
+ if (compare_seq (compose_buffer, next_seq) == 0)
+ return TRUE;
+ }
+
+ *compose_finish = TRUE;
+ return TRUE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+compare_seq_index (const void *key, const void *value)
+{
+ const guint16 *keysyms = key;
+ const guint16 *seq = value;
+
+ if (keysyms[0] < seq[0])
+ return -1;
+ else if (keysyms[0] > seq[0])
+ return 1;
+
+ return 0;
+}
+
+gboolean
+gtk_compose_table_compact_check (const GtkComposeTableCompact *table,
+ const guint16 *compose_buffer,
+ int n_compose,
+ gboolean *compose_finish,
+ gboolean *compose_match,
+ gunichar *output_char)
+{
+ int row_stride;
+ guint16 *seq_index;
+ guint16 *seq;
+ int i;
+ gboolean match;
+ gunichar value;
+
+ if (compose_finish)
+ *compose_finish = FALSE;
+ if (compose_match)
+ *compose_match = FALSE;
+ if (output_char)
+ *output_char = 0;
+
+ /* Will never match, if the sequence in the compose buffer is longer
+ * than the sequences in the table. Further, compare_seq (key, val)
+ * will overrun val if key is longer than val.
+ */
+ if (n_compose > table->max_seq_len)
+ return FALSE;
+
+ seq_index = bsearch (compose_buffer,
+ table->data,
+ table->n_index_size,
+ sizeof (guint16) * table->n_index_stride,
+ compare_seq_index);
+
+ if (!seq_index)
+ return FALSE;
+
+ if (seq_index && n_compose == 1)
+ return TRUE;
+
+ seq = NULL;
+ match = FALSE;
+ value = 0;
+
+ for (i = n_compose - 1; i < table->max_seq_len; i++)
+ {
+ row_stride = i + 1;
+
+ if (seq_index[i + 1] - seq_index[i] > 0)
+ {
+ seq = bsearch (compose_buffer + 1,
+ table->data + seq_index[i],
+ (seq_index[i + 1] - seq_index[i]) / row_stride,
+ sizeof (guint16) * row_stride,
+ compare_seq);
+
+ if (seq)
+ {
+ if (i == n_compose - 1)
+ {
+ value = seq[row_stride - 1];
+ match = TRUE;
+ }
+ else
+ {
+ if (output_char)
+ *output_char = value;
+ if (match)
+ {
+ if (compose_match)
+ *compose_match = TRUE;
+ }
+
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ if (match)
+ {
+ if (compose_match)
+ *compose_match = TRUE;
+ if (compose_finish)
+ *compose_finish = TRUE;
+ if (output_char)
+ *output_char = value;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Checks if a keysym is a dead key.
+ * Dead key keysym values are defined in ../gdk/gdkkeysyms.h and the
+ * first is GDK_KEY_dead_grave. As X.Org is updated, more dead keys
+ * are added and we need to update the upper limit.
+ */
+#define IS_DEAD_KEY(k) \
+ ((k) >= GDK_KEY_dead_grave && (k) <= GDK_KEY_dead_greek)
+
+/* This function receives a sequence of Unicode characters and tries to
+ * normalize it (NFC). We check for the case where the resulting string
+ * has length 1 (single character).
+ * NFC normalisation normally rearranges diacritic marks, unless these
+ * belong to the same Canonical Combining Class.
+ * If they belong to the same canonical combining class, we produce all
+ * permutations of the diacritic marks, then attempt to normalize.
+ */
+static gboolean
+check_normalize_nfc (gunichar *combination_buffer,
+ int n_compose)
+{
+ gunichar *combination_buffer_temp;
+ char *combination_utf8_temp = NULL;
+ char *nfc_temp = NULL;
+ int n_combinations;
+ gunichar temp_swap;
+ int i;
+
+ combination_buffer_temp = g_alloca (n_compose * sizeof (gunichar));
+
+ n_combinations = 1;
+
+ for (i = 1; i < n_compose; i++)
+ n_combinations *= i;
+
+ /* Xorg reuses dead_tilde for the perispomeni diacritic mark.
+ * We check if base character belongs to Greek Unicode block,
+ * and if so, we replace tilde with perispomeni.
+ */
+ if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF)
+ {
+ for (i = 1; i < n_compose; i++ )
+ if (combination_buffer[i] == 0x303)
+ combination_buffer[i] = 0x342;
+ }
+
+ memcpy (combination_buffer_temp, combination_buffer, n_compose * sizeof (gunichar) );
+
+ for (i = 0; i < n_combinations; i++)
+ {
+ g_unicode_canonical_ordering (combination_buffer_temp, n_compose);
+ combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, n_compose, NULL, NULL, NULL);
+ nfc_temp = g_utf8_normalize (combination_utf8_temp, -1, G_NORMALIZE_NFC);
+
+ if (g_utf8_strlen (nfc_temp, -1) == 1)
+ {
+ memcpy (combination_buffer, combination_buffer_temp, n_compose * sizeof (gunichar) );
+
+ g_free (combination_utf8_temp);
+ g_free (nfc_temp);
+
+ return TRUE;
+ }
+
+ g_free (combination_utf8_temp);
+ g_free (nfc_temp);
+
+ if (n_compose > 2)
+ {
+ temp_swap = combination_buffer_temp[i % (n_compose - 1) + 1];
+ combination_buffer_temp[i % (n_compose - 1) + 1] = combination_buffer_temp[(i+1) % (n_compose - 1) + 1];
+ combination_buffer_temp[(i+1) % (n_compose - 1) + 1] = temp_swap;
+ }
+ else
+ break;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gtk_check_algorithmically (const guint16 *compose_buffer,
+ int n_compose,
+ gunichar *output_char)
+
+{
+ int i;
+ gunichar *combination_buffer;
+ char *combination_utf8, *nfc;
+
+ combination_buffer = alloca (sizeof (gunichar) * (n_compose + 1));
+
+ if (output_char)
+ *output_char = 0;
+
+ for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
+ ;
+ if (i == n_compose)
+ return TRUE;
+
+ if (i > 0 && i == n_compose - 1)
+ {
+ combination_buffer[0] = gdk_keyval_to_unicode (compose_buffer[i]);
+ combination_buffer[n_compose] = 0;
+ i--;
+ while (i >= 0)
+ {
+ switch (compose_buffer[i])
+ {
+#define CASE(keysym, unicode) \
+ case GDK_KEY_dead_##keysym: combination_buffer[i+1] = unicode; break
+
+ CASE (grave, 0x0300);
+ CASE (acute, 0x0301);
+ CASE (circumflex, 0x0302);
+ CASE (tilde, 0x0303); /* Also used with perispomeni, 0x342. */
+ CASE (macron, 0x0304);
+ CASE (breve, 0x0306);
+ CASE (abovedot, 0x0307);
+ CASE (diaeresis, 0x0308);
+ CASE (abovering, 0x30A);
+ CASE (hook, 0x0309);
+ CASE (doubleacute, 0x030B);
+ CASE (caron, 0x030C);
+ CASE (cedilla, 0x0327);
+ CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/
+ CASE (iota, 0x0345);
+ CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */
+ CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */
+ CASE (belowdot, 0x0323);
+ CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */
+ CASE (stroke, 0x335);
+ CASE (abovecomma, 0x0313); /* Equivalent to psili */
+ CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */
+ CASE (doublegrave, 0x30F);
+ CASE (belowring, 0x325);
+ CASE (belowmacron, 0x331);
+ CASE (belowcircumflex, 0x32D);
+ CASE (belowtilde, 0x330);
+ CASE (belowbreve, 0x32e);
+ CASE (belowdiaeresis, 0x324);
+ CASE (invertedbreve, 0x32f);
+ CASE (belowcomma, 0x326);
+ CASE (lowline, 0x332);
+ CASE (aboveverticalline, 0x30D);
+ CASE (belowverticalline, 0x329);
+ CASE (longsolidusoverlay, 0x338);
+ CASE (a, 0x363);
+ CASE (A, 0x363);
+ CASE (e, 0x364);
+ CASE (E, 0x364);
+ CASE (i, 0x365);
+ CASE (I, 0x365);
+ CASE (o, 0x366);
+ CASE (O, 0x366);
+ CASE (u, 0x367);
+ CASE (U, 0x367);
+ CASE (small_schwa, 0x1DEA);
+ CASE (capital_schwa, 0x1DEA);
+#undef CASE
+ default:
+ combination_buffer[i+1] = gdk_keyval_to_unicode (compose_buffer[i]);
+ }
+ i--;
+ }
+
+ /* If the buffer normalizes to a single character, then modify the order
+ * of combination_buffer accordingly, if necessary, and return TRUE.
+ */
+ if (check_normalize_nfc (combination_buffer, n_compose))
+ {
+ combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1, NULL, NULL, NULL);
+ nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC);
+
+ if (output_char)
+ *output_char = g_utf8_get_char (nfc);
+
+ g_free (combination_utf8);
+ g_free (nfc);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
diff --git a/gtk/gtkcomposetable.h b/gtk/gtkcomposetable.h
index 885ec6306d..101aa94359 100644
--- a/gtk/gtkcomposetable.h
+++ b/gtk/gtkcomposetable.h
@@ -29,8 +29,10 @@ typedef struct _GtkComposeTableCompact GtkComposeTableCompact;
struct _GtkComposeTable
{
guint16 *data;
+ char *char_data;
int max_seq_len;
int n_seqs;
+ int n_chars;
guint32 id;
};
@@ -42,13 +44,32 @@ struct _GtkComposeTableCompact
int n_index_stride;
};
-GtkComposeTable * gtk_compose_table_new_with_file (const char *compose_file);
-GSList *gtk_compose_table_list_add_array (GSList *compose_tables,
- const guint16 *data,
- int max_seq_len,
- int n_seqs);
-GSList *gtk_compose_table_list_add_file (GSList *compose_tables,
- const char *compose_file);
+GtkComposeTable * gtk_compose_table_new_with_file (const char *compose_file);
+GSList * gtk_compose_table_list_add_array (GSList *compose_tables,
+ const guint16 *data,
+ int max_seq_len,
+ int n_seqs);
+GSList * gtk_compose_table_list_add_file (GSList *compose_tables,
+ const char *compose_file);
+
+gboolean gtk_compose_table_check (const GtkComposeTable *table,
+ const guint16 *compose_buffer,
+ int n_compose,
+ gboolean *compose_finish,
+ gboolean *compose_match,
+ GString *output);
+
+gboolean gtk_compose_table_compact_check (const GtkComposeTableCompact *table,
+ const guint16 *compose_buffer,
+ int n_compose,
+ gboolean *compose_finish,
+ gboolean *compose_match,
+ gunichar *output_char);
+
+gboolean gtk_check_algorithmically (const guint16 *compose_buffer,
+ int n_compose,
+ gunichar *output);
+
G_END_DECLS
diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c
index 76afa8c1ee..701e433c19 100644
--- a/gtk/gtkimcontextsimple.c
+++ b/gtk/gtkimcontextsimple.c
@@ -32,7 +32,6 @@
#include "gtkcomposetable.h"
#include "gtkimmoduleprivate.h"
-#include "gtkimcontextsimpleprivate.h"
#include "gtkimcontextsimpleseqs.h"
#include "gdk/gdkprofilerprivate.h"
@@ -61,8 +60,9 @@
struct _GtkIMContextSimplePrivate
{
- guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1];
- gunichar tentative_match;
+ guint16 *compose_buffer;
+ int compose_buffer_len;
+ GString *tentative_match;
int tentative_match_len;
guint in_hex_sequence : 1;
@@ -174,8 +174,7 @@ gtk_im_context_simple_init_compose_table (void)
g_free (path);
return;
}
- g_free (path);
- path = NULL;
+ g_clear_pointer (&path, g_free);
home = g_get_home_dir ();
if (home == NULL)
@@ -190,8 +189,7 @@ gtk_im_context_simple_init_compose_table (void)
g_free (path);
return;
}
- g_free (path);
- path = NULL;
+ g_clear_pointer (&path, g_free);
locale = g_getenv ("LC_CTYPE");
if (locale == NULL)
@@ -224,8 +222,7 @@ gtk_im_context_simple_init_compose_table (void)
if (g_file_test (path, G_FILE_TEST_EXISTS))
break;
- g_free (path);
- path = NULL;
+ g_clear_pointer (&path, g_free);
}
g_free (x11_compose_file_dir);
@@ -237,8 +234,7 @@ gtk_im_context_simple_init_compose_table (void)
global_tables = gtk_compose_table_list_add_file (global_tables, path);
G_UNLOCK (global_tables);
}
- g_free (path);
- path = NULL;
+ g_clear_pointer (&path, g_free);
}
static void
@@ -271,14 +267,27 @@ init_compose_table_async (GCancellable *cancellable,
}
static void
-gtk_im_context_simple_init (GtkIMContextSimple *im_context_simple)
+gtk_im_context_simple_init (GtkIMContextSimple *context_simple)
{
- im_context_simple->priv = gtk_im_context_simple_get_instance_private (im_context_simple);
+ GtkIMContextSimplePrivate *priv;
+
+ priv = context_simple->priv = gtk_im_context_simple_get_instance_private (context_simple);
+
+ priv->compose_buffer_len = gtk_compose_table_compact.max_seq_len + 1;
+ priv->compose_buffer = g_new0 (guint16, priv->compose_buffer_len);
+ priv->tentative_match = g_string_new ("");
+ priv->tentative_match_len = 0;
}
static void
gtk_im_context_simple_finalize (GObject *obj)
{
+ GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (obj);
+ GtkIMContextSimplePrivate *priv = context_simple->priv;;
+
+ g_free (priv->compose_buffer);
+ g_string_free (priv->tentative_match, TRUE);
+
G_OBJECT_CLASS (gtk_im_context_simple_parent_class)->finalize (obj);
}
@@ -296,413 +305,29 @@ gtk_im_context_simple_new (void)
}
static void
-gtk_im_context_simple_commit_char (GtkIMContext *context,
- gunichar ch)
+gtk_im_context_simple_commit_string (GtkIMContextSimple *context_simple,
+ const char *str)
{
- GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context);
GtkIMContextSimplePrivate *priv = context_simple->priv;
- char buf[10];
- int len;
-
- g_return_if_fail (g_unichar_validate (ch));
-
- len = g_unichar_to_utf8 (ch, buf);
- buf[len] = '\0';
-
priv->in_hex_sequence = FALSE;
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
priv->compose_buffer[0] = 0;
- g_signal_emit_by_name (context, "preedit-changed");
- g_signal_emit_by_name (context, "preedit-end");
-
- g_signal_emit_by_name (context, "commit", &buf);
-}
-
-static int
-compare_seq_index (const void *key, const void *value)
-{
- const guint16 *keysyms = key;
- const guint16 *seq = value;
-
- if (keysyms[0] < seq[0])
- return -1;
- else if (keysyms[0] > seq[0])
- return 1;
-
- return 0;
-}
-
-static int
-compare_seq (const void *key, const void *value)
-{
- int i = 0;
- const guint16 *keysyms = key;
- const guint16 *seq = value;
-
- while (keysyms[i])
- {
- if (keysyms[i] < seq[i])
- return -1;
- else if (keysyms[i] > seq[i])
- return 1;
-
- i++;
- }
-
- return 0;
-}
-
-static gboolean
-check_table (GtkIMContextSimple *context_simple,
- const GtkComposeTable *table,
- int n_compose)
-{
- GtkIMContextSimplePrivate *priv = context_simple->priv;
- int row_stride = table->max_seq_len + 2;
- guint16 *seq;
-
- /* Will never match, if the sequence in the compose buffer is longer
- * than the sequences in the table. Further, compare_seq (key, val)
- * will overrun val if key is longer than val. */
- if (n_compose > table->max_seq_len)
- return FALSE;
-
- seq = bsearch (priv->compose_buffer,
- table->data, table->n_seqs,
- sizeof (guint16) * row_stride,
- compare_seq);
-
- if (seq)
- {
- guint16 *prev_seq;
-
- /* Back up to the first sequence that matches to make sure
- * we find the exact match if there is one.
- */
- while (seq > table->data)
- {
- prev_seq = seq - row_stride;
- if (compare_seq (priv->compose_buffer, prev_seq) != 0)
- break;
- seq = prev_seq;
- }
-
- if (n_compose == table->max_seq_len ||
- seq[n_compose] == 0) /* complete sequence */
- {
- guint16 *next_seq;
- gunichar value =
- 0x10000 * seq[table->max_seq_len] + seq[table->max_seq_len + 1];
-
- /* We found a tentative match. See if there are any longer
- * sequences containing this subsequence
- */
- next_seq = seq + row_stride;
- if (next_seq < table->data + row_stride * table->n_seqs)
- {
- if (compare_seq (priv->compose_buffer, next_seq) == 0)
- {
- priv->tentative_match = value;
- priv->tentative_match_len = n_compose;
-
- g_signal_emit_by_name (context_simple, "preedit-changed");
-
- return TRUE;
- }
- }
-
- gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple), value);
-
- return TRUE;
- }
-
- g_signal_emit_by_name (context_simple, "preedit-changed");
-
- return TRUE;
- }
-
- return FALSE;
+ g_signal_emit_by_name (context_simple, "preedit-changed");
+ g_signal_emit_by_name (context_simple, "preedit-end");
+ g_signal_emit_by_name (context_simple, "commit", str);
}
-/* Checks if a keysym is a dead key.
- * Dead key keysym values are defined in ../gdk/gdkkeysyms.h and the
- * first is GDK_KEY_dead_grave. As X.Org is updated, more dead keys
- * are added and we need to update the upper limit.
- */
-#define IS_DEAD_KEY(k) \
- ((k) >= GDK_KEY_dead_grave && (k) <= GDK_KEY_dead_greek)
-
-gboolean
-gtk_check_compact_table (const GtkComposeTableCompact *table,
- guint16 *compose_buffer,
- int n_compose,
- gboolean *compose_finish,
- gboolean *compose_match,
- gunichar *output_char)
-{
- int row_stride;
- guint16 *seq_index;
- guint16 *seq;
- int i;
- gboolean match;
- gunichar value;
-
- if (compose_finish)
- *compose_finish = FALSE;
- if (compose_match)
- *compose_match = FALSE;
- if (output_char)
- *output_char = 0;
-
- /* Will never match, if the sequence in the compose buffer is longer
- * than the sequences in the table. Further, compare_seq (key, val)
- * will overrun val if key is longer than val.
- */
- if (n_compose > table->max_seq_len)
- return FALSE;
-
- seq_index = bsearch (compose_buffer,
- table->data,
- table->n_index_size,
- sizeof (guint16) * table->n_index_stride,
- compare_seq_index);
-
- if (!seq_index)
- return FALSE;
-
- if (seq_index && n_compose == 1)
- return TRUE;
-
- seq = NULL;
- match = FALSE;
- value = 0;
-
- for (i = n_compose - 1; i < table->max_seq_len; i++)
- {
- row_stride = i + 1;
-
- if (seq_index[i + 1] - seq_index[i] > 0)
- {
- seq = bsearch (compose_buffer + 1,
- table->data + seq_index[i],
- (seq_index[i + 1] - seq_index[i]) / row_stride,
- sizeof (guint16) * row_stride,
- compare_seq);
-
- if (seq)
- {
- if (i == n_compose - 1)
- {
- value = seq[row_stride - 1];
- match = TRUE;
- }
- else
- {
- if (output_char)
- *output_char = value;
- if (match)
- {
- if (compose_match)
- *compose_match = TRUE;
- }
-
- return TRUE;
- }
- }
- }
- }
-
- if (match)
- {
- if (compose_match)
- *compose_match = TRUE;
- if (compose_finish)
- *compose_finish = TRUE;
- if (output_char)
- *output_char = value;
-
- return TRUE;
- }
-
- return FALSE;
-}
-
-/* This function receives a sequence of Unicode characters and tries to
- * normalize it (NFC). We check for the case where the resulting string
- * has length 1 (single character).
- * NFC normalisation normally rearranges diacritic marks, unless these
- * belong to the same Canonical Combining Class.
- * If they belong to the same canonical combining class, we produce all
- * permutations of the diacritic marks, then attempt to normalize.
- */
-static gboolean
-check_normalize_nfc (gunichar* combination_buffer, int n_compose)
+static void
+gtk_im_context_simple_commit_char (GtkIMContextSimple *context_simple,
+ gunichar ch)
{
- gunichar combination_buffer_temp[GTK_MAX_COMPOSE_LEN];
- char *combination_utf8_temp = NULL;
- char *nfc_temp = NULL;
- int n_combinations;
- gunichar temp_swap;
- int i;
-
- n_combinations = 1;
-
- for (i = 1; i < n_compose; i++ )
- n_combinations *= i;
-
- /* Xorg reuses dead_tilde for the perispomeni diacritic mark.
- * We check if base character belongs to Greek Unicode block,
- * and if so, we replace tilde with perispomeni.
- */
- if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF)
- {
- for (i = 1; i < n_compose; i++ )
- if (combination_buffer[i] == 0x303)
- combination_buffer[i] = 0x342;
- }
-
- memcpy (combination_buffer_temp, combination_buffer, GTK_MAX_COMPOSE_LEN * sizeof (gunichar) );
-
- for (i = 0; i < n_combinations; i++ )
- {
- g_unicode_canonical_ordering (combination_buffer_temp, n_compose);
- combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, -1, NULL, NULL, NULL);
- nfc_temp = g_utf8_normalize (combination_utf8_temp, -1, G_NORMALIZE_NFC);
-
- if (g_utf8_strlen (nfc_temp, -1) == 1)
- {
- memcpy (combination_buffer, combination_buffer_temp, GTK_MAX_COMPOSE_LEN * sizeof (gunichar) );
-
- g_free (combination_utf8_temp);
- g_free (nfc_temp);
-
- return TRUE;
- }
+ char buf[8] = { 0, };
- g_free (combination_utf8_temp);
- g_free (nfc_temp);
+ g_unichar_to_utf8 (ch, buf);
- if (n_compose > 2)
- {
- temp_swap = combination_buffer_temp[i % (n_compose - 1) + 1];
- combination_buffer_temp[i % (n_compose - 1) + 1] = combination_buffer_temp[(i+1) % (n_compose - 1) + 1];
- combination_buffer_temp[(i+1) % (n_compose - 1) + 1] = temp_swap;
- }
- else
- break;
- }
-
- return FALSE;
-}
-
-gboolean
-gtk_check_algorithmically (const guint16 *compose_buffer,
- int n_compose,
- gunichar *output_char)
-
-{
- int i;
- gunichar combination_buffer[GTK_MAX_COMPOSE_LEN];
- char *combination_utf8, *nfc;
-
- if (output_char)
- *output_char = 0;
-
- if (n_compose >= GTK_MAX_COMPOSE_LEN)
- return FALSE;
-
- for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
- ;
- if (i == n_compose)
- return TRUE;
-
- if (i > 0 && i == n_compose - 1)
- {
- combination_buffer[0] = gdk_keyval_to_unicode (compose_buffer[i]);
- combination_buffer[n_compose] = 0;
- i--;
- while (i >= 0)
- {
- switch (compose_buffer[i])
- {
-#define CASE(keysym, unicode) \
- case GDK_KEY_dead_##keysym: combination_buffer[i+1] = unicode; break
-
- CASE (grave, 0x0300);
- CASE (acute, 0x0301);
- CASE (circumflex, 0x0302);
- CASE (tilde, 0x0303); /* Also used with perispomeni, 0x342. */
- CASE (macron, 0x0304);
- CASE (breve, 0x0306);
- CASE (abovedot, 0x0307);
- CASE (diaeresis, 0x0308);
- CASE (abovering, 0x30A);
- CASE (hook, 0x0309);
- CASE (doubleacute, 0x030B);
- CASE (caron, 0x030C);
- CASE (cedilla, 0x0327);
- CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/
- CASE (iota, 0x0345);
- CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */
- CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */
- CASE (belowdot, 0x0323);
- CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */
- CASE (stroke, 0x335);
- CASE (abovecomma, 0x0313); /* Equivalent to psili */
- CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */
- CASE (doublegrave, 0x30F);
- CASE (belowring, 0x325);
- CASE (belowmacron, 0x331);
- CASE (belowcircumflex, 0x32D);
- CASE (belowtilde, 0x330);
- CASE (belowbreve, 0x32e);
- CASE (belowdiaeresis, 0x324);
- CASE (invertedbreve, 0x32f);
- CASE (belowcomma, 0x326);
- CASE (lowline, 0x332);
- CASE (aboveverticalline, 0x30D);
- CASE (belowverticalline, 0x329);
- CASE (longsolidusoverlay, 0x338);
- CASE (a, 0x363);
- CASE (A, 0x363);
- CASE (e, 0x364);
- CASE (E, 0x364);
- CASE (i, 0x365);
- CASE (I, 0x365);
- CASE (o, 0x366);
- CASE (O, 0x366);
- CASE (u, 0x367);
- CASE (U, 0x367);
- CASE (small_schwa, 0x1DEA);
- CASE (capital_schwa, 0x1DEA);
-#undef CASE
- default:
- combination_buffer[i+1] = gdk_keyval_to_unicode (compose_buffer[i]);
- }
- i--;
- }
-
- /* If the buffer normalizes to a single character, then modify the order
- * of combination_buffer accordingly, if necessary, and return TRUE.
- */
- if (check_normalize_nfc (combination_buffer, n_compose))
- {
- combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1, NULL, NULL, NULL);
- nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC);
-
- if (output_char)
- *output_char = g_utf8_get_char (nfc);
-
- g_free (combination_utf8);
- g_free (nfc);
-
- return TRUE;
- }
- }
-
- return FALSE;
+ gtk_im_context_simple_commit_string (context_simple, buf);
}
/* In addition to the table-driven sequences, we allow Unicode hex
@@ -733,7 +358,7 @@ check_hex (GtkIMContextSimple *context_simple,
char *nptr = NULL;
char buf[7];
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
str = g_string_new (NULL);
@@ -773,7 +398,8 @@ check_hex (GtkIMContextSimple *context_simple,
if (g_unichar_validate (n))
{
- priv->tentative_match = n;
+ g_string_set_size (priv->tentative_match, 0);
+ g_string_append_unichar (priv->tentative_match, n);
priv->tentative_match_len = n_compose;
}
@@ -809,18 +435,23 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
/* No compose sequences found, check first if we have a partial
* match pending.
*/
- if (priv->tentative_match)
+ if (priv->tentative_match_len > 0)
{
int len = priv->tentative_match_len;
int i;
- guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1];
+ guint16 *compose_buffer;
+ char *str;
- memcpy (compose_buffer, priv->compose_buffer, sizeof (compose_buffer));
+ compose_buffer = alloca (sizeof (guint16) * priv->compose_buffer_len);
+
+ memcpy (compose_buffer, priv->compose_buffer, sizeof (guint16) * priv->compose_buffer_len);
+
+ str = g_strdup (priv->tentative_match->str);
+ gtk_im_context_simple_commit_string (context_simple, str);
+ g_free (str);
- gtk_im_context_simple_commit_char (context, priv->tentative_match);
-
for (i = 0; i < n_compose - len - 1; i++)
- {
+ {
GdkTranslatedKey translated;
translated.keyval = compose_buffer[len + i];
translated.consumed = 0;
@@ -858,7 +489,7 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
ch = gdk_keyval_to_unicode (keyval);
if (ch != 0 && !g_unichar_iscntrl (ch))
{
- gtk_im_context_simple_commit_char (context, ch);
+ gtk_im_context_simple_commit_char (context_simple, ch);
return TRUE;
}
else
@@ -942,7 +573,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
gunichar output_char;
guint keyval, state;
- while (priv->compose_buffer[n_compose] != 0)
+ while (priv->compose_buffer[n_compose] != 0 && n_compose < priv->compose_buffer_len)
n_compose++;
keyval = gdk_key_event_get_keyval (event);
@@ -954,10 +585,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
(keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R))
{
- if (priv->tentative_match &&
- g_unichar_validate (priv->tentative_match))
+ if (priv->tentative_match->len > 0)
{
- gtk_im_context_simple_commit_char (context, priv->tentative_match);
+ char *str = g_strdup (priv->tentative_match->str);
+ gtk_im_context_simple_commit_string (context_simple, str);
+ g_free (str);
return TRUE;
}
@@ -972,7 +604,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* invalid hex sequence */
beep_surface (surface);
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
@@ -1072,10 +704,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* Check for hex sequence restart */
if (priv->in_hex_sequence && have_hex_mods && is_hex_start)
{
- if (priv->tentative_match &&
- g_unichar_validate (priv->tentative_match))
+ if (priv->tentative_match->len > 0)
{
- gtk_im_context_simple_commit_char (context, priv->tentative_match);
+ char *str = g_strdup (priv->tentative_match->str);
+ gtk_im_context_simple_commit_string (context_simple, str);
+ g_free (str);
}
else
{
@@ -1083,7 +716,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
if (n_compose > 0)
beep_surface (surface);
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
}
@@ -1095,7 +728,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
priv->compose_buffer[0] = 0;
priv->in_hex_sequence = TRUE;
priv->modifiers_dropped = FALSE;
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
g_signal_emit_by_name (context_simple, "preedit-start");
g_signal_emit_by_name (context_simple, "preedit-changed");
@@ -1106,7 +739,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* Then, check for compose sequences */
if (priv->in_hex_sequence)
{
- if (hex_keyval)
+ if (hex_keyval && n_compose < 6)
priv->compose_buffer[n_compose++] = hex_keyval;
else if (is_escape)
{
@@ -1115,13 +748,21 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
}
else if (!is_hex_end)
{
- /* non-hex character in hex sequence */
+ /* non-hex character in hex sequence, or sequence too long */
beep_surface (surface);
return TRUE;
}
}
else
- priv->compose_buffer[n_compose++] = keyval;
+ {
+ if (n_compose + 1 == priv->compose_buffer_len)
+ {
+ priv->compose_buffer_len += 1;
+ priv->compose_buffer = g_renew (guint16, priv->compose_buffer, priv->compose_buffer_len);
+ }
+
+ priv->compose_buffer[n_compose++] = keyval;
+ }
priv->compose_buffer[n_compose] = 0;
@@ -1133,17 +774,18 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
/* space or return ends the sequence, and we eat the key */
if (n_compose > 0 && is_hex_end)
{
- if (priv->tentative_match &&
- g_unichar_validate (priv->tentative_match))
+ if (priv->tentative_match->len > 0)
{
- gtk_im_context_simple_commit_char (context, priv->tentative_match);
+ char *str = g_strdup (priv->tentative_match->str);
+ gtk_im_context_simple_commit_string (context_simple, str);
+ g_free (str);
}
else
{
/* invalid hex sequence */
beep_surface (surface);
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->in_hex_sequence = FALSE;
priv->compose_buffer[0] = 0;
}
@@ -1162,43 +804,65 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
else
{
gboolean success = FALSE;
+ GString *output;
+
+ output = g_string_new ("");
G_LOCK (global_tables);
tmp_list = global_tables;
while (tmp_list)
{
- if (check_table (context_simple, tmp_list->data, n_compose))
+ if (gtk_compose_table_check ((GtkComposeTable *)tmp_list->data,
+ priv->compose_buffer, n_compose,
+ &compose_finish, &compose_match,
+ output))
{
+ if (compose_finish)
+ {
+ if (compose_match)
+ gtk_im_context_simple_commit_string (context_simple, output->str);
+ }
+ else
+ {
+ if (compose_match)
+ {
+ g_string_assign (priv->tentative_match, output->str);
+ priv->tentative_match_len = n_compose;
+ }
+ g_signal_emit_by_name (context_simple, "preedit-changed");
+ }
+
success = TRUE;
break;
}
+
tmp_list = tmp_list->next;
}
G_UNLOCK (global_tables);
+ g_string_free (output, TRUE);
+
if (success)
return TRUE;
- if (gtk_check_compact_table (&gtk_compose_table_compact,
- priv->compose_buffer,
- n_compose, &compose_finish,
- &compose_match, &output_char))
+ if (gtk_compose_table_compact_check (&gtk_compose_table_compact,
+ priv->compose_buffer, n_compose,
+ &compose_finish, &compose_match,
+ &output_char))
{
if (compose_finish)
{
if (compose_match)
- {
- gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple),
- output_char);
- }
+ gtk_im_context_simple_commit_char (context_simple, output_char);
}
else
{
if (compose_match)
{
- priv->tentative_match = output_char;
+ g_string_set_size (priv->tentative_match, 0);
+ g_string_append_unichar (priv->tentative_match, output_char);
priv->tentative_match_len = n_compose;
}
g_signal_emit_by_name (context_simple, "preedit-changed");
@@ -1206,18 +870,15 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
return TRUE;
}
-
+
if (gtk_check_algorithmically (priv->compose_buffer, n_compose, &output_char))
{
if (output_char)
- {
- gtk_im_context_simple_commit_char (GTK_IM_CONTEXT (context_simple),
- output_char);
- }
- return TRUE;
+ gtk_im_context_simple_commit_char (context_simple, output_char);
+ return TRUE;
}
}
-
+
/* The current compose_buffer doesn't match anything */
return no_sequence_matches (context_simple, n_compose, (GdkEvent *)event);
}
@@ -1230,10 +891,10 @@ gtk_im_context_simple_reset (GtkIMContext *context)
priv->compose_buffer[0] = 0;
- if (priv->tentative_match || priv->in_hex_sequence)
+ if (priv->tentative_match->len > 0 || priv->in_hex_sequence)
{
priv->in_hex_sequence = FALSE;
- priv->tentative_match = 0;
+ g_string_set_size (priv->tentative_match, 0);
priv->tentative_match_len = 0;
g_signal_emit_by_name (context_simple, "preedit-changed");
g_signal_emit_by_name (context_simple, "preedit-end");
@@ -1260,9 +921,9 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
for (i = 0; priv->compose_buffer[i]; i++)
g_string_append_unichar (s, gdk_keyval_to_unicode (priv->compose_buffer[i]));
}
- else if (priv->tentative_match && priv->compose_buffer[0] != 0)
+ else if (priv->tentative_match->len > 0 && priv->compose_buffer[0] != 0)
{
- g_string_append_unichar (s, priv->tentative_match);
+ g_string_append (s, priv->tentative_match->str);
}
else
{
@@ -1300,7 +961,6 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context,
* @context_simple: A #GtkIMContextSimple
* @data: (array): the table
* @max_seq_len: Maximum length of a sequence in the table
- * (cannot be greater than #GTK_MAX_COMPOSE_LEN)
* @n_seqs: number of sequences in the table
*
* Adds an additional table to search to the input context.
@@ -1320,7 +980,6 @@ gtk_im_context_simple_add_table (GtkIMContextSimple *context_simple,
int n_seqs)
{
g_return_if_fail (GTK_IS_IM_CONTEXT_SIMPLE (context_simple));
- g_return_if_fail (max_seq_len <= GTK_MAX_COMPOSE_LEN);
G_LOCK (global_tables);
@@ -1345,8 +1004,7 @@ gtk_im_context_simple_add_compose_file (GtkIMContextSimple *context_simple,
G_LOCK (global_tables);
- global_tables = gtk_compose_table_list_add_file (global_tables,
- compose_file);
+ global_tables = gtk_compose_table_list_add_file (global_tables, compose_file);
G_UNLOCK (global_tables);
}
diff --git a/gtk/gtkimcontextsimple.h b/gtk/gtkimcontextsimple.h
index e25cd4d216..7bd4454b1c 100644
--- a/gtk/gtkimcontextsimple.h
+++ b/gtk/gtkimcontextsimple.h
@@ -27,10 +27,9 @@
G_BEGIN_DECLS
-/**
- * GTK_MAX_COMPOSE_LEN:
- *
- * The maximum length of sequences in compose tables.
+/*
+ * No longer used by GTK, just left here on the off chance that some
+ * 3rd party code used this define.
*/
#define GTK_MAX_COMPOSE_LEN 7
diff --git a/gtk/gtkimcontextsimpleprivate.h b/gtk/gtkimcontextsimpleprivate.h
deleted file mode 100644
index 5b79ed2f44..0000000000
--- a/gtk/gtkimcontextsimpleprivate.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* GTK - The GIMP Toolkit
- * Copyright (C) 2000 Red Hat Software
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__
-#define __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__
-
-#include <glib.h>
-
-#include "gdk/gdkkeysyms.h"
-
-G_BEGIN_DECLS
-
-extern const GtkComposeTableCompact gtk_compose_table_compact;
-
-gboolean gtk_check_algorithmically (const guint16 *compose_buffer,
- int n_compose,
- gunichar *output);
-gboolean gtk_check_compact_table (const GtkComposeTableCompact *table,
- guint16 *compose_buffer,
- int n_compose,
- gboolean *compose_finish,
- gboolean *compose_match,
- gunichar *output_char);
-
-G_END_DECLS
-
-
-#endif /* __GTK_IM_CONTEXT_SIMPLE_PRIVATE_H__ */
diff --git a/testsuite/gtk/compose/basic b/testsuite/gtk/compose/basic
new file mode 100644
index 0000000000..536fb013fc
--- /dev/null
+++ b/testsuite/gtk/compose/basic
@@ -0,0 +1 @@
+<Multi_key> <s> <e> <q> : "!"
diff --git a/testsuite/gtk/compose/basic.expected b/testsuite/gtk/compose/basic.expected
new file mode 100644
index 0000000000..ab7a0d2b94
--- /dev/null
+++ b/testsuite/gtk/compose/basic.expected
@@ -0,0 +1,3 @@
+# n_seqs: 1
+# max_seq_len: 4
+<Uff20> <U73> <U65> <U71> : "!" # U21
diff --git a/testsuite/gtk/compose/codepoint b/testsuite/gtk/compose/codepoint
new file mode 100644
index 0000000000..22f44be8f9
--- /dev/null
+++ b/testsuite/gtk/compose/codepoint
@@ -0,0 +1 @@
+<Multi_key> <U73> <U6F> <U7a> : "!"
diff --git a/testsuite/gtk/compose/codepoint.expected b/testsuite/gtk/compose/codepoint.expected
new file mode 100644
index 0000000000..d2c09f6c3f
--- /dev/null
+++ b/testsuite/gtk/compose/codepoint.expected
@@ -0,0 +1,3 @@
+# n_seqs: 1
+# max_seq_len: 4
+<Uff20> <U73> <U6f> <U7a> : "!" # U21
diff --git a/testsuite/gtk/compose/hex b/testsuite/gtk/compose/hex
new file mode 100644
index 0000000000..267972168f
--- /dev/null
+++ b/testsuite/gtk/compose/hex
@@ -0,0 +1 @@
+<Multi_key> <s> <e> <q> : "\x23fe\X23F3"
diff --git a/testsuite/gtk/compose/hex.expected b/testsuite/gtk/compose/hex.expected
new file mode 100644
index 0000000000..603344216c
--- /dev/null
+++ b/testsuite/gtk/compose/hex.expected
@@ -0,0 +1,3 @@
+# n_seqs: 1
+# max_seq_len: 4
+<Uff20> <U73> <U65> <U71> : "⏾⏳"
diff --git a/testsuite/gtk/compose/long b/testsuite/gtk/compose/long
new file mode 100644
index 0000000000..84ef7acee8
--- /dev/null
+++ b/testsuite/gtk/compose/long
@@ -0,0 +1 @@
+<Multi_key> <e> <m> <m> <e> <n> <t> <a> <l> <e> <r> : "🧀"
diff --git a/testsuite/gtk/compose/long.expected b/testsuite/gtk/compose/long.expected
new file mode 100644
index 0000000000..17de9b5575
--- /dev/null
+++ b/testsuite/gtk/compose/long.expected
@@ -0,0 +1,3 @@
+# n_seqs: 1
+# max_seq_len: 11
+<Uff20> <U65> <U6d> <U6d> <U65> <U6e> <U74> <U61> <U6c> <U65> <U72> : "🧀" # U1f9c0
diff --git a/testsuite/gtk/compose/match b/testsuite/gtk/compose/match
new file mode 100644
index 0000000000..0554ac02c2
--- /dev/null
+++ b/testsuite/gtk/compose/match
@@ -0,0 +1,3 @@
+<Multi_key> <s> <e> <q> : "!"
+<Multi_key> <s> <e> <q> <u> : "?"
+<Multi_key> <z> <w> <i> <n> <e> <s> : "🥂"
diff --git a/testsuite/gtk/compose/multi b/testsuite/gtk/compose/multi
new file mode 100644
index 0000000000..ee8416ea1b
--- /dev/null
+++ b/testsuite/gtk/compose/multi
@@ -0,0 +1,3 @@
+<Multi_key> <s> <e> <q> : "!"
+<Multi_key> <u> <b> <2> <3> : "/"
+<Multi_key> <s> <a> <s> : "_"
diff --git a/testsuite/gtk/compose/multi.expected b/testsuite/gtk/compose/multi.expected
new file mode 100644
index 0000000000..21b97a0fe5
--- /dev/null
+++ b/testsuite/gtk/compose/multi.expected
@@ -0,0 +1,5 @@
+# n_seqs: 3
+# max_seq_len: 5
+<Uff20> <U73> <U61> <U73> <U0> : "_" # U5f
+<Uff20> <U73> <U65> <U71> <U0> : "!" # U21
+<Uff20> <U75> <U62> <U32> <U33> : "/" # U2f
diff --git a/testsuite/gtk/compose/octal b/testsuite/gtk/compose/octal
new file mode 100644
index 0000000000..350ecf8951
--- /dev/null
+++ b/testsuite/gtk/compose/octal
@@ -0,0 +1 @@
+<Multi_key> <s> <e> <q> : "\041"
diff --git a/testsuite/gtk/compose/octal.expected b/testsuite/gtk/compose/octal.expected
new file mode 100644
index 0000000000..ab7a0d2b94
--- /dev/null
+++ b/testsuite/gtk/compose/octal.expected
@@ -0,0 +1,3 @@
+# n_seqs: 1
+# max_seq_len: 4
+<Uff20> <U73> <U65> <U71> : "!" # U21
diff --git a/testsuite/gtk/compose/strings b/testsuite/gtk/compose/strings
new file mode 100644
index 0000000000..36d76785ac
--- /dev/null
+++ b/testsuite/gtk/compose/strings
@@ -0,0 +1,4 @@
+<Multi_key> <s> <e> <q> : "!a"
+<Multi_key> <s> <e> <q> <u> : "?"
+<Multi_key> <u> <b> <2> <3> : "\121\122"
+<Multi_key> <s> <a> <s> : "\"\\"
diff --git a/testsuite/gtk/compose/strings.expected b/testsuite/gtk/compose/strings.expected
new file mode 100644
index 0000000000..e1dfdc6dc1
--- /dev/null
+++ b/testsuite/gtk/compose/strings.expected
@@ -0,0 +1,6 @@
+# n_seqs: 4
+# max_seq_len: 5
+<Uff20> <U73> <U61> <U73> <U0> : "\"\\"
+<Uff20> <U73> <U65> <U71> <U0> : "!a"
+<Uff20> <U73> <U65> <U71> <U75> : "?" # U3f
+<Uff20> <U75> <U62> <U32> <U33> : "QR"
diff --git a/testsuite/gtk/composetable.c b/testsuite/gtk/composetable.c
new file mode 100644
index 0000000000..b41faf08f2
--- /dev/null
+++ b/testsuite/gtk/composetable.c
@@ -0,0 +1,320 @@
+#include <gtk/gtk.h>
+#include <locale.h>
+
+#include "../gtk/gtkcomposetable.h"
+#include "../gtk/gtkimcontextsimpleseqs.h"
+#include "testsuite/testutils.h"
+
+static void
+append_escaped (GString *str,
+ const char *s)
+{
+ for (const char *p = s; *p; p = g_utf8_next_char (p))
+ {
+ gunichar ch = g_utf8_get_char (p);
+ if (ch == '"')
+ g_string_append (str, "\\\"");
+ else if (ch == '\\')
+ g_string_append (str, "\\\\");
+ else if (g_unichar_isprint (ch))
+ g_string_append_unichar (str, ch);
+ else
+ {
+ guint n[8] = { 0, };
+ int i = 0;
+ while (ch != 0)
+ {
+ n[i++] = ch & 7;
+ ch = ch >> 3;
+ }
+ for (; i >= 0; i--)
+ g_string_append_printf (str, "\\%o", n[i]);
+ }
+ }
+}
+
+static char *
+gtk_compose_table_print (GtkComposeTable *table)
+{
+ int i, j;
+ guint16 *seq;
+ GString *str;
+
+ str = g_string_new ("");
+
+ g_string_append_printf (str, "# n_seqs: %d\n# max_seq_len: %d\n",
+ table->n_seqs,
+ table->max_seq_len);
+
+ for (i = 0, seq = table->data; i < table->n_seqs; i++, seq += table->max_seq_len + 2)
+ {
+ gunichar value;
+ char buf[8] = { 0 };
+
+ for (j = 0; j < table->max_seq_len; j++)
+ g_string_append_printf (str, "<U%x> ", seq[j]);
+
+ value = (seq[table->max_seq_len] << 16) | seq[table->max_seq_len + 1];
+ if ((value & (1 << 31)) != 0)
+ {
+ const char *out = &table->char_data[value & ~(1 << 31)];
+
+ g_string_append (str, ": \"");
+ append_escaped (str, out);
+ g_string_append (str, "\"\n");
+ }
+ else
+ {
+ g_unichar_to_utf8 (value, buf);
+ g_string_append_printf (str, ": \"%s\" # U%x\n", buf, value);
+ }
+
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+generate_output (const char *file)
+{
+ GSList *tables = NULL;
+ GtkComposeTable *table;
+ char *output;
+
+ tables = gtk_compose_table_list_add_file (tables, file);
+ table = tables->data;
+ output = gtk_compose_table_print (table);
+
+ g_print ("%s", output);
+}
+
+static void
+compose_table_compare (gconstpointer data)
+{
+ const char *basename = data;
+ GSList *tables = NULL;
+ GtkComposeTable *table;
+ char *file;
+ char *expected;
+ char *output;
+ char *diff;
+ GError *error = NULL;
+
+ file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", basename, NULL);
+ expected = g_strconcat (file, ".expected", NULL);
+
+ tables = gtk_compose_table_list_add_file (tables, file);
+
+ g_assert_true (g_slist_length (tables) == 1);
+
+ table = tables->data;
+
+ output = gtk_compose_table_print (table);
+ diff = diff_with_file (expected, output, -1, &error);
+ g_assert_no_error (error);
+
+ if (diff && diff[0])
+ {
+ g_print ("Resulting output doesn't match reference:\n%s", diff);
+ g_test_fail ();
+ }
+
+ g_free (output);
+ g_free (file);
+ g_free (expected);
+}
+
+/* Check matching against a small table */
+static void
+compose_table_match (void)
+{
+ GSList *tables = NULL;
+ GtkComposeTable *table;
+ char *file;
+ guint16 buffer[8] = { 0, };
+ gboolean finish, match, ret;
+ GString *output;
+
+ output = g_string_new ("");
+
+ file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", "match", NULL);
+
+ tables = gtk_compose_table_list_add_file (tables, file);
+
+ g_assert_true (g_slist_length (tables) == 1);
+
+ table = tables->data;
+
+ buffer[0] = GDK_KEY_Multi_key;
+ buffer[1] = 0;
+ ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
+ g_assert_true (ret);
+ g_assert_false (finish);
+ g_assert_false (match);
+ g_assert_true (output->len == 0);
+
+ buffer[0] = GDK_KEY_a;
+ buffer[1] = 0;
+ ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
+ g_assert_false (ret);
+ g_assert_false (finish);
+ g_assert_false (match);
+ g_assert_true (output->len == 0);
+
+ buffer[0] = GDK_KEY_Multi_key;
+ buffer[1] = GDK_KEY_s;
+ buffer[2] = GDK_KEY_e;
+ ret = gtk_compose_table_check (table, buffer, 3, &finish, &match, output);
+ g_assert_true (ret);
+ g_assert_false (finish);
+ g_assert_false (match);
+ g_assert_true (output->len == 0);
+
+ buffer[0] = GDK_KEY_Multi_key;
+ buffer[1] = GDK_KEY_s;
+ buffer[2] = GDK_KEY_e;
+ buffer[3] = GDK_KEY_q;
+ ret = gtk_compose_table_check (table, buffer, 4, &finish, &match, output);
+ g_assert_true (ret);
+ g_assert_false (finish);
+ g_assert_true (match);
+ g_assert_cmpstr (output->str, ==, "!");
+
+ g_string_set_size (output, 0);
+
+ buffer[0] = GDK_KEY_Multi_key;
+ buffer[1] = GDK_KEY_s;
+ buffer[2] = GDK_KEY_e;
+ buffer[3] = GDK_KEY_q;
+ buffer[4] = GDK_KEY_u;
+ ret = gtk_compose_table_check (table, buffer, 5, &finish, &match, output);
+ g_assert_true (ret);
+ g_assert_true (finish);
+ g_assert_true (match);
+ g_assert_cmpstr (output->str, ==, "?");
+
+ g_string_free (output, TRUE);
+ g_free (file);
+}
+
+/* just check some random sequences */
+static void
+compose_table_match_compact (void)
+{
+ const GtkComposeTableCompact table = {
+ gtk_compose_seqs_compact,
+ 5,
+ 30,
+ 6
+ };
+ guint16 buffer[8] = { 0, };
+ gboolean finish, match, ret;
+ gunichar ch;
+
+ buffer[0] = GDK_KEY_Multi_key;
+
+ ret = gtk_compose_table_compact_check (&table, buffer, 1, &finish, &match, &ch);
+ g_assert_true (ret);
+ g_assert_false (finish);
+ g_assert_false (match);
+ g_assert_true (ch == 0);
+
+ buffer[0] = GDK_KEY_a;
+ buffer[1] = GDK_KEY_b;
+ buffer[2] = GDK_KEY_c;
+
+ ret = gtk_compose_table_compact_check (&table, buffer, 3, &finish, &match, &ch);
+ g_assert_false (ret);
+ g_assert_false (finish);
+ g_assert_false (match);
+ g_assert_true (ch == 0);
+
+ buffer[0] = GDK_KEY_Multi_key;
+ buffer[1] = GDK_KEY_parenleft;
+ buffer[2] = GDK_KEY_j;
+ buffer[3] = GDK_KEY_parenright;
+
+ ret = gtk_compose_table_compact_check (&table, buffer, 4, &finish, &match, &ch);
+ g_assert_true (ret);
+ g_assert_true (finish);
+ g_assert_true (match);
+ g_assert_true (ch == 0x24d9); /* CIRCLED LATIN SMALL LETTER J */
+}
+
+static void
+match_algorithmic (void)
+{
+ guint16 buffer[8] = { 0, };
+ gboolean ret;
+ gunichar ch;
+
+ buffer[0] = GDK_KEY_a;
+ buffer[1] = GDK_KEY_b;
+
+ ret = gtk_check_algorithmically (buffer, 2, &ch);
+ g_assert_false (ret);
+ g_assert_true (ch == 0);
+
+ buffer[0] = GDK_KEY_dead_abovering;
+ buffer[1] = GDK_KEY_A;
+
+ ret = gtk_check_algorithmically (buffer, 2, &ch);
+ g_assert_true (ret);
+ g_assert_true (ch == 0xc5);
+
+ buffer[0] = GDK_KEY_A;
+ buffer[1] = GDK_KEY_dead_abovering;
+
+ ret = gtk_check_algorithmically (buffer, 2, &ch);
+ g_assert_false (ret);
+ g_assert_true (ch == 0);
+
+ buffer[0] = GDK_KEY_dead_dasia;
+ buffer[1] = GDK_KEY_dead_perispomeni;
+ buffer[2] = GDK_KEY_Greek_alpha;
+
+ ret = gtk_check_algorithmically (buffer, 3, &ch);
+ g_assert_true (ret);
+ g_assert_true (ch == 0x1f07);
+
+ buffer[0] = GDK_KEY_dead_perispomeni;
+ buffer[1] = GDK_KEY_dead_dasia;
+ buffer[2] = GDK_KEY_Greek_alpha;
+
+ ret = gtk_check_algorithmically (buffer, 3, &ch);
+ g_assert_true (ret);
+ g_assert_true (ch == 0x1f07);
+}
+
+int
+main (int argc, char *argv[])
+{
+ char *dir;
+
+ dir = g_dir_make_tmp ("composetableXXXXXX", NULL);
+ g_setenv ("XDG_CACHE_HOME", dir, TRUE);
+ g_free (dir);
+
+ if (argc == 3 && strcmp (argv[1], "--generate") == 0)
+ {
+ setlocale (LC_ALL, "");
+
+ generate_output (argv[2]);
+ return 0;
+ }
+
+ gtk_test_init (&argc, &argv, NULL);
+
+ g_test_add_data_func ("/compose-table/basic", "basic", compose_table_compare);
+ g_test_add_data_func ("/compose-table/long", "long", compose_table_compare);
+ g_test_add_data_func ("/compose-table/octal", "octal", compose_table_compare);
+ g_test_add_data_func ("/compose-table/hex", "hex", compose_table_compare);
+ g_test_add_data_func ("/compose-table/codepoint", "codepoint", compose_table_compare);
+ g_test_add_data_func ("/compose-table/multi", "multi", compose_table_compare);
+ g_test_add_data_func ("/compose-table/strings", "strings", compose_table_compare);
+ g_test_add_func ("/compose-table/match", compose_table_match);
+ g_test_add_func ("/compose-table/match-compact", compose_table_match_compact);
+ g_test_add_func ("/compose-table/match-algorithmic", match_algorithmic);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index 1ba0bfd9d8..b0af6dc642 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -103,6 +103,13 @@ tests = [
# Tests that test private apis and therefore are linked against libgtk-4.a
internal_tests = [
{ 'name': 'bitmask' },
+ {
+ 'name': 'composetable',
+ 'sources': [
+ 'composetable.c',
+ '../testutils.c'
+ ],
+ },
{ 'name': 'constraint-solver' },
{ 'name': 'rbtree-crash' },
{ 'name': 'propertylookuplistmodel' },