summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfujiwarat <takao.fujiwara1@gmail.com>2016-06-21 18:10:21 +0900
committerfujiwarat <takao.fujiwara1@gmail.com>2016-06-21 18:10:21 +0900
commit160d3c975af91eea6b8271b757be769b8ceef98d (patch)
tree8ea7728f7076896e927186eb12390ab7db0f24f4
parenta598ae29223d1ca25e76bf7d7de9703f63ea337e (diff)
downloadibus-160d3c975af91eea6b8271b757be769b8ceef98d.tar.gz
engine: Implement Emoji typing with XKB engines
Now Ctrl+Shift+e can convert an Emoji annotation to the Emoji characters likes Ctrl+Shift+u. The annotations are described as "Keywords" in the Unicode Emoji Data: http://www.unicode.org/emoji/charts/emoji-list.html 'emoji-parser' compiles 'emoji-list.html' and generates 'emoji.dict' Review URL: https://codereview.appspot.com/295610043
-rw-r--r--configure.ac16
-rw-r--r--src/Makefile.am31
-rw-r--r--src/emoji-parser.c217
-rw-r--r--src/ibusenginesimple.c443
-rw-r--r--src/ibusutil.c193
-rw-r--r--src/ibusutil.h31
6 files changed, 890 insertions, 41 deletions
diff --git a/configure.ac b/configure.ac
index 1e1f5dd3..3128ef98 100644
--- a/configure.ac
+++ b/configure.ac
@@ -599,6 +599,21 @@ if test x"$enable_libnotify" = x"yes"; then
enable_libnotify="yes (enabled, use --disable-libnotify to disable)"
fi
+# --disable-emoji-dict option.
+AC_ARG_ENABLE(emoji-dict,
+ AS_HELP_STRING([--disable-emoji-dict],
+ [Do not build Emoji dict files]),
+ [enable_emoji_dict=$enableval],
+ [enable_emoji_dict=yes]
+)
+AM_CONDITIONAL([ENABLE_EMOJI_DICT], [test x"$enable_emoji_dict" = x"yes"])
+if test x"$enable_emoji_dict" = x"yes"; then
+ PKG_CHECK_MODULES(LIBXML2, [
+ libxml-2.0
+ ])
+ enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
+fi
+
# Check iso-codes.
PKG_CHECK_MODULES(ISOCODES, [
iso-codes
@@ -682,6 +697,7 @@ Build options:
Panel icon "$IBUS_ICON_KEYBOARD"
Enable surrounding-text $enable_surrounding_text
Enable libnotify $enable_libnotify
+ Enable Emoji dict $enable_emoji_dict
Run test cases $enable_tests
])
diff --git a/src/Makefile.am b/src/Makefile.am
index adaebe98..a33b67dd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -231,7 +231,38 @@ ibusmarshalers.c: ibusmarshalers.h ibusmarshalers.list
$(GLIB_GENMARSHAL) --prefix=_ibus_marshal $(srcdir)/ibusmarshalers.list --body --internal) > $@.tmp && \
mv $@.tmp $@
+if ENABLE_EMOJI_DICT
+AM_CPPFLAGS += -DENABLE_EMOJI_DICT
+
+dictdir = $(pkgdatadir)/dicts
+dict_DATA = emoji.dict
+
+noinst_PROGRAMS = emoji-parser
+
+emoji.dict: emoji-parser emoji-list.html
+ $(builddir)/emoji-parser emoji-list.html $@
+
+emoji_parser_SOURCES = \
+ emoji-parser.c \
+ $(NULL)
+emoji_parser_CFLAGS = \
+ $(GLIB2_CFLAGS) \
+ $(LIBXML2_CFLAGS) \
+ $(NULL)
+emoji_parser_LDADD = \
+ $(GLIB2_LIBS) \
+ $(LIBXML2_LIBS) \
+ $(libibus) \
+ $(NULL)
+
+CLEANFILES += \
+ $(dict_DATA) \
+ $(NULL)
+endif
+
EXTRA_DIST = \
+ emoji-list.html \
+ emoji-parser.c \
ibusversion.h.in \
ibusmarshalers.list \
ibusenumtypes.h.template \
diff --git a/src/emoji-parser.c b/src/emoji-parser.c
new file mode 100644
index 00000000..cf92feea
--- /dev/null
+++ b/src/emoji-parser.c
@@ -0,0 +1,217 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2016 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2016 Red Hat, Inc.
+ *
+ * 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.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+/* Convert http://www.unicode.org/emoji/charts/emoji-list.html
+ * to the dictionary file which look up the Emoji from the annotation.
+ */
+
+#include <glib.h>
+#include <libxml/HTMLparser.h>
+#include <libgen.h>
+
+#include "ibusutil.h"
+
+typedef struct _EmojiData EmojiData;
+struct _EmojiData {
+ gchar *class;
+ gchar *emoji;
+ GSList *annotates;
+ GSList *prev_annotates;
+ GHashTable *dict;
+};
+
+const gchar *progname;
+
+static gboolean parse_node (xmlNode *node,
+ gboolean is_child,
+ const gchar *prop_name,
+ EmojiData *data);
+
+static void
+usage (void)
+{
+ g_print ("%s emoji-list.html emoji.dict\n", progname);
+}
+
+static void
+reset_emoji_element (EmojiData *data)
+{
+ g_clear_pointer (&data->class, g_free);
+ g_clear_pointer (&data->emoji, g_free);
+ if (data->annotates) {
+ g_slist_free_full (data->prev_annotates, g_free);
+ data->prev_annotates = data->annotates;
+ data->annotates = NULL;
+ }
+}
+
+static void
+free_dict_words (gpointer list)
+{
+ g_slist_free_full (list, g_free);
+}
+
+static gboolean
+parse_attr (xmlAttr *attr,
+ EmojiData *data)
+{
+ if (g_strcmp0 ((const gchar *) attr->name, "class") == 0 && attr->children)
+ parse_node (attr->children, TRUE, (const gchar *) attr->name, data);
+ if (g_strcmp0 ((const gchar *) attr->name, "target") == 0 && attr->children)
+ parse_node (attr->children, TRUE, (const gchar *) attr->name, data);
+ if (attr->next)
+ parse_attr (attr->next, data);
+ return TRUE;
+}
+
+static gboolean
+parse_node (xmlNode *node,
+ gboolean is_child,
+ const gchar *prop_name,
+ EmojiData *data)
+{
+ if (g_strcmp0 ((const gchar *) node->name, "tr") == 0) {
+ GSList *annotates = data->annotates;
+ while (annotates) {
+ GSList *emojis = g_hash_table_lookup (data->dict, annotates->data);
+ if (emojis) {
+ emojis = g_slist_copy_deep (emojis, (GCopyFunc) g_strdup, NULL);
+ }
+ emojis = g_slist_append (emojis, g_strdup (data->emoji));
+ g_hash_table_replace (data->dict,
+ g_strdup (annotates->data),
+ emojis);
+ annotates = annotates->next;
+ }
+ reset_emoji_element (data);
+ }
+ /* if node->name is "text" and is_child is FALSE,
+ * it's '\n' or Space between <td> and <td>.
+ */
+ if (g_strcmp0 ((const gchar *) node->name, "text") == 0 && is_child) {
+ /* Get "chars" in <td class="chars"> */
+ if (g_strcmp0 (prop_name, "class") == 0) {
+ if (g_strcmp0 (data->class, (const gchar *) node->content) != 0) {
+ g_clear_pointer (&data->class, g_free);
+ data->class = g_strdup ((const gchar *) node->content);
+ }
+ }
+ /* Get "annotate" in <td class="name"><a target="annotate"> */
+ if (g_strcmp0 (prop_name, "target") == 0 &&
+ g_strcmp0 (data->class, "name") == 0) {
+ g_clear_pointer (&data->class, g_free);
+ data->class = g_strdup ((const gchar *) node->content);
+ }
+ /* Get "emoji" in <td class="chars">emoji</td> */
+ if (g_strcmp0 (prop_name, "td") == 0 &&
+ g_strcmp0 (data->class, "chars") == 0) {
+ data->emoji = g_strdup ((const gchar *) node->content);
+ }
+ /* We ignore "NAME" for <td class="name">NAME</td> but
+ * takes "ANNOTATE" for
+ * <td class="name"><a target="annotate">ANNOTATE</a></td>
+ */
+ if (g_strcmp0 (prop_name, "td") == 0 &&
+ g_strcmp0 (data->class, "name") == 0) {
+ g_slist_free_full (data->annotates, g_free);
+ data->annotates = NULL;
+ }
+ /* Get "ANNOTATE" in
+ * <td class="name"><a target="annotate">ANNOTATE</a></td>
+ */
+ if (g_strcmp0 (prop_name, "a") == 0 &&
+ g_strcmp0 (data->class, "annotate") == 0) {
+ data->annotates =
+ g_slist_append (data->annotates,
+ g_strdup ((const gchar *) node->content));
+ }
+ }
+ /* Get "foo" in <td class="foo"> */
+ if (g_strcmp0 ((const gchar *) node->name, "td") == 0 &&
+ node->properties != NULL) {
+ parse_attr (node->properties, data);
+ }
+ /* Get "foo" in <a target="foo"> */
+ if (g_strcmp0 ((const gchar *) node->name, "a") == 0 &&
+ node->properties != NULL) {
+ parse_attr (node->properties, data);
+ }
+ if (node->children) {
+ parse_node (node->children, TRUE, (const gchar *) node->name, data);
+ } else {
+ /* If annotate is NULL likes <td class="name"></td>,
+ * the previous emoji cell has the same annotate.
+ */
+ if (g_strcmp0 ((const gchar *) node->name, "td") == 0 &&
+ g_strcmp0 (data->class, "name") == 0) {
+ data->annotates = g_slist_copy_deep (data->prev_annotates,
+ (GCopyFunc) g_strdup,
+ NULL);
+ }
+ }
+ if (node->next)
+ parse_node (node->next, FALSE, (const gchar *) node->name, data);
+
+ return TRUE;
+}
+
+static GHashTable *
+parse_html (const gchar *filename)
+{
+ xmlDoc *doc = htmlParseFile (filename, "utf-8");
+ EmojiData data = { 0, };
+
+ if (doc == NULL || doc->children == NULL) {
+ g_warning ("Parse Error in document type: %x",
+ doc ? doc->type : 0);
+ return FALSE;
+ }
+
+ data.dict = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ free_dict_words);
+ parse_node (doc->children, TRUE, (const gchar *) doc->name, &data);
+
+ reset_emoji_element (&data);
+ g_slist_free_full (data.prev_annotates, g_free);
+
+ return data.dict;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GHashTable *dict;
+ progname = basename (argv[0]);
+
+ if (argc < 3) {
+ usage ();
+ return -1;
+ }
+
+ dict = parse_html (argv[1]);
+ ibus_emoji_dict_save (argv[2], dict);
+ g_hash_table_destroy (dict);
+
+ return 0;
+}
diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
index 1b688b09..8efe5a92 100644
--- a/src/ibusenginesimple.c
+++ b/src/ibusenginesimple.c
@@ -3,7 +3,7 @@
/* ibus - The Input Bus
* Copyright (C) 2014 Peng Huang <shawn.p.huang@gmail.com>
* Copyright (C) 2015-2016 Takao Fujiwara <takao.fujiwara1@gmail.com>
- * Copyright (C) 2014 Red Hat, Inc.
+ * Copyright (C) 2014-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -31,6 +31,7 @@
#include "ibuskeys.h"
#include "ibuskeysyms.h"
+#include "ibusutil.h"
/* This file contains the table of the compose sequences,
* static const guint16 gtk_compose_seqs_compact[] = {}
@@ -42,16 +43,27 @@
#include <stdlib.h>
#define X11_DATADIR X11_DATA_PREFIX "/share/X11/locale"
+#define EMOJI_SOURCE_LEN 100
#define IBUS_ENGINE_SIMPLE_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_ENGINE_SIMPLE, IBusEngineSimplePrivate))
-struct _IBusEngineSimplePrivate {
- guint16 compose_buffer[IBUS_MAX_COMPOSE_LEN + 1];
- gunichar tentative_match;
- gint tentative_match_len;
+typedef struct {
+ GHashTable *dict;
+ int max_seq_len;
+} IBusEngineDict;
- guint in_hex_sequence : 1;
- guint modifiers_dropped : 1;
+struct _IBusEngineSimplePrivate {
+ guint16 compose_buffer[EMOJI_SOURCE_LEN];
+ gunichar tentative_match;
+ gchar *tentative_emoji;
+ gint tentative_match_len;
+
+ guint in_hex_sequence : 1;
+ guint in_emoji_sequence : 1;
+ guint modifiers_dropped : 1;
+ IBusEngineDict *emoji_dict;
+ IBusLookupTable *lookup_table;
+ gboolean lookup_table_visible;
};
/* From the values below, the value 30 means the number of different first keysyms
@@ -97,6 +109,8 @@ static gboolean ibus_engine_simple_process_key_event
guint modifiers);
static void ibus_engine_simple_commit_char (IBusEngineSimple *simple,
gunichar ch);
+static void ibus_engine_simple_commit_str (IBusEngineSimple *simple,
+ const gchar *str);
static void ibus_engine_simple_update_preedit_text
(IBusEngineSimple *simple);
@@ -128,6 +142,18 @@ ibus_engine_simple_init (IBusEngineSimple *simple)
static void
ibus_engine_simple_destroy (IBusEngineSimple *simple)
{
+ IBusEngineSimplePrivate *priv = simple->priv;
+
+ if (priv->emoji_dict) {
+ if (priv->emoji_dict->dict)
+ g_clear_pointer (&priv->emoji_dict->dict, g_hash_table_destroy);
+ g_slice_free (IBusEngineDict, priv->emoji_dict);
+ priv->emoji_dict = NULL;
+ }
+
+ g_clear_pointer (&priv->lookup_table, g_object_unref);
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+
IBUS_OBJECT_CLASS(ibus_engine_simple_parent_class)->destroy (
IBUS_OBJECT (simple));
}
@@ -146,6 +172,11 @@ ibus_engine_simple_reset (IBusEngine *engine)
priv->tentative_match_len = 0;
ibus_engine_hide_preedit_text ((IBusEngine *)simple);
}
+ if (priv->tentative_emoji || priv->in_emoji_sequence) {
+ priv->in_emoji_sequence = FALSE;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ ibus_engine_hide_preedit_text ((IBusEngine *)simple);
+ }
}
static void
@@ -162,23 +193,60 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple,
priv->tentative_match_len = 0;
ibus_engine_simple_update_preedit_text (simple);
}
+ if (priv->tentative_emoji || priv->in_emoji_sequence) {
+ priv->in_emoji_sequence = FALSE;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ ibus_engine_simple_update_preedit_text (simple);
+ }
ibus_engine_commit_text ((IBusEngine *)simple,
ibus_text_new_from_unichar (ch));
}
static void
+ibus_engine_simple_commit_str (IBusEngineSimple *simple,
+ const gchar *str)
+{
+ IBusEngineSimplePrivate *priv = simple->priv;
+ gchar *backup_str;
+
+ g_return_if_fail (str && *str);
+
+ backup_str = g_strdup (str);
+
+ if (priv->tentative_match || priv->in_hex_sequence) {
+ priv->in_hex_sequence = FALSE;
+ priv->tentative_match = 0;
+ priv->tentative_match_len = 0;
+ ibus_engine_simple_update_preedit_text (simple);
+ }
+ if (priv->tentative_emoji || priv->in_emoji_sequence) {
+ priv->in_emoji_sequence = FALSE;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ ibus_engine_simple_update_preedit_text (simple);
+ }
+
+ ibus_engine_commit_text ((IBusEngine *)simple,
+ ibus_text_new_from_string (backup_str));
+ g_free (backup_str);
+}
+
+static void
ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
{
IBusEngineSimplePrivate *priv = simple->priv;
- gunichar outbuf[IBUS_MAX_COMPOSE_LEN + 2];
+ gunichar outbuf[EMOJI_SOURCE_LEN + 1];
int len = 0;
- if (priv->in_hex_sequence) {
+ if (priv->in_hex_sequence || priv->in_emoji_sequence) {
int hexchars = 0;
- outbuf[0] = L'u';
+ if (priv->in_hex_sequence)
+ outbuf[0] = L'u';
+ else
+ outbuf[0] = L'@';
+
len = 1;
while (priv->compose_buffer[hexchars] != 0) {
@@ -187,10 +255,22 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
++len;
++hexchars;
}
- g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1);
+
+ if (priv->in_hex_sequence)
+ g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1);
+ else
+ g_assert (len <= EMOJI_SOURCE_LEN + 1);
}
- else if (priv->tentative_match)
+ else if (priv->tentative_match) {
outbuf[len++] = priv->tentative_match;
+ } else if (priv->tentative_emoji && *priv->tentative_emoji) {
+ IBusText *text = ibus_text_new_from_string (priv->tentative_emoji);
+ len = strlen (priv->tentative_emoji);
+ ibus_text_append_attribute (text,
+ IBUS_ATTR_TYPE_UNDERLINE, IBUS_ATTR_UNDERLINE_SINGLE, 0, len);
+ ibus_engine_update_preedit_text ((IBusEngine *)simple, text, len, TRUE);
+ return;
+ }
outbuf[len] = L'\0';
if (len == 0) {
@@ -277,6 +357,104 @@ check_hex (IBusEngineSimple *simple,
return TRUE;
}
+static IBusEngineDict *
+load_emoji_dict ()
+{
+ IBusEngineDict *emoji_dict;
+ GList *keys;
+ int max_length = 0;
+
+ emoji_dict = g_slice_new0 (IBusEngineDict);
+ emoji_dict->dict = ibus_emoji_dict_load (IBUS_DATA_DIR "/dicts/emoji.dict");
+ if (!emoji_dict->dict)
+ return emoji_dict;
+
+ keys = g_hash_table_get_keys (emoji_dict->dict);
+ for (; keys; keys = keys->next) {
+ int length = strlen (keys->data);
+ if (max_length < length)
+ max_length = length;
+ }
+ emoji_dict->max_seq_len = max_length;
+
+ return emoji_dict;
+}
+
+static gboolean
+check_emoji_table (IBusEngineSimple *simple,
+ gint n_compose,
+ gint index)
+{
+ IBusEngineSimplePrivate *priv = simple->priv;
+ IBusEngineDict *emoji_dict = priv->emoji_dict;
+ GString *str = NULL;
+ gint i;
+ gchar buf[7];
+ GSList *words = NULL;
+
+ g_assert (IBUS_IS_ENGINE_SIMPLE (simple));
+
+ if (priv->lookup_table == NULL) {
+ priv->lookup_table = ibus_lookup_table_new (10, 0, TRUE, TRUE);
+ g_object_ref_sink (priv->lookup_table);
+ }
+ if (emoji_dict == NULL)
+ emoji_dict = priv->emoji_dict = load_emoji_dict (simple);
+
+ if (emoji_dict == NULL || emoji_dict->dict == NULL)
+ return FALSE;
+
+ if (n_compose > emoji_dict->max_seq_len)
+ return FALSE;
+
+ str = g_string_new (NULL);
+ priv->lookup_table_visible = FALSE;
+
+ i = 0;
+ while (i < n_compose) {
+ gunichar ch;
+
+ ch = ibus_keyval_to_unicode (priv->compose_buffer[i]);
+
+ if (ch == 0)
+ return FALSE;
+
+ if (!g_unichar_isprint (ch))
+ return FALSE;
+
+ buf[g_unichar_to_utf8 (ch, buf)] = '\0';
+
+ g_string_append (str, buf);
+
+ ++i;
+ }
+
+ if (str->str) {
+ words = g_hash_table_lookup (emoji_dict->dict, str->str);
+ }
+ g_string_free (str, TRUE);
+
+ if (words != NULL) {
+ int i = 0;
+ ibus_lookup_table_clear (priv->lookup_table);
+ priv->lookup_table_visible = TRUE;
+
+ while (words) {
+ if (i == index) {
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ priv->tentative_emoji = g_strdup (words->data);
+ }
+ IBusText *text = ibus_text_new_from_string (words->data);
+ ibus_lookup_table_append_candidate (priv->lookup_table, text);
+ words = words->next;
+ i++;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static int
compare_seq_index (const void *key, const void *value)
{
@@ -626,10 +804,10 @@ ibus_check_algorithmically (const guint16 *compose_buffer,
static gboolean
no_sequence_matches (IBusEngineSimple *simple,
- gint n_compose,
- guint keyval,
- guint keycode,
- guint modifiers)
+ gint n_compose,
+ guint keyval,
+ guint keycode,
+ guint modifiers)
{
IBusEngineSimplePrivate *priv = simple->priv;
@@ -642,8 +820,7 @@ no_sequence_matches (IBusEngineSimple *simple,
gint len = priv->tentative_match_len;
int i;
- ibus_engine_simple_commit_char (simple,
- priv->tentative_match);
+ ibus_engine_simple_commit_char (simple, priv->tentative_match);
priv->compose_buffer[0] = 0;
for (i=0; i < n_compose - len - 1; i++) {
@@ -655,8 +832,11 @@ no_sequence_matches (IBusEngineSimple *simple,
return ibus_engine_simple_process_key_event (
(IBusEngine *)simple, keyval, keycode, modifiers);
- }
- else {
+ } else if (priv->tentative_emoji && *priv->tentative_emoji) {
+ ibus_engine_simple_commit_str (simple, priv->tentative_emoji);
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ priv->compose_buffer[0] = 0;
+ } else {
priv->compose_buffer[0] = 0;
if (n_compose > 1) {
/* Invalid sequence */
@@ -676,6 +856,7 @@ no_sequence_matches (IBusEngineSimple *simple,
else
return FALSE;
}
+ return FALSE;
}
static gboolean
@@ -687,6 +868,39 @@ is_hex_keyval (guint keyval)
}
static gboolean
+is_graph_keyval (guint keyval)
+{
+ gunichar ch = ibus_keyval_to_unicode (keyval);
+
+ return g_unichar_isgraph (ch);
+}
+
+static void
+ibus_engine_simple_update_lookup_and_aux_table (IBusEngineSimple *simple)
+{
+ IBusEngineSimplePrivate *priv;
+ guint index, candidates;
+ gchar *aux_label = NULL;
+ IBusText *text = NULL;
+
+ g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple));
+
+ priv = simple->priv;
+ index = ibus_lookup_table_get_cursor_pos (priv->lookup_table) + 1;
+ candidates = ibus_lookup_table_get_number_of_candidates(priv->lookup_table);
+ aux_label = g_strdup_printf ("(%u / %u)", index, candidates);
+ text = ibus_text_new_from_string (aux_label);
+ g_free (aux_label);
+
+ ibus_engine_update_auxiliary_text (IBUS_ENGINE (simple),
+ text,
+ priv->lookup_table_visible);
+ ibus_engine_update_lookup_table (IBUS_ENGINE (simple),
+ priv->lookup_table,
+ priv->lookup_table_visible);
+}
+
+static gboolean
ibus_engine_simple_process_key_event (IBusEngine *engine,
guint keyval,
guint keycode,
@@ -697,10 +911,13 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
gint n_compose = 0;
gboolean have_hex_mods;
gboolean is_hex_start;
+ gboolean is_emoji_start = FALSE;
gboolean is_hex_end;
+ gboolean is_space;
gboolean is_backspace;
gboolean is_escape;
guint hex_keyval;
+ guint printable_keyval;
gint i;
gboolean compose_finish;
gunichar output_char;
@@ -714,17 +931,16 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
keyval == IBUS_KEY_Shift_L || keyval == IBUS_KEY_Shift_R)) {
if (priv->tentative_match &&
g_unichar_validate (priv->tentative_match)) {
- ibus_engine_simple_commit_char (simple,
- priv->tentative_match);
- }
- else if (n_compose == 0) {
+ ibus_engine_simple_commit_char (simple, priv->tentative_match);
+ } else if (n_compose == 0) {
priv->modifiers_dropped = TRUE;
- }
- else {
+ } else {
/* invalid hex sequence */
/* FIXME beep_window (event->window); */
priv->tentative_match = 0;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
priv->in_hex_sequence = FALSE;
+ priv->in_emoji_sequence = FALSE;
priv->compose_buffer[0] = 0;
ibus_engine_simple_update_preedit_text (simple);
@@ -732,6 +948,26 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
return TRUE;
}
+ /* Handle Shift + Space */
+ else if (priv->in_emoji_sequence &&
+ (keyval == IBUS_KEY_Control_L || keyval == IBUS_KEY_Control_R)) {
+ if (priv->tentative_emoji && *priv->tentative_emoji) {
+ ibus_engine_simple_commit_str (simple, priv->tentative_emoji);
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ } else if (n_compose == 0) {
+ priv->modifiers_dropped = TRUE;
+ } else {
+ /* invalid hex sequence */
+ /* FIXME beep_window (event->window); */
+ priv->tentative_match = 0;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ priv->in_hex_sequence = FALSE;
+ priv->in_emoji_sequence = FALSE;
+ priv->compose_buffer[0] = 0;
+
+ ibus_engine_simple_update_preedit_text (simple);
+ }
+ }
else
return FALSE;
}
@@ -741,25 +977,33 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
if (keyval == ibus_compose_ignore[i])
return FALSE;
- 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
+ } else {
have_hex_mods = (modifiers & (HEX_MOD_MASK)) == HEX_MOD_MASK;
+ }
is_hex_start = keyval == IBUS_KEY_U;
+#ifdef ENABLE_EMOJI_DICT
+ is_emoji_start = keyval == IBUS_KEY_E;
+#endif
is_hex_end = (keyval == IBUS_KEY_space ||
keyval == IBUS_KEY_KP_Space ||
keyval == IBUS_KEY_Return ||
keyval == IBUS_KEY_ISO_Enter ||
keyval == IBUS_KEY_KP_Enter);
+ is_space = (keyval == IBUS_KEY_space || keyval == IBUS_KEY_KP_Space);
is_backspace = keyval == IBUS_KEY_BackSpace;
is_escape = keyval == IBUS_KEY_Escape;
hex_keyval = is_hex_keyval (keyval) ? keyval : 0;
+ printable_keyval = is_graph_keyval (keyval) ? keyval : 0;
/* gtkimcontextsimple causes a buffer overflow in priv->compose_buffer.
* Add the check code here.
*/
- if (n_compose >= IBUS_MAX_COMPOSE_LEN) {
+ if ((n_compose >= IBUS_MAX_COMPOSE_LEN && priv->in_hex_sequence) ||
+ (n_compose >= EMOJI_SOURCE_LEN && priv->in_emoji_sequence)) {
if (is_backspace) {
priv->compose_buffer[--n_compose] = 0;
}
@@ -767,7 +1011,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
/* invalid hex sequence */
// beep_window (event->window);
priv->tentative_match = 0;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
priv->in_hex_sequence = FALSE;
+ priv->in_emoji_sequence = FALSE;
priv->compose_buffer[0] = 0;
}
else if (is_escape) {
@@ -789,12 +1035,16 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
* 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 && !is_hex_start &&
+ !priv->in_emoji_sequence && !is_emoji_start) ||
(priv->in_hex_sequence && !hex_keyval &&
- !is_hex_start && !is_hex_end && !is_escape && !is_backspace)) {
+ !is_hex_start && !is_hex_end && !is_escape && !is_backspace) ||
+ (priv->in_emoji_sequence && !printable_keyval &&
+ !is_emoji_start && !is_hex_end && !is_escape && !is_backspace)) {
if (modifiers & (IBUS_MOD1_MASK | IBUS_CONTROL_MASK) ||
- (priv->in_hex_sequence && priv->modifiers_dropped &&
+ ((priv->in_hex_sequence || priv->in_emoji_sequence) &&
+ priv->modifiers_dropped &&
(keyval == IBUS_KEY_Return ||
keyval == IBUS_KEY_ISO_Enter ||
keyval == IBUS_KEY_KP_Enter))) {
@@ -816,6 +1066,20 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
return TRUE;
}
+ if (priv->in_emoji_sequence && have_hex_mods && is_backspace) {
+ if (n_compose > 0) {
+ n_compose--;
+ priv->compose_buffer[n_compose] = 0;
+ check_emoji_table (simple, n_compose, -1);
+ ibus_engine_simple_update_lookup_and_aux_table (simple);
+ } else {
+ priv->in_emoji_sequence = FALSE;
+ }
+
+ ibus_engine_simple_update_preedit_text (simple);
+
+ return TRUE;
+ }
/* Check for hex sequence restart */
if (priv->in_hex_sequence && have_hex_mods && is_hex_start) {
@@ -833,13 +1097,41 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
}
}
}
+ if (priv->in_emoji_sequence && have_hex_mods && is_emoji_start) {
+ if (priv->tentative_emoji && *priv->tentative_emoji) {
+ ibus_engine_simple_commit_str (simple, priv->tentative_emoji);
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ }
+ else {
+ if (n_compose > 0) {
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ priv->in_emoji_sequence = FALSE;
+ priv->compose_buffer[0] = 0;
+ }
+ }
+ }
/* Check for hex sequence start */
if (!priv->in_hex_sequence && have_hex_mods && is_hex_start) {
priv->compose_buffer[0] = 0;
priv->in_hex_sequence = TRUE;
+ priv->in_emoji_sequence = FALSE;
priv->modifiers_dropped = FALSE;
priv->tentative_match = 0;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+
+ // g_debug ("Start HEX MODE");
+
+ ibus_engine_simple_update_preedit_text (simple);
+
+ return TRUE;
+ } else if (!priv->in_emoji_sequence && have_hex_mods && is_emoji_start) {
+ priv->compose_buffer[0] = 0;
+ priv->in_hex_sequence = FALSE;
+ priv->in_emoji_sequence = TRUE;
+ priv->modifiers_dropped = FALSE;
+ priv->tentative_match = 0;
+ g_clear_pointer (&priv->tentative_emoji, g_free);
// g_debug ("Start HEX MODE");
@@ -864,9 +1156,20 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
// beep_window (event->window);
return TRUE;
}
- }
- else
+ } else if (priv->in_emoji_sequence) {
+ if (printable_keyval) {
+ priv->compose_buffer[n_compose++] = printable_keyval;
+ }
+ else if (is_space && (modifiers & IBUS_SHIFT_MASK)) {
+ priv->compose_buffer[n_compose++] = IBUS_KEY_space;
+ }
+ else if (is_escape) {
+ ibus_engine_simple_reset (engine);
+ return TRUE;
+ }
+ } else {
priv->compose_buffer[n_compose++] = keyval;
+ }
priv->compose_buffer[n_compose] = 0;
@@ -880,8 +1183,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
ibus_engine_simple_commit_char (simple,
priv->tentative_match);
priv->compose_buffer[0] = 0;
- }
- else {
+ } else {
// FIXME
/* invalid hex sequence */
// beep_window (event->window);
@@ -899,6 +1201,73 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
return TRUE;
}
}
+ else if (priv->in_emoji_sequence) {
+ if (have_hex_mods && n_compose > 0) {
+ gboolean update_lookup_table = FALSE;
+
+ if (priv->lookup_table_visible) {
+ switch (keyval) {
+ case IBUS_KEY_space:
+ case IBUS_KEY_KP_Space:
+ if ((modifiers & IBUS_SHIFT_MASK) == 0) {
+ ibus_lookup_table_cursor_down (priv->lookup_table);
+ update_lookup_table = TRUE;
+ }
+ break;
+ case IBUS_KEY_Down:
+ ibus_lookup_table_cursor_down (priv->lookup_table);
+ update_lookup_table = TRUE;
+ break;
+ case IBUS_KEY_Up:
+ ibus_lookup_table_cursor_up (priv->lookup_table);
+ update_lookup_table = TRUE;
+ break;
+ case IBUS_KEY_Page_Down:
+ ibus_lookup_table_page_down (priv->lookup_table);
+ update_lookup_table = TRUE;
+ break;
+ case IBUS_KEY_Page_Up:
+ ibus_lookup_table_page_up (priv->lookup_table);
+ update_lookup_table = TRUE;
+ break;
+ default:;
+ }
+ }
+
+ if (!update_lookup_table) {
+ if (is_hex_end && !is_space) {
+ if (priv->lookup_table) {
+ int index = (int) ibus_lookup_table_get_cursor_pos (
+ priv->lookup_table);
+ check_emoji_table (simple, n_compose, index);
+ priv->lookup_table_visible = FALSE;
+ update_lookup_table = TRUE;
+ }
+ }
+ else if (check_emoji_table (simple, n_compose, -1)) {
+ update_lookup_table = TRUE;
+ }
+ }
+
+ if (update_lookup_table)
+ ibus_engine_simple_update_lookup_and_aux_table (simple);
+ if (is_hex_end && !is_space) {
+ if (priv->tentative_emoji && *priv->tentative_emoji) {
+ ibus_engine_simple_commit_str (simple,
+ priv->tentative_emoji);
+ priv->compose_buffer[0] = 0;
+ } else {
+ g_clear_pointer (&priv->tentative_emoji, g_free);
+ priv->in_emoji_sequence = FALSE;
+ priv->compose_buffer[0] = 0;
+ }
+ }
+
+ ibus_engine_simple_update_preedit_text (simple);
+
+ return TRUE;
+ }
+ }
else {
GSList *list = global_tables;
while (list) {
diff --git a/src/ibusutil.c b/src/ibusutil.c
index b9f3fdde..bfaa4f4b 100644
--- a/src/ibusutil.c
+++ b/src/ibusutil.c
@@ -2,8 +2,8 @@
/* vim:set et sts=4: */
/* bus - The Input Bus
* Copyright (C) 2008-2015 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2010-2015 Takao Fujiwara <takao.fujiwara1@gmail.com>
- * Copyright (C) 2008-2015 Red Hat, Inc.
+ * Copyright (C) 2010-2016 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -34,6 +34,9 @@
#include <libintl.h>
#endif
+#define IBUS_DICT_MAGIC "IBusDict"
+#define IBUS_DICT_VERSION (1)
+
/* gettext macro */
#define N_(t) t
@@ -125,6 +128,74 @@ _load_lang()
ibus_xml_free (node);
}
+static void
+free_dict_words (gpointer list)
+{
+ g_slist_free_full (list, g_free);
+}
+
+static void
+variant_foreach_add_emoji (gchar *annotation,
+ GSList *emojis,
+ GVariantBuilder *builder)
+{
+ int i;
+ int length = (int) g_slist_length (emojis);
+ gchar **buff = g_new0 (gchar *, length);
+ GSList *l = emojis;
+
+ for (i = 0; i < length; i++, l = l->next)
+ buff[i] = (gchar *) l->data;
+
+ g_variant_builder_add (builder,
+ "{sv}",
+ annotation,
+ g_variant_new_strv ((const gchar * const *) buff,
+ length));
+ g_free (buff);
+}
+
+static GVariant *
+ibus_emoji_dict_serialize (GHashTable *dict)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_hash_table_foreach (dict, (GHFunc) variant_foreach_add_emoji, &builder);
+ return g_variant_builder_end (&builder);
+}
+
+static GHashTable *
+ibus_emoji_dict_deserialize (GVariant *variant)
+{
+ GHashTable *dict = NULL;
+ GVariantIter iter;
+ gchar *annotate = NULL;
+ GVariant *emojis_variant = NULL;
+
+ dict = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ free_dict_words);
+
+ g_variant_iter_init (&iter, variant);
+ while (g_variant_iter_loop (&iter, "{sv}", &annotate, &emojis_variant)) {
+ gsize i;
+ gsize length = 0;
+ const gchar **array = g_variant_get_strv (emojis_variant, &length);
+ GSList *emojis = NULL;
+
+ for (i = 0; i < length; i++) {
+ emojis = g_slist_append (emojis, g_strdup (array[i]));
+ }
+ g_hash_table_insert (dict, annotate, emojis);
+ annotate = NULL;
+ g_clear_pointer (&emojis_variant, g_variant_unref);
+ }
+
+ return dict;
+}
+
const gchar *
ibus_get_untranslated_language_name (const gchar *_locale)
{
@@ -171,3 +242,121 @@ ibus_g_variant_get_child_string (GVariant *variant, gsize index, char **str)
g_free (*str);
g_variant_get_child (variant, index, "s", str);
}
+
+void
+ibus_emoji_dict_save (const gchar *path, GHashTable *dict)
+{
+ GVariant *variant;
+ const gchar *header = IBUS_DICT_MAGIC;
+ const guint16 version = IBUS_DICT_VERSION;
+ const gchar *contents;
+ gsize length;
+ GError *error = NULL;
+
+ variant = g_variant_new ("(sqv)",
+ header,
+ version,
+ ibus_emoji_dict_serialize (dict));
+
+ contents = g_variant_get_data (variant);
+ length = g_variant_get_size (variant);
+
+ if (!g_file_set_contents (path, contents, length, &error)) {
+ g_warning ("Failed to save emoji dict %s: %s", path, error->message);
+ g_error_free (error);
+ }
+
+ g_variant_unref (variant);
+}
+
+GHashTable *
+ibus_emoji_dict_load (const gchar *path)
+{
+ gchar *contents = NULL;
+ gsize length = 0;
+ GError *error = NULL;
+ GVariant *variant_table = NULL;
+ GVariant *variant = NULL;
+ const gchar *header = NULL;
+ guint16 version = 0;
+ GHashTable *retval = NULL;
+
+ if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+ g_warning ("Emoji dict does not exist: %s", path);
+ goto out_load_cache;
+ }
+
+ if (!g_file_get_contents (path, &contents, &length, &error)) {
+ g_warning ("Failed to get dict content %s: %s", path, error->message);
+ g_error_free (error);
+ goto out_load_cache;
+ }
+
+ variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
+ contents,
+ length,
+ FALSE,
+ NULL,
+ NULL);
+
+ if (variant_table == NULL) {
+ g_warning ("cache table is broken.");
+ goto out_load_cache;
+ }
+
+ g_variant_get (variant_table, "(&sq)", &header, &version);
+
+ if (g_strcmp0 (header, IBUS_DICT_MAGIC) != 0) {
+ g_warning ("cache is not IBusDict.");
+ goto out_load_cache;
+ }
+
+ if (version != IBUS_DICT_VERSION) {
+ g_warning ("cache version is different: %u != %u",
+ version, IBUS_DICT_VERSION);
+ goto out_load_cache;
+ }
+
+ version = 0;
+ header = NULL;
+ g_variant_unref (variant_table);
+
+ variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
+ contents,
+ length,
+ FALSE,
+ NULL,
+ NULL);
+
+ if (variant_table == NULL) {
+ g_warning ("cache table is broken.");
+ goto out_load_cache;
+ }
+
+ g_variant_get (variant_table, "(&sqv)",
+ NULL,
+ NULL,
+ &variant);
+
+ if (variant == NULL) {
+ g_warning ("cache dict is broken.");
+ goto out_load_cache;
+ }
+
+ retval = ibus_emoji_dict_deserialize (variant);
+
+out_load_cache:
+ if (variant)
+ g_variant_unref (variant);
+ if (variant_table)
+ g_variant_unref (variant_table);
+
+ return retval;
+}
+
+GSList *
+ibus_emoji_dict_lookup (GHashTable *dict,
+ const gchar *annotation)
+{
+ return (GSList *) g_hash_table_lookup (dict, annotation);
+}
diff --git a/src/ibusutil.h b/src/ibusutil.h
index 2c1360c7..e619b678 100644
--- a/src/ibusutil.h
+++ b/src/ibusutil.h
@@ -2,8 +2,8 @@
/* vim:set et sts=4: */
/* bus - The Input Bus
* Copyright (C) 2008-2015 Peng Huang <shawn.p.huang@gmail.com>
- * Copyright (C) 2010-2015 Takao Fujiwara <takao.fujiwara1@gmail.com>
- * Copyright (C) 2008-2015 Red Hat, Inc.
+ * Copyright (C) 2010-2016 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ * Copyright (C) 2008-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -53,4 +53,31 @@ const gchar * ibus_get_untranslated_language_name
*/
const gchar * ibus_get_language_name (const gchar *_locale);
+/**
+ * ibus_emoji_dict_save:
+ * @path: A path of the saved dictionary file.
+ * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary
+ *
+ * Save the Emoji dictionary to the cache file.
+ */
+void ibus_emoji_dict_save (const gchar *path,
+ GHashTable *dict);
+/**
+ * ibus_emoji_dict_load:
+ * @path: A path of the saved dictionary file.
+ *
+ * Returns: (element-type utf8 gpointer) (transfer none): An Emoji dictionary file loaded from the saved cache file.
+ */
+GHashTable * ibus_emoji_dict_load (const gchar *path);
+
+/**
+ * ibus_emoji_dict_lookup:
+ * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary
+ * @annotation: Annotation for Emoji characters
+ *
+ * Returns: (element-type utf8) (transfer none): List of Emoji characters
+ * This API is for gobject-introspection.
+ */
+GSList * ibus_emoji_dict_lookup (GHashTable *dict,
+ const gchar *annotation);
#endif