[gtk+/gtk-3-22] Add support for entering emoji by name



commit a86de5905c0946b09b4ea89c87c87bdd5e38033b
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. 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 at this time, see
    the GtkIMContextSimple docs for a full list.

 gtk/gtkimcontextsimple.c |  274 ++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 238 insertions(+), 36 deletions(-)
---
diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c
index 987e2b0..e730b17 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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]