[gtk+/wip/emoji-input] Add support for entering emoji by name
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wip/emoji-input] Add support for entering emoji by name
- Date: Fri, 4 Aug 2017 05:02:22 +0000 (UTC)
commit 6569f6aa7e95ec856f8e599524d5446654b09a74
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Aug 4 00:54:15 2017 -0400
Add support for entering emoji by name
This commit adds some basic support for entering emoji by name
to GtkIMContextSimple. We respect the input-purpose property and
only support emoji in free-form fields.
To begin an emoji sequence, use Ctrl-Shift-e instead of Ctrl-Shift-u
that is used for hex input. Otherwise, the behavior is the same:
you can can let go of the modifier keys and end the sequence with
space or enter, or hold on to the modifier keys and end the sequence
by releasing them.
Only a limited, fixed set of names is supported:
:-)
8-)
<3
kiss
grin
joy
:-*
xD
like
dislike
up
v
ok
B-)
;-)
;-P
:-p
3(
:-(
;]
:'(
:_(
:((
:o
:|
3-)
>(
>((
O:)
;o
8|
8o
:X
}:)
gtk/gtkimcontextsimple.c | 222 ++++++++++++++++++++++++++++++++++++++-------
1 files changed, 187 insertions(+), 35 deletions(-)
---
diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c
index 5404f0a..f843b4d 100644
--- a/gtk/gtkimcontextsimple.c
+++ b/gtk/gtkimcontextsimple.c
@@ -72,6 +72,7 @@ struct _GtkIMContextSimplePrivate
gint tentative_match_len;
guint in_hex_sequence : 1;
+ guint in_emoji_sequence : 1;
guint modifiers_dropped : 1;
};
@@ -295,9 +296,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 +867,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 +1084,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,37 +1097,41 @@ 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;
gunichar output_char;
+ GtkInputPurpose purpose;
while (priv->compose_buffer[n_compose] != 0)
n_compose++;
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 +1139,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 +1164,25 @@ 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);
+ g_object_get (context, "input-purpose", &purpose, NULL);
+ if (purpose == GTK_INPUT_PURPOSE_FREE_FORM)
+ is_emoji_start = event->keyval == GDK_KEY_E;
+ else
+ is_emoji_start = FALSE;
+ 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 +1192,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 +1205,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 +1215,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 +1249,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 +1275,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 +1297,42 @@ 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;
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 +1347,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 +1478,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 +1500,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 +1518,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);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]