diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c index 5404f0a1ef..e5efdbbe4d 100644 --- a/gtk/gtkimcontextsimple.c +++ b/gtk/gtkimcontextsimple.c @@ -59,19 +59,63 @@ * Compose file). The syntax of these files is described in the Compose(5) * manual page. * + * ## Unicode characters + * * GtkIMContextSimple also supports numeric entry of Unicode characters * by typing Ctrl-Shift-u, followed by a hexadecimal Unicode codepoint. * For example, Ctrl-Shift-u 1 2 3 Enter yields U+0123 LATIN SMALL LETTER * G WITH CEDILLA, i.e. ģ. + * + * ## Emoji + * + * GtkIMContextSimple also supports entry of Emoji by their name. + * This works by first typing Ctrl-Shift-e, followed by an Emoji name. + * + * The following names are supported: + * - :-) 🙂 + * - 8-) 😍 + * - <3 ❤ + * - kiss 💋 + * - grin 😁 + * - joy 😂 + * - :-* 😚 + * - xD 😆 + * - like 👍 + * - dislike 👎 + * - up 👆 + * - v ✌ + * - ok 👌 + * - B-) 😎 + * - ;-) 😉 + * - ;-P 😜 + * - :-p 😋 + * - 3( 😔 + * - :-( 😞 + * - :] 😏 + * - :'( 😢 + * - :_( 😭 + * - :(( 😩 + * - :o 😨 + * - :| 😐 + * - 3-) 😌 + * - >( 😠 + * - >(( 😡 + * - O:) 😇 + * - ;o 😰 + * - 8| 😳 + * - 8o 😲 + * - :X 😷 + * - }:) 😈 */ struct _GtkIMContextSimplePrivate { - guint16 compose_buffer[GTK_MAX_COMPOSE_LEN + 1]; + guint16 compose_buffer[MAX(GTK_MAX_COMPOSE_LEN + 1, 9)]; gunichar tentative_match; gint tentative_match_len; guint in_hex_sequence : 1; + guint in_emoji_sequence : 1; guint modifiers_dropped : 1; }; @@ -295,9 +339,10 @@ gtk_im_context_simple_commit_char (GtkIMContext *context, len = g_unichar_to_utf8 (ch, buf); buf[len] = '\0'; - if (priv->tentative_match || priv->in_hex_sequence) + if (priv->tentative_match || priv->in_hex_sequence || priv->in_emoji_sequence) { priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->tentative_match = 0; priv->tentative_match_len = 0; g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -865,6 +910,104 @@ check_hex (GtkIMContextSimple *context_simple, return TRUE; } +typedef struct { + const char *name; + gunichar ch; +} EmojiItem; + +static EmojiItem emoji[] = { + { ":-)", 0x1f642 }, + { "8-)", 0x1f60d }, + { "<3", 0x02764 }, + { "kiss", 0x1f48b }, + { "grin", 0x1f601 }, + { "joy", 0x1f602 }, + { ":-*", 0x1f61a }, + { "xD", 0x1f606 }, + { "like", 0x1f44d }, + { "dislike", 0x1f44e }, + { "up", 0x1f446 }, + { "v", 0x0270c }, + { "ok", 0x1f44c }, + { "B-)", 0x1f60e }, + { ":-D", 0x1f603 }, + { ";-)", 0x1f609 }, + { ";-P", 0x1f61c }, + { ":-p", 0x1f60b }, + { "3(", 0x1f614 }, + { ":-(", 0x1f61e }, + { ":]", 0x1f60f }, + { ":'(", 0x1f622 }, + { ":_(", 0x1f62d }, + { ":((", 0x1f629 }, + { ":o", 0x1f628 }, + { ":|", 0x1f610 }, + { "3-)", 0x1f60c }, + { ">(", 0x1f620 }, + { ">((", 0x1f621 }, + { "O:)", 0x1f607 }, + { ";o", 0x1f630 }, + { "8|", 0x1f633 }, + { "8o", 0x1f632 }, + { ":X", 0x1f637 }, + { "}:)", 0x1f608 }, + { NULL, 0 } +}; + +static gboolean +check_emoji (GtkIMContextSimple *context_simple, + gint n_compose) +{ + GtkIMContextSimplePrivate *priv = context_simple->priv; + GString *str; + gint i; + gchar buf[7]; + char *lower; + + priv->tentative_match = 0; + priv->tentative_match_len = 0; + + str = g_string_new (NULL); + + i = 0; + while (i < n_compose) + { + gunichar ch; + + ch = gdk_keyval_to_unicode (priv->compose_buffer[i]); + + if (ch == 0) + return FALSE; + + if (priv->in_hex_sequence && !g_unichar_isxdigit (ch)) + return FALSE; + + buf[g_unichar_to_utf8 (ch, buf)] = '\0'; + + g_string_append (str, buf); + + ++i; + } + + lower = g_utf8_strdown (str->str, str->len); + + for (i = 0; emoji[i].name; i++) + { + if (strcmp (str->str, emoji[i].name) == 0 || + strcmp (lower, emoji[i].name) == 0) + { + priv->tentative_match = emoji[i].ch; + priv->tentative_match_len = n_compose; + break; + } + } + + g_string_free (str, TRUE); + g_free (lower); + + return priv->tentative_match != 0; +} + static void beep_window (GdkWindow *window) { @@ -984,6 +1127,12 @@ canonical_hex_keyval (GdkEventKey *event) return 0; } +static guint +canonical_emoji_keyval (GdkEventKey *event) +{ + return event->keyval; +} + static gboolean gtk_im_context_simple_filter_keypress (GtkIMContext *context, GdkEventKey *event) @@ -991,15 +1140,17 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, GtkIMContextSimple *context_simple = GTK_IM_CONTEXT_SIMPLE (context); GtkIMContextSimplePrivate *priv = context_simple->priv; GdkDisplay *display = gdk_window_get_display (event->window); - GSList *tmp_list; + GSList *tmp_list; int n_compose = 0; GdkModifierType hex_mod_mask; gboolean have_hex_mods; gboolean is_hex_start; - gboolean is_hex_end; + gboolean is_end; + gboolean is_emoji_start; gboolean is_backspace; gboolean is_escape; guint hex_keyval; + guint emoji_keyval; int i; gboolean compose_finish; gboolean compose_match; @@ -1010,18 +1161,19 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, if (event->type == GDK_KEY_RELEASE) { - if (priv->in_hex_sequence && - (event->keyval == GDK_KEY_Control_L || event->keyval == GDK_KEY_Control_R || + if ((event->keyval == GDK_KEY_Control_L || event->keyval == GDK_KEY_Control_R || event->keyval == GDK_KEY_Shift_L || event->keyval == GDK_KEY_Shift_R)) { - if (priv->tentative_match && + if ((priv->in_hex_sequence || priv->in_emoji_sequence) && + priv->tentative_match && g_unichar_validate (priv->tentative_match)) { gtk_im_context_simple_commit_char (context, priv->tentative_match); priv->compose_buffer[0] = 0; } - else if (n_compose == 0) + else if (priv->in_emoji_sequence || + (priv->in_hex_sequence && n_compose == 0)) { priv->modifiers_dropped = TRUE; } @@ -1029,11 +1181,12 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, { /* invalid hex sequence */ beep_window (event->window); - + priv->tentative_match = 0; priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; - + g_signal_emit_by_name (context_simple, "preedit-changed"); g_signal_emit_by_name (context_simple, "preedit-end"); } @@ -1053,19 +1206,21 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR); hex_mod_mask |= GDK_SHIFT_MASK; - if (priv->in_hex_sequence && priv->modifiers_dropped) + if ((priv->in_hex_sequence || priv->in_emoji_sequence) && priv->modifiers_dropped) have_hex_mods = TRUE; else have_hex_mods = (event->state & (hex_mod_mask)) == hex_mod_mask; is_hex_start = event->keyval == GDK_KEY_U; - is_hex_end = (event->keyval == GDK_KEY_space || - event->keyval == GDK_KEY_KP_Space || - event->keyval == GDK_KEY_Return || - event->keyval == GDK_KEY_ISO_Enter || - event->keyval == GDK_KEY_KP_Enter); + is_emoji_start = event->keyval == GDK_KEY_E; + is_end = (event->keyval == GDK_KEY_space || + event->keyval == GDK_KEY_KP_Space || + event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_ISO_Enter || + event->keyval == GDK_KEY_KP_Enter); is_backspace = event->keyval == GDK_KEY_BackSpace; is_escape = event->keyval == GDK_KEY_Escape; hex_keyval = canonical_hex_keyval (event); + emoji_keyval = canonical_emoji_keyval (event); /* If we are already in a non-hex sequence, or * this keystroke is not hex modifiers + hex digit, don't filter @@ -1075,10 +1230,11 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, * ISO_Level3_Switch. */ if (!have_hex_mods || - (n_compose > 0 && !priv->in_hex_sequence) || - (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start) || + (n_compose > 0 && !priv->in_hex_sequence && !priv->in_emoji_sequence) || + (n_compose == 0 && !priv->in_hex_sequence && !priv->in_emoji_sequence && + !is_hex_start && !is_emoji_start) || (priv->in_hex_sequence && !hex_keyval && - !is_hex_start && !is_hex_end && !is_escape && !is_backspace)) + !is_hex_start && !is_end && !is_escape && !is_backspace)) { GdkModifierType no_text_input_mask; @@ -1087,7 +1243,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, GDK_MODIFIER_INTENT_NO_TEXT_INPUT); if (event->state & no_text_input_mask || - (priv->in_hex_sequence && priv->modifiers_dropped && + ((priv->in_hex_sequence || priv->in_emoji_sequence) && priv->modifiers_dropped && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_ISO_Enter || event->keyval == GDK_KEY_KP_Enter))) @@ -1097,17 +1253,21 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, } /* Handle backspace */ - if (priv->in_hex_sequence && have_hex_mods && is_backspace) + if ((priv->in_hex_sequence || priv->in_emoji_sequence) && have_hex_mods && is_backspace) { if (n_compose > 0) { n_compose--; priv->compose_buffer[n_compose] = 0; - check_hex (context_simple, n_compose); + if (priv->in_hex_sequence) + check_hex (context_simple, n_compose); + else if (priv->in_emoji_sequence) + check_emoji (context_simple, n_compose); } else { priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; } g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -1127,7 +1287,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, gtk_im_context_simple_commit_char (context, priv->tentative_match); priv->compose_buffer[0] = 0; } - else + else { /* invalid hex sequence */ if (n_compose > 0) @@ -1153,6 +1313,20 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, return TRUE; } + /* Check for emoji sequence start */ + if (!priv->in_emoji_sequence && have_hex_mods && is_emoji_start) + { + priv->compose_buffer[0] = 0; + priv->in_emoji_sequence = TRUE; + priv->modifiers_dropped = FALSE; + priv->tentative_match = 0; + + g_signal_emit_by_name (context_simple, "preedit-start"); + g_signal_emit_by_name (context_simple, "preedit-changed"); + + return TRUE; + } + /* Then, check for compose sequences */ if (priv->in_hex_sequence) { @@ -1161,29 +1335,54 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, else if (is_escape) { gtk_im_context_simple_reset (context); - return TRUE; } - else if (!is_hex_end) + else if (!is_end) { /* non-hex character in hex sequence */ beep_window (event->window); - return TRUE; } } + else if (priv->in_emoji_sequence) + { + if (emoji_keyval) + priv->compose_buffer[n_compose++] = emoji_keyval; + else if (is_escape) + { + gtk_im_context_simple_reset (context); + return TRUE; + } + else + { + beep_window (event->window); + return TRUE; + } + } else priv->compose_buffer[n_compose++] = event->keyval; + if (n_compose == MAX(GTK_MAX_COMPOSE_LEN + 1, 9)) + { + beep_window (event->window); + priv->tentative_match = 0; + priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; + priv->compose_buffer[0] = 0; + g_signal_emit_by_name (context_simple, "preedit-changed"); + + return TRUE; + } + priv->compose_buffer[n_compose] = 0; - if (priv->in_hex_sequence) + if (priv->in_hex_sequence || priv->in_emoji_sequence) { /* If the modifiers are still held down, consider the sequence again */ if (have_hex_mods) { /* space or return ends the sequence, and we eat the key */ - if (n_compose > 0 && is_hex_end) + if (n_compose > 0 && is_end) { if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) @@ -1198,15 +1397,17 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context, priv->tentative_match = 0; priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; } } - else if (!check_hex (context_simple, n_compose)) + else if ((priv->in_hex_sequence && !check_hex (context_simple, n_compose)) || + (priv->in_emoji_sequence && !check_emoji (context_simple, n_compose))) beep_window (event->window); - + g_signal_emit_by_name (context_simple, "preedit-changed"); - if (!priv->in_hex_sequence) + if (!priv->in_hex_sequence && !priv->in_emoji_sequence) g_signal_emit_by_name (context_simple, "preedit-end"); return TRUE; @@ -1327,9 +1528,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 || priv->in_hex_sequence || priv->in_emoji_sequence) { priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->tentative_match = 0; priv->tentative_match_len = 0; g_signal_emit_by_name (context_simple, "preedit-changed"); @@ -1348,11 +1550,11 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context, char outbuf[37]; /* up to 6 hex digits */ int len = 0; - if (priv->in_hex_sequence) + if (priv->in_hex_sequence || priv->in_emoji_sequence) { int hexchars = 0; - outbuf[0] = 'u'; + outbuf[0] = priv->in_hex_sequence ? 'u' : 'e'; len = 1; while (priv->compose_buffer[hexchars] != 0) @@ -1366,8 +1568,8 @@ gtk_im_context_simple_get_preedit_string (GtkIMContext *context, } else if (priv->tentative_match) len = g_unichar_to_utf8 (priv->tentative_match, outbuf); - - outbuf[len] = '\0'; + + outbuf[len] = '\0'; if (str) *str = g_strdup (outbuf);