Blob Blame History Raw
From 7e477d5e0ffe19b6c52558c5b37fdd9cb82c097a Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 9 Mar 2017 11:31:21 +0900
Subject: [PATCH] tools: Fix `ibus emoji` SEGV when language is changed.

BUG=rhbz#1430290

Review URL: https://codereview.appspot.com/317410043
---
 tools/main.vala | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tools/main.vala b/tools/main.vala
index 73c6f57..fd9fd0e 100644
--- a/tools/main.vala
+++ b/tools/main.vala
@@ -353,6 +353,9 @@ int emoji_dialog(string[] argv) {
     } else {
         GLib.MainLoop loop = new GLib.MainLoop();
         emojier.loaded_emoji_dict.connect(() => {
+            // The signal is called when the language is changed.
+            if (emojier.is_running())
+                return;
             run_dialog(emojier);
             loop.quit();
         });
-- 
2.7.4

From 9dbea347050ae2ad79d1b53f2ad62a7a2cafadc6 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 9 Mar 2017 12:45:20 +0900
Subject: [PATCH] ui/gtk3: Get emoji colors from the theme

Get selected and normal text color from the theme.
Implement to activate an emoji with mouse motion.
Create back button on emoji language chooser dialog.
Set row_homogeneous on emoji table.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/319470043
---
 ui/gtk3/Makefile.am        |   3 +
 ui/gtk3/candidatearea.vala | 186 +++++++++++++++++++++++++------------------
 ui/gtk3/emojier.vala       | 192 ++++++++++++++++++++++++++++++++-------------
 3 files changed, 251 insertions(+), 130 deletions(-)

diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index d5ddc42..4e7fd1b 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -172,8 +172,11 @@ libibus_emoji_dialog_1_0_la_LDFLAGS =   \
     -version-info @LT_VERSION_INFO@     \
     $(NULL)
 libibus_emoji_dialog_1_0_la_SOURCES =   \
+    candidatearea.c                     \
     emojier.c                           \
     iconwidget.c                        \
+    pango.c                             \
+    separator.c                         \
     $(NULL)
 
 -include $(INTROSPECTION_MAKEFILE)
diff --git a/ui/gtk3/candidatearea.vala b/ui/gtk3/candidatearea.vala
index a095e76..e162a96 100644
--- a/ui/gtk3/candidatearea.vala
+++ b/ui/gtk3/candidatearea.vala
@@ -21,6 +21,108 @@
  * USA
  */
 
+class ThemedRGBA {
+    public Gdk.RGBA *normal_fg { get; set; }
+    public Gdk.RGBA *normal_bg { get; set; }
+    public Gdk.RGBA *selected_fg { get; set; }
+    public Gdk.RGBA *selected_bg { get; set; }
+
+    private Gtk.StyleContext m_style_context;
+
+    public ThemedRGBA(Gtk.Widget widget) {
+        this.normal_fg = null;
+        this.normal_bg = null;
+        this.selected_fg = null;
+        this.selected_bg = null;
+
+        /* Use the color of Gtk.TextView instead of Gtk.Label
+         * because the selected label "color" is not configured
+         * in "Adwaita" theme and the selected label "background-color"
+         * is not configured in "Maia" theme.
+         * https://github.com/ibus/ibus/issues/1871
+         */
+        Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
+        widget_path.append_type(typeof(Gtk.TextView));
+        m_style_context = new Gtk.StyleContext();
+        m_style_context.set_path(widget_path);
+        m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
+
+        /* "-gtk-secondary-caret-color" value is different
+         * if the parent widget is set in "Menta" theme.
+         */
+        m_style_context.set_parent(widget.get_style_context());
+
+        get_rgba();
+
+        m_style_context.changed.connect(() => { get_rgba(); });
+    }
+
+    ~ThemedRGBA() {
+        reset_rgba();
+    }
+
+    private void reset_rgba() {
+        if (this.normal_fg != null) {
+            this.normal_fg.free();
+            this.normal_fg = null;
+        }
+        if (this.normal_bg != null) {
+            this.normal_bg.free();
+            this.normal_bg = null;
+        }
+        if (this.selected_fg != null) {
+            this.selected_fg.free();
+            this.selected_fg = null;
+        }
+        if (this.selected_bg != null) {
+            this.selected_bg.free();
+            this.selected_bg = null;
+        }
+    }
+
+    private void get_rgba() {
+        reset_rgba();
+        Gdk.RGBA *normal_fg = null;
+        Gdk.RGBA *normal_bg = null;
+        Gdk.RGBA *selected_fg = null;
+        Gdk.RGBA *selected_bg = null;
+        m_style_context.get(Gtk.StateFlags.NORMAL,
+                            "color",
+                            out normal_fg);
+        m_style_context.get(Gtk.StateFlags.SELECTED,
+                            "color",
+                            out selected_fg);
+
+        string bg_prop = "background-color";
+        m_style_context.get(Gtk.StateFlags.NORMAL,
+                            bg_prop,
+                            out normal_bg);
+        m_style_context.get(Gtk.StateFlags.SELECTED,
+                            bg_prop,
+                            out selected_bg);
+        if (normal_bg.red   == selected_bg.red &&
+            normal_bg.green == selected_bg.green &&
+            normal_bg.blue  == selected_bg.blue &&
+            normal_bg.alpha == selected_bg.alpha) {
+            normal_bg.free();
+            normal_bg = null;
+            normal_bg.free();
+            normal_bg = null;
+            bg_prop = "-gtk-secondary-caret-color";
+            m_style_context.get(Gtk.StateFlags.NORMAL,
+                                bg_prop,
+                                out normal_bg);
+            m_style_context.get(Gtk.StateFlags.SELECTED,
+                                bg_prop,
+                                out selected_bg);
+        }
+        this.normal_fg   = normal_fg;
+        this.normal_bg   = normal_bg;
+        this.selected_fg = selected_fg;
+        this.selected_bg = selected_bg;
+    }
+}
+
 class CandidateArea : Gtk.Box {
     private bool m_vertical;
     private Gtk.Label[] m_labels;
@@ -30,9 +132,7 @@ class CandidateArea : Gtk.Box {
     private IBus.Text[] m_ibus_candidates;
     private uint m_focus_candidate;
     private bool m_show_cursor;
-    Gtk.StyleContext m_style_context;
-    private Gdk.RGBA *m_selected_fg_color = null;
-    private Gdk.RGBA *m_selected_bg_color = null;
+    private ThemedRGBA m_rgba;
 
     private const string LABELS[] = {
         "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.",
@@ -58,38 +158,7 @@ class CandidateArea : Gtk.Box {
     public CandidateArea(bool vertical) {
         GLib.Object();
         set_vertical(vertical, true);
-
-        /* Use the color of Gtk.TextView instead of Gtk.Label
-         * because the selected label "color" is not configured
-         * in "Adwaita" theme and the selected label "background-color"
-         * is not configured in "Maia" theme.
-         * https://github.com/ibus/ibus/issues/1871
-         */
-        Gtk.WidgetPath widget_path = new Gtk.WidgetPath();
-        widget_path.append_type(typeof(Gtk.TextView));
-        m_style_context = new Gtk.StyleContext();
-        m_style_context.set_path(widget_path);
-        m_style_context.add_class(Gtk.STYLE_CLASS_VIEW);
-
-        /* "-gtk-secondary-caret-color" value is different
-         * if the parent widget is set in "Menta" theme.
-         */
-        m_style_context.set_parent(get_style_context());
-
-        get_selected_color();
-
-        m_style_context.changed.connect(() => { get_selected_color(); });
-    }
-
-    ~CandidateArea() {
-        if (m_selected_bg_color != null) {
-            m_selected_bg_color.free();
-            m_selected_bg_color = null;
-        }
-        if (m_selected_bg_color != null) {
-            m_selected_bg_color.free();
-            m_selected_bg_color = null;
-        }
+        m_rgba = new ThemedRGBA(this);
     }
 
     public bool candidate_scrolled(Gdk.EventScroll event) {
@@ -150,17 +219,17 @@ class CandidateArea : Gtk.Box {
                 Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(candidates[i]);
                 if (i == focus_candidate && show_cursor) {
                     Pango.Attribute pango_attr = Pango.attr_foreground_new(
-                            (uint16)(m_selected_fg_color.red * uint16.MAX),
-                            (uint16)(m_selected_fg_color.green * uint16.MAX),
-                            (uint16)(m_selected_fg_color.blue * uint16.MAX));
+                            (uint16)(m_rgba.selected_fg.red * uint16.MAX),
+                            (uint16)(m_rgba.selected_fg.green * uint16.MAX),
+                            (uint16)(m_rgba.selected_fg.blue * uint16.MAX));
                     pango_attr.start_index = 0;
                     pango_attr.end_index = candidates[i].get_text().length;
                     attrs.insert((owned)pango_attr);
 
                     pango_attr = Pango.attr_background_new(
-                            (uint16)(m_selected_bg_color.red * uint16.MAX),
-                            (uint16)(m_selected_bg_color.green * uint16.MAX),
-                            (uint16)(m_selected_bg_color.blue * uint16.MAX));
+                           (uint16)(m_rgba.selected_bg.red * uint16.MAX),
+                           (uint16)(m_rgba.selected_bg.green * uint16.MAX),
+                           (uint16)(m_rgba.selected_bg.blue * uint16.MAX));
                     pango_attr.start_index = 0;
                     pango_attr.end_index = candidates[i].get_text().length;
                     attrs.insert((owned)pango_attr);
@@ -181,41 +250,6 @@ class CandidateArea : Gtk.Box {
         }
     }
 
-    private void get_selected_color() {
-        if (m_selected_fg_color != null) {
-            m_selected_fg_color.free();
-            m_selected_fg_color = null;
-        }
-        m_style_context.get(Gtk.StateFlags.SELECTED,
-                            "color",
-                            out m_selected_fg_color);
-
-        string bg_prop = "background-color";
-        Gdk.RGBA *normal_color = null;
-        if (m_selected_bg_color != null) {
-            m_selected_bg_color.free();
-            m_selected_bg_color = null;
-        }
-        m_style_context.get(Gtk.StateFlags.NORMAL,
-                            bg_prop,
-                            out normal_color);
-        m_style_context.get(Gtk.StateFlags.SELECTED,
-                            bg_prop,
-                            out m_selected_bg_color);
-        if (normal_color.red   == m_selected_bg_color.red &&
-            normal_color.green == m_selected_bg_color.green &&
-            normal_color.blue  == m_selected_bg_color.blue &&
-            normal_color.alpha == m_selected_bg_color.alpha) {
-            m_selected_bg_color.free();
-            m_selected_bg_color = null;
-            bg_prop = "-gtk-secondary-caret-color";
-            m_style_context.get(Gtk.StateFlags.SELECTED,
-                                bg_prop,
-                                out m_selected_bg_color);
-        }
-        normal_color.free();
-    }
-
     private void recreate_ui() {
         foreach (Gtk.Widget w in get_children()) {
             w.destroy();
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 5496c4e..bc1eff4 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -72,12 +72,31 @@ class IBusEmojier : Gtk.Window {
     private class EGrid : Gtk.Grid {
         public EGrid() {
             GLib.Object(
+                row_homogeneous : false,
                 vexpand : true,
                 halign : Gtk.Align.FILL,
                 valign : Gtk.Align.FILL
             );
         }
     }
+    private class EWhiteLabel : Gtk.Label {
+        public EWhiteLabel(string text) {
+            GLib.Object(
+                name : "IBusEmojierWhiteLabel"
+            );
+            if (text != "")
+                set_label(text);
+        }
+    }
+    private class ESelectedLabel : Gtk.Label {
+        public ESelectedLabel(string text) {
+            GLib.Object(
+                name : "IBusEmojierSelectedLabel"
+            );
+            if (text != "")
+                set_label(text);
+        }
+    }
     private class EPaddedLabel : Gtk.Box {
         public EPaddedLabel(string          text,
                             Gtk.Align       align,
@@ -161,6 +180,7 @@ class IBusEmojier : Gtk.Window {
     }
 
     private const uint EMOJI_GRID_PAGE = 10;
+    private ThemedRGBA m_rgba;
     private Gtk.Box m_vbox;
     private ETitleLabel m_title;
     private EEntry m_entry;
@@ -174,7 +194,8 @@ class IBusEmojier : Gtk.Window {
     private GLib.MainLoop? m_loop;
     private string? m_result;
     private GLib.SList<string> m_lang_list;
-    private string m_current_lang = "en";
+    private string m_current_lang_id = "en";
+    private string m_current_language = "English";
     private string? m_unicode_point = null;
     private bool m_candidate_panel_is_visible;
     private GLib.HashTable<string, GLib.SList>?
@@ -189,11 +210,8 @@ class IBusEmojier : Gtk.Window {
     private Gtk.Label[] m_candidates;
     private string m_emoji_font = "Monospace 16";
     private string[] m_favorites = {};
-    // TODO: Get the selected color from CandidateArea
-    private Gdk.RGBA m_selected_fg_color = Gdk.RGBA(){
-            red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0 };
-    private Gdk.RGBA m_selected_bg_color = Gdk.RGBA(){
-            red = 0.300, green = 0.565, blue = 0.851, alpha = 1.0 };
+    private bool m_enter_notify_enable = true;
+    private uint m_entry_notify_show_id;
 
     public signal void candidate_clicked(uint index, uint button, uint state);
     public signal void loaded_emoji_dict();
@@ -220,7 +238,33 @@ class IBusEmojier : Gtk.Window {
             warning("Could not open display.");
             return;
         }
-        string data = "grid { background-color: #ffffff; }";
+        m_rgba = new ThemedRGBA(this);
+        uint bg_red = (uint)(m_rgba.normal_bg.red * 255);
+        uint bg_green = (uint)(m_rgba.normal_bg.green * 255);
+        uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255);
+        double bg_alpha = m_rgba.normal_bg.alpha;
+        string data =
+                "#IBusEmojierWhiteLabel { background-color: " +
+                        "rgba(%u, %u, %u, %lf); ".printf(
+                        bg_red, bg_green, bg_blue, bg_alpha) +
+                "border-width: 4px; border-radius: 3px; } ";
+
+        uint fg_red = (uint)(m_rgba.selected_fg.red * 255);
+        uint fg_green = (uint)(m_rgba.selected_fg.green * 255);
+        uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255);
+        double fg_alpha = m_rgba.selected_fg.alpha;
+        bg_red = (uint)(m_rgba.selected_bg.red * 255);
+        bg_green = (uint)(m_rgba.selected_bg.green * 255);
+        bg_blue = (uint)(m_rgba.selected_bg.blue * 255);
+        bg_alpha = m_rgba.selected_bg.alpha;
+        data += "#IBusEmojierSelectedLabel { color: " +
+                        "rgba(%u, %u, %u, %lf); ".printf(
+                        fg_red, fg_green, fg_blue, fg_alpha) +
+                "background-color: " +
+                        "rgba(%u, %u, %u, %lf); ".printf(
+                        bg_red, bg_green, bg_blue, bg_alpha) +
+                "border-width: 4px; border-radius: 3px; }";
+
         Gtk.CssProvider css_provider = new Gtk.CssProvider();
         try {
             css_provider.load_from_data(data, -1);
@@ -317,6 +361,8 @@ class IBusEmojier : Gtk.Window {
         lang_list.sort((a, b) => {
             string a_lang = IBus.get_language_name(a);
             string b_lang = IBus.get_language_name(b);
+            a_lang = "%s (%s)".printf(a_lang, a);
+            b_lang = "%s (%s)".printf(b_lang, b);
             return GLib.strcmp(a_lang, b_lang);
         });
         return lang_list;
@@ -325,8 +371,8 @@ class IBusEmojier : Gtk.Window {
     private void reload_emoji_dict() {
         init_emoji_dict();
         make_emoji_dict("en");
-        if (m_current_lang != "en")
-            make_emoji_dict(m_current_lang);
+        if (m_current_lang_id != "en")
+            make_emoji_dict(m_current_lang_id);
         loaded_emoji_dict();
     }
 
@@ -458,22 +504,50 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+    private void activated_language(EBoxRow row) {
+        m_category_active_index = 0;
+        if (m_current_lang_id != row.id) {
+            m_current_lang_id = row.id;
+            m_current_language = row.text;
+            reload_emoji_dict();
+        }
+        m_current_category_type = CategoryType.EMOJI;
+        show_category_list();
+    }
+
     private void show_category_list() {
         remove_all_children();
         m_scrolled_window = new EScrolledWindow();
         set_fixed_size();
-        string language = IBus.get_language_name(m_current_lang);
-        EPaddedLabel label = new EPaddedLabel(language, Gtk.Align.CENTER);
+        EPaddedLabel label;
+        if (m_current_category_type == CategoryType.EMOJI) {
+            label = new EPaddedLabel(m_current_language, Gtk.Align.CENTER);
+        } else if (m_current_category_type == CategoryType.LANG) {
+            label = new EPaddedLabel(m_current_language,
+                                     Gtk.Align.CENTER,
+                                     TravelDirection.BACKWARD);
+        } else {
+            label = new EPaddedLabel("", Gtk.Align.CENTER);
+        }
         Gtk.Button button = new Gtk.Button();
         button.add(label);
         m_vbox.add(button);
         button.show_all();
-        button.button_press_event.connect((e) => {
-            m_category_active_index = 0;
-            m_current_category_type = CategoryType.LANG;
-            show_category_list();
-            return true;
-        });
+        if (m_current_category_type == CategoryType.EMOJI) {
+            button.button_press_event.connect((e) => {
+                m_category_active_index = 0;
+                m_current_category_type = CategoryType.LANG;
+                show_category_list();
+                return true;
+            });
+        } else if (m_current_category_type == CategoryType.LANG) {
+            button.button_press_event.connect((e) => {
+                m_category_active_index = 0;
+                m_current_category_type = CategoryType.EMOJI;
+                show_category_list();
+                return true;
+            });
+        }
 
         m_vbox.add(m_scrolled_window);
         Gtk.Viewport viewport = new Gtk.Viewport(null, null);
@@ -523,21 +597,19 @@ class IBusEmojier : Gtk.Window {
             }
         } else if (m_current_category_type == CategoryType.LANG) {
             m_list_box.row_activated.connect((box, gtkrow) => {
-                m_category_active_index = 0;
-                EBoxRow row = gtkrow as EBoxRow;
-                if (m_current_lang != row.id) {
-                    m_current_lang = row.id;
-                    reload_emoji_dict();
-                }
-                m_current_category_type = CategoryType.EMOJI;
-                show_category_list();
+                activated_language(gtkrow as EBoxRow);
             });
             uint n = 1;
+            string prev_language = null;
             foreach (unowned string id in m_lang_list) {
-                string selected_language = IBus.get_language_name(id);
-                EBoxRow row = new EBoxRow("", id);
+                string language = IBus.get_language_name(id);
+                if (prev_language == language)
+                    language = "%s (%s)".printf(language, id);
+                else
+                    prev_language = language;
+                EBoxRow row = new EBoxRow(language, id);
                 EPaddedLabel widget =
-                        new EPaddedLabel(selected_language, Gtk.Align.CENTER);
+                        new EPaddedLabel(language, Gtk.Align.CENTER);
                 row.add(widget);
                 m_list_box.add(row);
                 if (n++ == m_category_active_index)
@@ -573,27 +645,6 @@ class IBusEmojier : Gtk.Window {
         show_candidate_panel();
     }
 
-    private void label_set_active_color(Gtk.Label label) {
-        unowned string text = label.get_text();
-        Pango.AttrList attrs = new Pango.AttrList();
-        Pango.Attribute pango_attr = Pango.attr_foreground_new(
-                (uint16)(m_selected_fg_color.red * uint16.MAX),
-                (uint16)(m_selected_fg_color.green * uint16.MAX),
-                (uint16)(m_selected_fg_color.blue * uint16.MAX));
-        pango_attr.start_index = 0;
-        pango_attr.end_index = text.char_count();
-        attrs.insert((owned)pango_attr);
-
-        pango_attr = Pango.attr_background_new(
-                (uint16)(m_selected_bg_color.red * uint16.MAX),
-                (uint16)(m_selected_bg_color.green * uint16.MAX),
-                (uint16)(m_selected_bg_color.blue * uint16.MAX));
-        pango_attr.start_index = 0;
-        pango_attr.end_index = text.char_count();
-        attrs.insert((owned)pango_attr);
-        label.set_attributes(attrs);
-    }
-
     private void show_arrow_buttons() {
         Gtk.Button next_button = new Gtk.Button();
         next_button.clicked.connect(() => {
@@ -709,10 +760,17 @@ class IBusEmojier : Gtk.Window {
             });
         }
         EGrid grid = new EGrid();
+        grid.set_row_spacing(5);
+        grid.set_column_spacing(5);
+        grid.set_border_width(2);
         int n = 0;
         for (uint i = page_start_pos; i < page_end_pos; i++) {
             IBus.Text candidate = m_lookup_table.get_candidate(i);
-            Gtk.Label label = new Gtk.Label(candidate.text);
+            Gtk.Label label;
+            if (i == cursor)
+                label = new ESelectedLabel(candidate.text) as Gtk.Label;
+            else
+                label = new EWhiteLabel(candidate.text) as Gtk.Label;
             string emoji_font = m_emoji_font;
             if (candidate.text.char_count() > 2) {
                 Pango.FontDescription font_desc =
@@ -726,9 +784,6 @@ class IBusEmojier : Gtk.Window {
             label.set_markup(markup);
             label.set_halign(Gtk.Align.FILL);
             label.set_valign(Gtk.Align.FILL);
-            if (i == cursor) {
-                label_set_active_color(label);
-            }
             Gtk.EventBox candidate_ebox = new Gtk.EventBox();
             candidate_ebox.add(label);
             // Make a copy of i to workaround a bug in vala.
@@ -738,6 +793,23 @@ class IBusEmojier : Gtk.Window {
                 candidate_clicked(index, e.button, e.state);
                 return true;
             });
+            // m_enter_notify_enable is added because
+            // enter_notify_event conflicts with keyboard operations.
+            if (m_enter_notify_enable) {
+                candidate_ebox.enter_notify_event.connect((e) => {
+                    m_lookup_table.set_cursor_pos(index);
+                    if (m_entry_notify_show_id > 0) {
+                        GLib.Source.remove(m_entry_notify_show_id);
+                    }
+                    // If timeout is not added, memory leak happens and
+                    // button_press_event signal does not work above.
+                    m_entry_notify_show_id = GLib.Timeout.add(100, () => {
+                        show_candidate_panel();
+                        return false;
+                    });
+                    return true;
+                });
+            }
             grid.attach(candidate_ebox,
                         n % (int)EMOJI_GRID_PAGE, n / (int)EMOJI_GRID_PAGE,
                         1, 1);
@@ -797,6 +869,7 @@ class IBusEmojier : Gtk.Window {
     }
 
     private void hide_candidate_panel() {
+        m_enter_notify_enable = true;
         m_candidate_panel_is_visible = false;
         if (m_loop.is_running())
             show_category_list();
@@ -841,6 +914,7 @@ class IBusEmojier : Gtk.Window {
     }
 
     private void candidate_panel_cursor_down() {
+        m_enter_notify_enable = false;
         uint ncandidates = m_lookup_table.get_number_of_candidates();
         uint cursor = m_lookup_table.get_cursor_pos();
         if ((cursor + EMOJI_GRID_PAGE) < ncandidates) {
@@ -854,6 +928,7 @@ class IBusEmojier : Gtk.Window {
     }
 
     private void candidate_panel_cursor_up() {
+        m_enter_notify_enable = false;
         int ncandidates = (int)m_lookup_table.get_number_of_candidates();
         int cursor = (int)m_lookup_table.get_cursor_pos();
         int highest_pos =
@@ -891,6 +966,7 @@ class IBusEmojier : Gtk.Window {
         m_input_context_path = input_context_path;
         m_candidate_panel_is_visible = false;
         m_result = null;
+        m_enter_notify_enable = true;
 
         /* Let gtk recalculate the window size. */
         resize(1, 1);
@@ -1011,7 +1087,10 @@ class IBusEmojier : Gtk.Window {
             } else if (m_category_active_index > 0) {
                 Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row();
                 EBoxRow row = gtkrow as EBoxRow;
-                show_emoji_for_category(row);
+                if (m_current_category_type == CategoryType.EMOJI)
+                    show_emoji_for_category(row);
+                else if (m_current_category_type == CategoryType.LANG)
+                    activated_language(row);
             }
             return true;
         case Gdk.Key.BackSpace:
@@ -1026,6 +1105,7 @@ class IBusEmojier : Gtk.Window {
                 break;
             }
             if (m_candidate_panel_is_visible) {
+                m_enter_notify_enable = false;
                 m_lookup_table.cursor_down();
                 show_candidate_panel();
             }
@@ -1035,6 +1115,7 @@ class IBusEmojier : Gtk.Window {
             return true;
         case Gdk.Key.Right:
             if (m_candidate_panel_is_visible) {
+                m_enter_notify_enable = false;
                 m_lookup_table.cursor_down();
                 show_candidate_panel();
                 return true;
@@ -1042,6 +1123,7 @@ class IBusEmojier : Gtk.Window {
             break;
         case Gdk.Key.Left:
             if (m_candidate_panel_is_visible) {
+                m_enter_notify_enable = false;
                 m_lookup_table.cursor_up();
                 show_candidate_panel();
                 return true;
@@ -1061,6 +1143,7 @@ class IBusEmojier : Gtk.Window {
             return true;
         case Gdk.Key.Page_Down:
             if (m_candidate_panel_is_visible) {
+                m_enter_notify_enable = false;
                 m_lookup_table.page_down();
                 show_candidate_panel();
                 return true;
@@ -1068,6 +1151,7 @@ class IBusEmojier : Gtk.Window {
             break;
         case Gdk.Key.Page_Up:
             if (m_candidate_panel_is_visible) {
+                m_enter_notify_enable = false;
                 m_lookup_table.page_up();
                 show_candidate_panel();
                 return true;
-- 
2.7.4

From 31ed31e5303c3946f53b035a63d76f5c1e3cd84a Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 13 Mar 2017 11:43:09 +0900
Subject: [PATCH] src: Fix ibus_emoji_dict_load()

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/320350043
---
 configure.ac       | 3 ++-
 src/emoji-parser.c | 6 ++++--
 src/ibusemoji.c    | 6 +++---
 3 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/configure.ac b/configure.ac
index 027c027..369485c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -620,7 +620,8 @@ fi
 
 AC_ARG_WITH(emoji-json-file,
     AS_HELP_STRING([--with-emoji-json-file[=DIR/emoji.json]],
-        [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json")]),
+        [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json")
+         You can get emoji.json with "npm install -g emojione".]),
     EMOJI_JSON_FILE=$with_emoji_json_file,
     EMOJI_JSON_FILE="/usr/lib/node_modules/emojione/emoji.json"
 )
diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index c5af42c..5965309 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -1,7 +1,7 @@
 /* -*- 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-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2016 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
@@ -20,9 +20,11 @@
  * USA
  */
 
-/* Convert ../data/annotations/en.xml and
+/* Convert /usr/share/unicode/cldr/common/annotations/*.xml and
  * /usr/lib/node_modules/emojione/emoji.json
  * to the dictionary file which look up the Emoji from the annotation.
+ * Get *.xml from https://github.com/fujiwarat/cldr-emoji-annotation
+ * or http://www.unicode.org/repos/cldr/trunk/common/annotations .
  * Get emoji.json with 'npm install -g emojione'.
  * en.xml is used for the Unicode annotations and emoji.json is used
  * for the aliases_ascii, e.g. ":)", and category, e.g. "people".
diff --git a/src/ibusemoji.c b/src/ibusemoji.c
index 8d88704..996d136 100644
--- a/src/ibusemoji.c
+++ b/src/ibusemoji.c
@@ -431,10 +431,10 @@ ibus_emoji_dict_load (const gchar *path)
     GHashTable *dict = g_hash_table_new_full (g_str_hash,
                                               g_str_equal,
                                               g_free,
-                                              free_dict_words);
+                                              g_object_unref);
 
     for (l = list; l; l = l->next) {
-        IBusEmojiData *data = list->data;
+        IBusEmojiData *data = l->data;
         if (!IBUS_IS_EMOJI_DATA (data)) {
             g_warning ("Your dict format is no longer supported.\n"
                        "Need to create the dictionaries again.");
@@ -442,7 +442,7 @@ ibus_emoji_dict_load (const gchar *path)
         }
         g_hash_table_insert (dict,
                              g_strdup (ibus_emoji_data_get_emoji (data)),
-                             data);
+                             g_object_ref_sink (data));
     }
 
     g_slist_free (list);
-- 
2.9.3

From c580845167b54ccab76d8b72bdc797ef8d64d554 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 13 Mar 2017 11:58:06 +0900
Subject: [PATCH] ui/gtk3: Fix emoji text entry cusor position

Focus on emoji text entry by default
Remove internal text buffer and use Gtk.Entry buffer instead.
Implement cusor left, right, home, end on emoji annotation preedit.

Review URL: https://codereview.appspot.com/313710043
---
 ui/gtk3/emojier.vala | 104 +++++++++++++++++++++++++++++++++------------------
 1 file changed, 68 insertions(+), 36 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index bc1eff4..8cecea8 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -75,7 +75,10 @@ class IBusEmojier : Gtk.Window {
                 row_homogeneous : false,
                 vexpand : true,
                 halign : Gtk.Align.FILL,
-                valign : Gtk.Align.FILL
+                valign : Gtk.Align.FILL,
+                row_spacing : 5,
+                column_spacing : 5,
+                border_width : 2
             );
         }
     }
@@ -190,7 +193,6 @@ class IBusEmojier : Gtk.Window {
     private CategoryType m_current_category_type = CategoryType.EMOJI;
     private bool m_is_running = false;
     private string m_input_context_path = "";
-    private GLib.StringBuilder m_buffer_string;
     private GLib.MainLoop? m_loop;
     private string? m_result;
     private GLib.SList<string> m_lang_list;
@@ -288,11 +290,9 @@ class IBusEmojier : Gtk.Window {
         m_entry.set_placeholder_text(_("Type annotation or choose emoji"));
         m_vbox.add(m_entry);
         m_entry.changed.connect(() => {
-            m_buffer_string.assign(m_entry.get_text());
-            update_cadidate_window();
+            update_candidate_window();
         });
         m_entry.icon_release.connect((icon_pos, event) => {
-            m_buffer_string.erase();
             hide_candidate_panel();
         });
 
@@ -303,7 +303,6 @@ class IBusEmojier : Gtk.Window {
         Atk.Object obj = m_entry.get_accessible();
         obj.set_role (Atk.Role.STATUSBAR);
 
-        m_buffer_string = new StringBuilder();
         grab_focus();
 
         // The constructor of IBus.LookupTable does not support more than
@@ -680,11 +679,16 @@ class IBusEmojier : Gtk.Window {
         buttons_hbox.show_all();
     }
 
-    private bool check_unicode_point(bool check_xdigit_only) {
-        m_unicode_point = null;
+    private bool check_unicode_point(string? annotation=null) {
+        bool check_xdigit_only = true;
+        if (annotation == null) {
+            annotation = m_entry.get_text();
+            m_unicode_point = null;
+            check_xdigit_only = false;
+        }
         GLib.StringBuilder buff = new GLib.StringBuilder();
-        for (int i = 0; i < m_buffer_string.str.char_count(); i++) {
-            unichar ch = m_buffer_string.str.get_char(i);
+        for (int i = 0; i < annotation.char_count(); i++) {
+            unichar ch = annotation.get_char(i);
             if (ch == 0)
                 return false;
             if (!ch.isxdigit())
@@ -704,7 +708,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
-    public void update_cadidate_window() {
+    public void update_candidate_window() {
         string annotation = m_entry.get_text();
         if (annotation.length == 0) {
             hide_candidate_panel();
@@ -716,7 +720,7 @@ class IBusEmojier : Gtk.Window {
             return;
         }
         // Call check_unicode_point() to get m_unicode_point
-        check_unicode_point(false);
+        check_unicode_point();
         unowned GLib.SList<string>? emojis =
             m_annotation_to_emojis_dict.lookup(annotation);
         if (emojis == null && m_unicode_point == null) {
@@ -725,7 +729,7 @@ class IBusEmojier : Gtk.Window {
         }
         m_lookup_table.clear();
         // Call check_unicode_point() to update m_lookup_table
-        check_unicode_point(false);
+        check_unicode_point();
         foreach (unowned string emoji in emojis) {
             IBus.Text text = new IBus.Text.from_string(emoji);
             m_lookup_table.append_candidate(text);
@@ -760,9 +764,6 @@ class IBusEmojier : Gtk.Window {
             });
         }
         EGrid grid = new EGrid();
-        grid.set_row_spacing(5);
-        grid.set_column_spacing(5);
-        grid.set_border_width(2);
         int n = 0;
         for (uint i = page_start_pos; i < page_end_pos; i++) {
             IBus.Text candidate = m_lookup_table.get_candidate(i);
@@ -878,14 +879,11 @@ class IBusEmojier : Gtk.Window {
     private bool if_in_range_of_lookup(uint keyval) {
         if (!m_candidate_panel_is_visible)
             return false;
-        string backup_annotation = m_buffer_string.str.dup();
+        StringBuilder buffer_string = new StringBuilder(m_entry.get_text());
         unichar ch = IBus.keyval_to_unicode (keyval);
-        m_buffer_string.append_unichar(ch);
-        if (check_unicode_point(true)) {
-            m_buffer_string.assign(backup_annotation);
+        buffer_string.append_unichar(ch);
+        if (check_unicode_point(buffer_string.str))
             return false;
-        }
-        m_buffer_string.assign(backup_annotation);
         if (keyval < Gdk.Key.@0 || keyval > Gdk.Key.@9)
             return false;
         if (keyval == Gdk.Key.@0)
@@ -958,6 +956,18 @@ class IBusEmojier : Gtk.Window {
         show_category_list();
     }
 
+    private void entry_enter_keyval(uint keyval) {
+        unichar ch = IBus.keyval_to_unicode(keyval);
+        if (!ch.isgraph())
+            return;
+        string str = ch.to_string();
+
+        // what gtk_entry_commit_cb() do
+        int pos = m_entry.get_position();
+        m_entry.insert_text(str, -1, ref pos);
+        m_entry.set_position(pos);
+    }
+
     public string run(Gdk.Event event,
                       string    input_context_path) {
         assert (m_loop == null);
@@ -972,7 +982,7 @@ class IBusEmojier : Gtk.Window {
         resize(1, 1);
 
         m_entry.set_text("");
-        m_buffer_string.erase();
+        m_entry.grab_focus();
 
         Gdk.Device device = event.get_device();
         if (device == null) {
@@ -1070,12 +1080,12 @@ class IBusEmojier : Gtk.Window {
                 m_current_category_type = CategoryType.EMOJI;
                 show_candidate_panel();
                 return true;
-            } else if (m_buffer_string.str.length == 0) {
+            } else if (m_entry.get_text().length == 0) {
                 m_loop.quit();
                 hide_candidate_panel();
                 return true;
             }
-            m_buffer_string.erase();
+            m_entry.delete_text(0, -1);
             break;
         case Gdk.Key.Return:
             if (m_candidate_panel_is_visible) {
@@ -1094,14 +1104,14 @@ class IBusEmojier : Gtk.Window {
             }
             return true;
         case Gdk.Key.BackSpace:
-            if (m_buffer_string.len > 0)
-                m_buffer_string.erase(m_buffer_string.len - 1);
+            if (m_entry.get_text().len() > 0) {
+                GLib.Signal.emit_by_name(m_entry, "backspace");
+            }
             break;
         case Gdk.Key.space:
         case Gdk.Key.KP_Space:
             if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
-                unichar ch = IBus.keyval_to_unicode (keyval);
-                m_buffer_string.append_unichar(ch);
+                entry_enter_keyval(keyval);
                 break;
             }
             if (m_candidate_panel_is_visible) {
@@ -1120,6 +1130,12 @@ class IBusEmojier : Gtk.Window {
                 show_candidate_panel();
                 return true;
             }
+            if (m_entry.get_text().len() > 0) {
+                GLib.Signal.emit_by_name(m_entry, "move-cursor",
+                                         Gtk.MovementStep.VISUAL_POSITIONS,
+                                         1, false);
+                return true;
+            }
             break;
         case Gdk.Key.Left:
             if (m_candidate_panel_is_visible) {
@@ -1128,6 +1144,12 @@ class IBusEmojier : Gtk.Window {
                 show_candidate_panel();
                 return true;
             }
+            if (m_entry.get_text().len() > 0) {
+                GLib.Signal.emit_by_name(m_entry, "move-cursor",
+                                         Gtk.MovementStep.VISUAL_POSITIONS,
+                                         -1, false);
+                return true;
+            }
             break;
         case Gdk.Key.Down:
             if (m_candidate_panel_is_visible)
@@ -1157,17 +1179,27 @@ class IBusEmojier : Gtk.Window {
                 return true;
             }
             break;
-        default:
-            unichar ch = IBus.keyval_to_unicode(keyval);
-            if (!ch.isgraph())
+        case Gdk.Key.Home:
+            if (m_entry.get_text().len() > 0) {
+                GLib.Signal.emit_by_name(m_entry, "move-cursor",
+                                         Gtk.MovementStep.DISPLAY_LINE_ENDS,
+                                         -1, false);
+                return true;
+            }
+            break;
+        case Gdk.Key.End:
+            if (m_entry.get_text().len() > 0) {
+                GLib.Signal.emit_by_name(m_entry, "move-cursor",
+                                         Gtk.MovementStep.DISPLAY_LINE_ENDS,
+                                         1, false);
                 return true;
-            m_buffer_string.append_unichar(ch);
+            }
+            break;
+        default:
+            entry_enter_keyval(keyval);
             break;
         }
 
-        string annotation = m_buffer_string.str;
-        m_entry.set_text(annotation);
-
         return true;
     }
 
-- 
2.9.3

From fbe3de1789c73a8a175a451b2a6b967f5d3c6514 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 13 Mar 2017 12:08:21 +0900
Subject: [PATCH] src: Update emoji-parser to treat tts as emoji
 description

unicode annotation xml files have a 'tts' attribute and seem
it is used as the emoji descriptions instead of emoji annotations.
This can show the translated descriptions.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/319480043
---
 src/emoji-parser.c   | 26 +++++++++++++++++++-------
 src/ibusemoji.c      | 16 +++++++++++++---
 src/ibusemoji.h      | 11 +++++++++++
 ui/gtk3/emojier.vala | 17 ++++++++++++++---
 4 files changed, 57 insertions(+), 13 deletions(-)

diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index 5965309..8ff04f1 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -43,6 +43,7 @@ struct _EmojiData {
     GSList     *annotations;
     gboolean    is_annotation;
     gchar      *description;
+    gboolean    is_tts;
     gchar      *category;
     GSList     *list;
 };
@@ -97,6 +98,8 @@ update_emoji_list (EmojiData *data)
                                        (GCopyFunc) g_strdup,
                                        NULL));
         }
+        if (data->description)
+            ibus_emoji_data_set_description (emoji, data->description);
     } else {
         IBusEmojiData *emoji =
                 ibus_emoji_data_new ("emoji",
@@ -149,13 +152,12 @@ unicode_annotations_start_element_cb (GMarkupParseContext *context,
                 data->emoji = g_strdup (value);
             }
         }
-        else if (g_strcmp0 (attribute, "tts") == 0) {
-            GSList *duplicated = g_slist_find_custom (data->annotations,
-                                                      value,
-                                                      (GCompareFunc) g_strcmp0);
-            if (duplicated == NULL) {
-                data->annotations = g_slist_prepend (data->annotations,
-                                                     g_strdup (value));
+        /* tts seems 'text to speach' and it would be a description
+         * instead of annotation.
+         */
+        else if (g_strcmp0 (attribute, "type") == 0) {
+            if (g_strcmp0 (value, "tts") == 0) {
+                data->is_tts = TRUE;
             }
         }
     }
@@ -177,6 +179,7 @@ unicode_annotations_end_element_cb (GMarkupParseContext *context,
 
     update_emoji_list (data);
     data->is_annotation = FALSE;
+    data->is_tts = FALSE;
 }
 
 void
@@ -194,6 +197,15 @@ unicode_annotations_text_cb (GMarkupParseContext *context,
     g_assert (data != NULL);
     if (!data->is_annotation)
         return;
+    if (data->is_tts) {
+        if (data->description) {
+            g_warning ("Duplicated 'tts' is found: %s: %s",
+                       data->description, text);
+            g_clear_pointer (&data->description, g_free);
+        }
+        data->description = g_strdup (text);
+        return;
+    }
     annotations = g_strsplit (text, " | ", -1);
     for (i = 0; (annotation = annotations[i]) != NULL; i++) {
         GSList *duplicated = g_slist_find_custom (data->annotations,
diff --git a/src/ibusemoji.c b/src/ibusemoji.c
index 996d136..c61cd70 100644
--- a/src/ibusemoji.c
+++ b/src/ibusemoji.c
@@ -29,7 +29,7 @@
 #include "ibusinternal.h"
 
 #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
-#define IBUS_EMOJI_DATA_VERSION (1)
+#define IBUS_EMOJI_DATA_VERSION (2)
 
 enum {
     PROP_0 = 0,
@@ -128,7 +128,7 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class)
                         "emoji description",
                         "The emoji description",
                         "",
-                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+                        G_PARAM_READWRITE));
 
     /**
      * IBusEmojiData:category:
@@ -352,6 +352,16 @@ ibus_emoji_data_get_description (IBusEmojiData *emoji)
     return emoji->priv->description;
 }
 
+void
+ibus_emoji_data_set_description (IBusEmojiData *emoji,
+                                 const gchar   *description)
+{
+    g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
+
+    g_free (emoji->priv->description);
+    emoji->priv->description = g_strdup (description);
+}
+
 const gchar *
 ibus_emoji_data_get_category (IBusEmojiData *emoji)
 {
@@ -541,7 +551,7 @@ ibus_emoji_data_load (const gchar *path)
         goto out_load_cache;
     }
 
-    if (version != IBUS_EMOJI_DATA_VERSION) {
+    if (version > IBUS_EMOJI_DATA_VERSION) {
         g_warning ("cache version is different: %u != %u",
                    version, IBUS_EMOJI_DATA_VERSION);
         goto out_load_cache;
diff --git a/src/ibusemoji.h b/src/ibusemoji.h
index 3fd09a9..eb24fdd 100644
--- a/src/ibusemoji.h
+++ b/src/ibusemoji.h
@@ -134,6 +134,17 @@ void            ibus_emoji_data_set_annotations (IBusEmojiData *emoji,
 const gchar *   ibus_emoji_data_get_description (IBusEmojiData *emoji);
 
 /**
+ * ibus_emoji_data_set_description:
+ * @emoji : An #IBusEmojiData
+ * @description: An emoji description
+ *
+ * Sets the description in #IBusEmojiData.
+ */
+void            ibus_emoji_data_set_description (IBusEmojiData *emoji,
+                                                 const gchar   *description);
+
+
+/**
  * ibus_emoji_data_get_category:
  * @emoji : An #IBusEmojiData
  *
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 8cecea8..5e126e9 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -370,8 +370,14 @@ class IBusEmojier : Gtk.Window {
     private void reload_emoji_dict() {
         init_emoji_dict();
         make_emoji_dict("en");
-        if (m_current_lang_id != "en")
+        if (m_current_lang_id != "en") {
+            var lang_ids = m_current_lang_id.split("_");
+            if (lang_ids.length > 1) {
+                string sub_id = lang_ids[0];
+                make_emoji_dict(sub_id);
+            }
             make_emoji_dict(m_current_lang_id);
+        }
         loaded_emoji_dict();
     }
 
@@ -393,8 +399,8 @@ class IBusEmojier : Gtk.Window {
         if (emoji_list == null)
             return;
         foreach (IBus.EmojiData data in emoji_list) {
-            update_annotation_to_emojis_dict(data);
             update_emoji_to_data_dict(data, lang);
+            update_annotation_to_emojis_dict(data);
             update_category_to_emojis_dict(data, lang);
         }
         GLib.List<unowned string> annotations =
@@ -434,11 +440,16 @@ class IBusEmojier : Gtk.Window {
             unowned IBus.EmojiData? en_data =
                     m_emoji_to_data_dict.lookup(emoji);
             if (en_data == null) {
-                warning("No IBusEmojiData for English: %s".printf(emoji));
                 m_emoji_to_data_dict.insert(emoji, data);
                 return;
             }
+            string trans_description = data.get_description();
+            en_data.set_description(trans_description);
             unowned GLib.SList<string> annotations = data.get_annotations();
+            var words = trans_description.split(" ");
+            // If the description has less than 3 words, add it to annotations
+            if (words.length < 3)
+                annotations.append(trans_description);
             unowned GLib.SList<string> en_annotations
                 = en_data.get_annotations();
             foreach (string annotation in en_annotations) {
-- 
2.9.3

From ab6c38c192cdf22356cbf254b98fb5b3d9d9a680 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 15 Mar 2017 11:48:24 +0900
Subject: [PATCH] client/x11: Add XSetIOErrorHandler() for GNOME3 desktop

When log into GNOME3 desktop immediately after the system is booted,
ibus-daemon is sometimes alive but ibus-x11 is dead after log out
the session. Because gdk_x_io_error() is called as the callback of
XSetIOErrorHandler() in gtk/gdk/x11/gdkmain-x11.c in ibus-x11.
Now I assume the callback is called in logout.

BUG=https://github.com/ibus/ibus/issues/1907

Review URL: https://codereview.appspot.com/319490043
---
 client/x11/main.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/client/x11/main.c b/client/x11/main.c
index a717a2c..159f430 100644
--- a/client/x11/main.c
+++ b/client/x11/main.c
@@ -2,6 +2,7 @@
 /* vim:set et sts=4: */
 /* ibus
  * Copyright (C) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
+ * Copyright (C) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
  * Copyright (C) 2007-2015 Red Hat, Inc.
  *
  * main.c:
@@ -1131,6 +1132,20 @@ _xerror_handler (Display *dpy, XErrorEvent *e)
     return 1;
 }
 
+/* When log into GNOME3 desktop immediately after the system is booted,
+ * ibus-daemon is sometimes alive but ibus-x11 is dead after log out
+ * the session. Because gdk_x_io_error() is called as the callback of
+ * XSetIOErrorHandler() in gtk/gdk/x11/gdkmain-x11.c in ibus-x11.
+ * Now I assume the callback is called in logout.
+ */
+static int
+_xerror_io_handler (Display *dpy)
+{
+    if (_kill_daemon)
+        _atexit_cb ();
+    return 0;
+}
+
 int
 main (int argc, char **argv)
 {
@@ -1146,6 +1161,7 @@ main (int argc, char **argv)
 
     gtk_init (&argc, &argv);
     XSetErrorHandler (_xerror_handler);
+    XSetIOErrorHandler (_xerror_io_handler);
 
     while (1) {
         static struct option long_options [] = {
-- 
2.9.3

From 58f6140f427815adc947a5bb5c7dea4f3e315ae8 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 15 Mar 2017 11:52:39 +0900
Subject: [PATCH] ui/gtk3: Implement shortcut keys on emoji dialog

- Implement Ctrl-f, Ctrl-b, Ctrl-n, Ctrl-p, Ctrl-h, Ctrh-e for
  cursor movements; forward, back, next, previous, head, end
  on emoji grid.
- Implement Ctrl-a and Shift+arrow for text selection on emoji annotation.
- Implement Ctrl-u to delete text on emoji annotation.
- Implement to delete a selected text on emoji annotation.
- Change to show page indices to candidate indices on emoji.
- Sorted emoji categories.
- Added timeout of m_enter_notify_enable = false to bring back mouse.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/315700043
---
 ui/gtk3/emojier.vala | 311 +++++++++++++++++++++++++++++++++++----------------
 1 file changed, 215 insertions(+), 96 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 5e126e9..7da96c7 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -214,6 +214,7 @@ class IBusEmojier : Gtk.Window {
     private string[] m_favorites = {};
     private bool m_enter_notify_enable = true;
     private uint m_entry_notify_show_id;
+    private uint m_entry_notify_disable_id;
 
     public signal void candidate_clicked(uint index, uint button, uint state);
     public signal void loaded_emoji_dict();
@@ -525,6 +526,18 @@ class IBusEmojier : Gtk.Window {
         show_category_list();
     }
 
+    private string get_title_string(string orig) {
+        StringBuilder buff = new StringBuilder();
+        for (int i = 0; i < orig.char_count(); i++) {
+            unichar ch = orig.get_char(i);
+            if (i == 0)
+                buff.append_unichar(ch.toupper());
+            else
+                buff.append_unichar(ch);
+        }
+        return buff.str;
+    }
+
     private void show_category_list() {
         remove_all_children();
         m_scrolled_window = new EScrolledWindow();
@@ -586,19 +599,14 @@ class IBusEmojier : Gtk.Window {
             }
             GLib.List<unowned string> categories =
                     m_category_to_emojis_dict.get_keys();
+            categories.sort((a, b) => {
+                return GLib.strcmp(_(a), _(b));
+            });
             foreach (unowned string category in categories) {
                 EBoxRow row = new EBoxRow(category);
                 string locale_category = _(category);
-                StringBuilder capital_category = new StringBuilder();
-                for (int i = 0; i < locale_category.char_count(); i++) {
-                    unichar ch = locale_category.get_char(i);
-                    if (i == 0)
-                        capital_category.append_unichar(ch.toupper());
-                    else
-                        capital_category.append_unichar(ch);
-                }
                 EPaddedLabel widget =
-                        new EPaddedLabel(capital_category.str,
+                        new EPaddedLabel(get_title_string(locale_category),
                                          Gtk.Align.CENTER);
                 row.add(widget);
                 m_list_box.add(row);
@@ -650,7 +658,7 @@ class IBusEmojier : Gtk.Window {
                 IBus.Text text = new IBus.Text.from_string(emoji);
                 m_lookup_table.append_candidate(text);
             }
-            m_backward = row.text;
+            m_backward = get_title_string(row.text);
         }
         show_candidate_panel();
     }
@@ -759,9 +767,7 @@ class IBusEmojier : Gtk.Window {
         uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates);
         if (m_backward != null) {
             string backward_desc =
-                    "%s (%u / %u)".printf(m_backward,
-                                          cursor / page_size + 1,
-                                          ncandidates / page_size + 1);
+                    "%s (%u / %u)".printf(m_backward, cursor, ncandidates - 1);
             EPaddedLabel label = new EPaddedLabel(backward_desc,
                                                   Gtk.Align.CENTER,
                                                   TravelDirection.BACKWARD);
@@ -805,23 +811,23 @@ class IBusEmojier : Gtk.Window {
                 candidate_clicked(index, e.button, e.state);
                 return true;
             });
-            // m_enter_notify_enable is added because
-            // enter_notify_event conflicts with keyboard operations.
-            if (m_enter_notify_enable) {
-                candidate_ebox.enter_notify_event.connect((e) => {
-                    m_lookup_table.set_cursor_pos(index);
-                    if (m_entry_notify_show_id > 0) {
+            candidate_ebox.enter_notify_event.connect((e) => {
+                // m_enter_notify_enable is added because
+                // enter_notify_event conflicts with keyboard operations.
+                if (!m_enter_notify_enable)
+                    return true;
+                m_lookup_table.set_cursor_pos(index);
+                if (m_entry_notify_show_id > 0) {
                         GLib.Source.remove(m_entry_notify_show_id);
-                    }
-                    // If timeout is not added, memory leak happens and
-                    // button_press_event signal does not work above.
-                    m_entry_notify_show_id = GLib.Timeout.add(100, () => {
+                }
+                // If timeout is not added, memory leak happens and
+                // button_press_event signal does not work above.
+                m_entry_notify_show_id = GLib.Timeout.add(100, () => {
                         show_candidate_panel();
                         return false;
-                    });
-                    return true;
                 });
-            }
+                return true;
+            });
             grid.attach(candidate_ebox,
                         n % (int)EMOJI_GRID_PAGE, n / (int)EMOJI_GRID_PAGE,
                         1, 1);
@@ -844,16 +850,23 @@ class IBusEmojier : Gtk.Window {
                 widget.show_all();
                 return;
             }
-            unowned IBus.EmojiData data =
+            unowned IBus.EmojiData? data =
                     m_emoji_to_data_dict.lookup(candidate.text);
-            unowned string description = data.get_description();
-            if (description != "") {
+            if (data == null) {
+                // TODO: Provide a description for the favorite emojis.
                 EPaddedLabel widget = new EPaddedLabel(
-                        _("Description: %s").printf(description),
+                        _("Description: %s").printf(_("None")),
                         Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
+                return;
             }
+            unowned string description = data.get_description();
+            EPaddedLabel desc_widget = new EPaddedLabel(
+                    _("Description: %s").printf(description),
+                    Gtk.Align.START);
+            m_vbox.add(desc_widget);
+            desc_widget.show_all();
             unowned GLib.SList<unowned string>? annotations =
                     data.get_annotations();
             GLib.StringBuilder buff = new GLib.StringBuilder();
@@ -922,8 +935,21 @@ class IBusEmojier : Gtk.Window {
         m_result = text.text;
     }
 
-    private void candidate_panel_cursor_down() {
+    private void enter_notify_disable_with_timer() {
+        // Enable keyboard operation and disable mouse operation.
         m_enter_notify_enable = false;
+        if (m_entry_notify_disable_id > 0) {
+            GLib.Source.remove(m_entry_notify_disable_id);
+        }
+        // Bring back the mouse operation after a timeout.
+        m_entry_notify_show_id = GLib.Timeout.add(100, () => {
+            m_enter_notify_enable = true;
+            return false;
+        });
+    }
+
+    private void candidate_panel_cursor_down() {
+        enter_notify_disable_with_timer();
         uint ncandidates = m_lookup_table.get_number_of_candidates();
         uint cursor = m_lookup_table.get_cursor_pos();
         if ((cursor + EMOJI_GRID_PAGE) < ncandidates) {
@@ -937,11 +963,11 @@ class IBusEmojier : Gtk.Window {
     }
 
     private void candidate_panel_cursor_up() {
-        m_enter_notify_enable = false;
+        enter_notify_disable_with_timer();
         int ncandidates = (int)m_lookup_table.get_number_of_candidates();
         int cursor = (int)m_lookup_table.get_cursor_pos();
         int highest_pos =
-            (ncandidates / (int)EMOJI_GRID_PAGE * (int)EMOJI_GRID_PAGE)
+            ((ncandidates - 1)/ (int)EMOJI_GRID_PAGE * (int)EMOJI_GRID_PAGE)
             + (cursor % (int)EMOJI_GRID_PAGE);
         if ((cursor - (int)EMOJI_GRID_PAGE) >= 0) {
             m_lookup_table.set_cursor_pos(cursor - (int)EMOJI_GRID_PAGE);
@@ -967,13 +993,119 @@ class IBusEmojier : Gtk.Window {
         show_category_list();
     }
 
+    private bool key_press_cursor_horizontal(uint keyval,
+                                             uint modifiers) {
+        assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right);
+
+        if (m_candidate_panel_is_visible) {
+            enter_notify_disable_with_timer();
+            if (keyval == Gdk.Key.Left)
+                m_lookup_table.cursor_up();
+            else if (keyval == Gdk.Key.Right)
+                m_lookup_table.cursor_down();
+            show_candidate_panel();
+        } else if (m_entry.get_text().len() > 0) {
+            int step = 0;
+            if (keyval == Gdk.Key.Left)
+                step = -1;
+            else if (keyval == Gdk.Key.Right)
+                step = 1;
+            GLib.Signal.emit_by_name(
+                    m_entry, "move-cursor",
+                    Gtk.MovementStep.VISUAL_POSITIONS,
+                    step,
+                    (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0
+                            ? true : false);
+        } else {
+            // For Gdk.Key.f and Gdk.Key.b
+            if (keyval == Gdk.Key.Left)
+                keyval = Gdk.Key.Up;
+            else if (keyval == Gdk.Key.Right)
+                keyval = Gdk.Key.Down;
+            category_list_cursor_move(keyval);
+        }
+        return true;
+    }
+
+    private bool key_press_cursor_vertical(uint keyval) {
+        assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up);
+
+        if (m_candidate_panel_is_visible) {
+            if (keyval == Gdk.Key.Down)
+                candidate_panel_cursor_down();
+            else if (keyval == Gdk.Key.Up)
+                candidate_panel_cursor_up();
+        } else {
+            category_list_cursor_move(keyval);
+        }
+        return true;
+    }
+
+    private bool key_press_cursor_home_end(uint keyval,
+                                           uint modifiers) {
+        assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End);
+
+        if (m_candidate_panel_is_visible) {
+            enter_notify_disable_with_timer();
+            if (keyval == Gdk.Key.Home) {
+                m_lookup_table.set_cursor_pos(0);
+            } else if (keyval == Gdk.Key.End) {
+                uint ncandidates = m_lookup_table.get_number_of_candidates();
+                m_lookup_table.set_cursor_pos(ncandidates - 1);
+            }
+            show_candidate_panel();
+            return true;
+        }
+        if (m_entry.get_text().len() > 0) {
+            int step = 0;
+            if (keyval == Gdk.Key.Home)
+                step = -1;
+            else if (keyval == Gdk.Key.End)
+                step = 1;
+            GLib.Signal.emit_by_name(
+                    m_entry, "move-cursor",
+                    Gtk.MovementStep.DISPLAY_LINE_ENDS,
+                    step,
+                    (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0
+                            ? true : false);
+            return true;
+        }
+        return false;
+    }
+
+    private bool key_press_cursor_escape() {
+        if (m_candidate_panel_is_visible) {
+            hide_candidate_panel();
+            return true;
+        } else if (m_current_category_type == CategoryType.LANG) {
+            m_current_category_type = CategoryType.EMOJI;
+            show_candidate_panel();
+            return true;
+        } else if (m_entry.get_text().length == 0) {
+            m_loop.quit();
+            hide_candidate_panel();
+            return true;
+        }
+        m_entry.delete_text(0, -1);
+        return true;
+    }
+
     private void entry_enter_keyval(uint keyval) {
         unichar ch = IBus.keyval_to_unicode(keyval);
-        if (!ch.isgraph())
+        if (ch.iscntrl())
             return;
         string str = ch.to_string();
 
         // what gtk_entry_commit_cb() do
+        if (m_entry.get_selection_bounds(null, null)) {
+            m_entry.delete_selection();
+        } else {
+            if (m_entry.get_overwrite_mode()) {
+               uint text_length = m_entry.get_buffer().get_length();
+               if (m_entry.cursor_position < text_length)
+                   m_entry.delete_from_cursor(Gtk.DeleteType.CHARS, 1);
+            }
+        }
         int pos = m_entry.get_position();
         m_entry.insert_text(str, -1, ref pos);
         m_entry.set_position(pos);
@@ -1084,19 +1216,8 @@ class IBusEmojier : Gtk.Window {
         }
         switch (keyval) {
         case Gdk.Key.Escape:
-            if (m_candidate_panel_is_visible) {
-                hide_candidate_panel();
-                return true;
-            } else if (m_current_category_type == CategoryType.LANG) {
-                m_current_category_type = CategoryType.EMOJI;
-                show_candidate_panel();
+            if (key_press_cursor_escape())
                 return true;
-            } else if (m_entry.get_text().length == 0) {
-                m_loop.quit();
-                hide_candidate_panel();
-                return true;
-            }
-            m_entry.delete_text(0, -1);
             break;
         case Gdk.Key.Return:
             if (m_candidate_panel_is_visible) {
@@ -1126,7 +1247,7 @@ class IBusEmojier : Gtk.Window {
                 break;
             }
             if (m_candidate_panel_is_visible) {
-                m_enter_notify_enable = false;
+                enter_notify_disable_with_timer();
                 m_lookup_table.cursor_down();
                 show_candidate_panel();
             }
@@ -1135,48 +1256,20 @@ class IBusEmojier : Gtk.Window {
             }
             return true;
         case Gdk.Key.Right:
-            if (m_candidate_panel_is_visible) {
-                m_enter_notify_enable = false;
-                m_lookup_table.cursor_down();
-                show_candidate_panel();
-                return true;
-            }
-            if (m_entry.get_text().len() > 0) {
-                GLib.Signal.emit_by_name(m_entry, "move-cursor",
-                                         Gtk.MovementStep.VISUAL_POSITIONS,
-                                         1, false);
-                return true;
-            }
-            break;
+            key_press_cursor_horizontal(keyval, modifiers);
+            return true;
         case Gdk.Key.Left:
-            if (m_candidate_panel_is_visible) {
-                m_enter_notify_enable = false;
-                m_lookup_table.cursor_up();
-                show_candidate_panel();
-                return true;
-            }
-            if (m_entry.get_text().len() > 0) {
-                GLib.Signal.emit_by_name(m_entry, "move-cursor",
-                                         Gtk.MovementStep.VISUAL_POSITIONS,
-                                         -1, false);
-                return true;
-            }
-            break;
+            key_press_cursor_horizontal(keyval, modifiers);
+            return true;
         case Gdk.Key.Down:
-            if (m_candidate_panel_is_visible)
-                candidate_panel_cursor_down();
-            else
-                category_list_cursor_move(Gdk.Key.Down);
+            key_press_cursor_vertical(keyval);
             return true;
         case Gdk.Key.Up:
-            if (m_candidate_panel_is_visible)
-                candidate_panel_cursor_up();
-            else
-                category_list_cursor_move(Gdk.Key.Up);
+            key_press_cursor_vertical(keyval);
             return true;
         case Gdk.Key.Page_Down:
             if (m_candidate_panel_is_visible) {
-                m_enter_notify_enable = false;
+                enter_notify_disable_with_timer();
                 m_lookup_table.page_down();
                 show_candidate_panel();
                 return true;
@@ -1184,33 +1277,59 @@ class IBusEmojier : Gtk.Window {
             break;
         case Gdk.Key.Page_Up:
             if (m_candidate_panel_is_visible) {
-                m_enter_notify_enable = false;
+                enter_notify_disable_with_timer();
                 m_lookup_table.page_up();
                 show_candidate_panel();
                 return true;
             }
             break;
         case Gdk.Key.Home:
-            if (m_entry.get_text().len() > 0) {
-                GLib.Signal.emit_by_name(m_entry, "move-cursor",
-                                         Gtk.MovementStep.DISPLAY_LINE_ENDS,
-                                         -1, false);
+            if (key_press_cursor_home_end(keyval, modifiers))
                 return true;
-            }
             break;
         case Gdk.Key.End:
-            if (m_entry.get_text().len() > 0) {
-                GLib.Signal.emit_by_name(m_entry, "move-cursor",
-                                         Gtk.MovementStep.DISPLAY_LINE_ENDS,
-                                         1, false);
+            if (key_press_cursor_home_end(keyval, modifiers))
                 return true;
-            }
-            break;
-        default:
-            entry_enter_keyval(keyval);
             break;
         }
 
+        if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
+            switch (keyval) {
+            case Gdk.Key.f:
+                key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
+                return true;
+            case Gdk.Key.b:
+                key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
+                return true;
+            case Gdk.Key.n:
+                key_press_cursor_vertical(Gdk.Key.Down);
+                return true;
+            case Gdk.Key.p:
+                key_press_cursor_vertical(Gdk.Key.Up);
+                return true;
+            case Gdk.Key.h:
+                if (key_press_cursor_home_end(Gdk.Key.Home, modifiers))
+                    return true;
+                break;
+            case Gdk.Key.e:
+                if (key_press_cursor_home_end(Gdk.Key.End, modifiers))
+                    return true;
+                break;
+            case Gdk.Key.u:
+                if (key_press_cursor_escape())
+                    return true;
+                break;
+            case Gdk.Key.a:
+                if (m_entry.get_text().len() > 0) {
+                    m_entry.select_region(0, -1);
+                    return true;
+                }
+                break;
+            }
+            return false;
+        }
+
+        entry_enter_keyval(keyval);
         return true;
     }
 
-- 
2.9.3

From 50e344afaffc29e626dbc27747a1aeee6cccafdf Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Fri, 17 Mar 2017 12:08:50 +0900
Subject: [PATCH] ui/gtk3: Enable strcasecmp to match emoji annotation

Users can type capital annotations.
Also shows emoji annotations in the status bar if the
typing unicode point matches a emoji character.

Review URL: https://codereview.appspot.com/314640043
---
 ui/gtk3/emojier.vala | 97 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 65 insertions(+), 32 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 7da96c7..b1dc50c 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -432,10 +432,45 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+    private string utf8_down(string str) {
+        GLib.StringBuilder buff = new GLib.StringBuilder();
+        int length = str.char_count();
+        for (int i = 0; i < length; i++) {
+            buff.append_unichar(str.get_char(0).tolower());
+            str = str.next_char();
+        }
+        return buff.str;
+    }
+
+    private string utf8_title(string str) {
+        StringBuilder buff = new StringBuilder();
+        int length = str.char_count();
+        for (int i = 0; i < length; i++) {
+            unichar ch = str.get_char(0);
+            if (i == 0)
+                buff.append_unichar(ch.toupper());
+            else
+                buff.append_unichar(ch);
+            str = str.next_char();
+        }
+        return buff.str;
+    }
+
     private void update_emoji_to_data_dict(IBus.EmojiData data,
                                            string         lang) {
         string emoji = data.get_emoji();
         if (lang == "en") {
+            string description = utf8_down(data.get_description());
+            unowned GLib.SList<string> annotations = data.get_annotations();
+            var words = description.split(" ");
+            // If the description has less than 3 words, add it to annotations
+            if (words.length < 3 &&
+                annotations.find_custom(
+                        description,
+                        (GLib.CompareFunc<string>)GLib.strcmp) == null) {
+                annotations.append(description);
+                data.set_annotations(annotations.copy_deep(GLib.strdup));
+            }
             m_emoji_to_data_dict.replace(emoji, data);
         } else {
             unowned IBus.EmojiData? en_data =
@@ -446,16 +481,24 @@ class IBusEmojier : Gtk.Window {
             }
             string trans_description = data.get_description();
             en_data.set_description(trans_description);
+            trans_description = utf8_down(trans_description);
             unowned GLib.SList<string> annotations = data.get_annotations();
             var words = trans_description.split(" ");
             // If the description has less than 3 words, add it to annotations
-            if (words.length < 3)
+            if (words.length < 3 &&
+                annotations.find_custom(
+                        trans_description,
+                        (GLib.CompareFunc<string>)GLib.strcmp) == null) {
                 annotations.append(trans_description);
+            }
             unowned GLib.SList<string> en_annotations
                 = en_data.get_annotations();
             foreach (string annotation in en_annotations) {
-                if (annotations.find_custom(annotation, GLib.strcmp) == null)
+                if (annotations.find_custom(
+                            annotation,
+                            (GLib.CompareFunc<string>)GLib.strcmp) == null) {
                     annotations.append(annotation.dup());
+                }
             }
             en_data.set_annotations(annotations.copy_deep(GLib.strdup));
         }
@@ -526,18 +569,6 @@ class IBusEmojier : Gtk.Window {
         show_category_list();
     }
 
-    private string get_title_string(string orig) {
-        StringBuilder buff = new StringBuilder();
-        for (int i = 0; i < orig.char_count(); i++) {
-            unichar ch = orig.get_char(i);
-            if (i == 0)
-                buff.append_unichar(ch.toupper());
-            else
-                buff.append_unichar(ch);
-        }
-        return buff.str;
-    }
-
     private void show_category_list() {
         remove_all_children();
         m_scrolled_window = new EScrolledWindow();
@@ -606,7 +637,7 @@ class IBusEmojier : Gtk.Window {
                 EBoxRow row = new EBoxRow(category);
                 string locale_category = _(category);
                 EPaddedLabel widget =
-                        new EPaddedLabel(get_title_string(locale_category),
+                        new EPaddedLabel(utf8_title(locale_category),
                                          Gtk.Align.CENTER);
                 row.add(widget);
                 m_list_box.add(row);
@@ -658,7 +689,7 @@ class IBusEmojier : Gtk.Window {
                 IBus.Text text = new IBus.Text.from_string(emoji);
                 m_lookup_table.append_candidate(text);
             }
-            m_backward = get_title_string(row.text);
+            m_backward = utf8_title(row.text);
         }
         show_candidate_panel();
     }
@@ -734,6 +765,7 @@ class IBusEmojier : Gtk.Window {
             m_backward = null;
             return;
         }
+        annotation = utf8_down(annotation);
         if (annotation.length > m_emoji_max_seq_len) {
             hide_candidate_panel();
             return;
@@ -841,6 +873,8 @@ class IBusEmojier : Gtk.Window {
             m_vbox.add(grid);
             grid.show_all();
             IBus.Text candidate = m_lookup_table.get_candidate(cursor);
+            unowned IBus.EmojiData? data =
+                    m_emoji_to_data_dict.lookup(candidate.text);
             if (cursor == 0 && candidate.text == m_unicode_point) {
                 EPaddedLabel widget = new EPaddedLabel(
                         _("Description: Unicode point U+%04X").printf(
@@ -848,25 +882,25 @@ class IBusEmojier : Gtk.Window {
                         Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
-                return;
-            }
-            unowned IBus.EmojiData? data =
-                    m_emoji_to_data_dict.lookup(candidate.text);
-            if (data == null) {
-                // TODO: Provide a description for the favorite emojis.
+                if (data == null)
+                    return;
+            } else if (data == null) {
+                // TODO: Provide a custom description and annotation for
+                // the favorite emojis.
                 EPaddedLabel widget = new EPaddedLabel(
                         _("Description: %s").printf(_("None")),
                         Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
                 return;
+            } else {
+                unowned string description = data.get_description();
+                EPaddedLabel widget = new EPaddedLabel(
+                        _("Description: %s").printf(description),
+                        Gtk.Align.START);
+                m_vbox.add(widget);
+                widget.show_all();
             }
-            unowned string description = data.get_description();
-            EPaddedLabel desc_widget = new EPaddedLabel(
-                    _("Description: %s").printf(description),
-                    Gtk.Align.START);
-            m_vbox.add(desc_widget);
-            desc_widget.show_all();
             unowned GLib.SList<unowned string>? annotations =
                     data.get_annotations();
             GLib.StringBuilder buff = new GLib.StringBuilder();
@@ -1243,10 +1277,9 @@ class IBusEmojier : Gtk.Window {
         case Gdk.Key.space:
         case Gdk.Key.KP_Space:
             if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
-                entry_enter_keyval(keyval);
-                break;
-            }
-            if (m_candidate_panel_is_visible) {
+                if (m_entry.get_text().len() > 0)
+                    entry_enter_keyval(keyval);
+            } else if (m_candidate_panel_is_visible) {
                 enter_notify_disable_with_timer();
                 m_lookup_table.cursor_down();
                 show_candidate_panel();
-- 
2.9.3

From bd7e0ba297f72ae1e2989743f2426c44df29f3ec Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 21 Mar 2017 12:56:23 +0900
Subject: [PATCH] Make more readable error messages if emoji xml files are
 missed

Also Fix CONFIG_CLEAN_FILES for autoreconf

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/320750043
---
 configure.ac        | 26 +++++++++++++++++---------
 src/Makefile.am     |  5 ++++-
 src/emoji-parser.c  |  2 +-
 ui/gtk3/Makefile.am |  4 +++-
 4 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/configure.ac b/configure.ac
index 369485c..0a5f2d5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -611,17 +611,11 @@ AC_ARG_ENABLE(emoji-dict,
     [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(JSON_GLIB1, [
-        json-glib-1.0
-    ])
-    enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
-fi
 
 AC_ARG_WITH(emoji-json-file,
     AS_HELP_STRING([--with-emoji-json-file[=DIR/emoji.json]],
         [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json")
-         You can get emoji.json with "npm install -g emojione".]),
+        ]),
     EMOJI_JSON_FILE=$with_emoji_json_file,
     EMOJI_JSON_FILE="/usr/lib/node_modules/emojione/emoji.json"
 )
@@ -630,13 +624,27 @@ AC_SUBST(EMOJI_JSON_FILE)
 AC_ARG_WITH(emoji-annotation-dir,
     AS_HELP_STRING([--with-emoji-annotation-dir[=DIR]],
         [Set the directory of CLDR annotation files.
-         (default: "/usr/share/unicode/cldr/common/annotations")
-         You can get https://github.com/fujiwarat/cldr-emoji-annotation]),
+         (default: "/usr/share/unicode/cldr/common/annotations")]),
     EMOJI_ANNOTATION_DIR=$with_emoji_annotation_dir,
     EMOJI_ANNOTATION_DIR="/usr/share/unicode/cldr/common/annotations"
 )
 AC_SUBST(EMOJI_ANNOTATION_DIR)
 
+if test x"$enable_emoji_dict" = x"yes"; then
+    if test ! -f $EMOJI_JSON_FILE ; then
+        AC_MSG_ERROR(Not found $EMOJI_JSON_FILE. You can get emoji.json \
+with "npm install -g emojione".)
+    fi
+    if test ! -f $EMOJI_ANNOTATION_DIR/en.xml ; then
+        AC_MSG_ERROR(Not found $EMOJI_ANNOTATION_DIR/en.xml. You can get \
+https://github.com/fujiwarat/cldr-emoji-annotation)
+    fi
+    PKG_CHECK_MODULES(JSON_GLIB1, [
+        json-glib-1.0
+    ])
+    enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)"
+fi
+
 # Check iso-codes.
 PKG_CHECK_MODULES(ISOCODES, [
     iso-codes
diff --git a/src/Makefile.am b/src/Makefile.am
index 0d403e8..7053e3e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -243,7 +243,10 @@ LANG_FILES = $(basename $(notdir $(wildcard $(EMOJI_ANNOTATION_DIR)/*.xml)))
 noinst_PROGRAMS = emoji-parser
 
 dicts/emoji-en.dict: emoji-parser
-	$(AM_V_at)for f in $(LANG_FILES) ; do \
+	$(AM_V_at)if test x"$(LANG_FILES)" = x ; then \
+	    echo "WARNING: Not found $(EMOJI_ANNOTATION_DIR)/en.xml" 1>&2; \
+	fi; \
+	for f in $(LANG_FILES) ; do \
 	    if test x"$$f" = xen ; then \
 	        $(builddir)/emoji-parser \
 	            --xml $(EMOJI_ANNOTATION_DIR)/$$f.xml \
diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index 8ff04f1..f9e3470 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -20,7 +20,7 @@
  * USA
  */
 
-/* Convert /usr/share/unicode/cldr/common/annotations/*.xml and
+/* Convert /usr/share/unicode/cldr/common/annotations/\*.xml and
  * /usr/lib/node_modules/emojione/emoji.json
  * to the dictionary file which look up the Emoji from the annotation.
  * Get *.xml from https://github.com/fujiwarat/cldr-emoji-annotation
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 4e7fd1b..b055f67 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -81,6 +81,8 @@ AM_VALAFLAGS = \
 	--target-glib="$(VALA_TARGET_GLIB_VERSION)" \
 	$(NULL)
 
+CONFIG_CLEAN_FILES =
+
 if ENABLE_LIBNOTIFY
 AM_CFLAGS += \
 	@LIBNOTIFY_CFLAGS@ \
@@ -239,7 +241,7 @@ vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
 
 MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS)
 # for make distclean
-CONFIG_CLEAN_FILES = $(VAPIGEN_VAPIS)
+CONFIG_CLEAN_FILES += $(VAPIGEN_VAPIS)
 EXTRA_DIST += $(VAPIGEN_VAPIS)
 
 endif
-- 
2.9.3

From 0efb1c503d5901bbddcdb6fa73007b364ba4368d Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 27 Mar 2017 15:12:42 +0900
Subject: [PATCH] Move language setting from IBusEmojier to ibus-setup

The language setting of emoji annotations now can be saved
with ibus-setup.
Implement `ibus emoji [--font|--lang|--help]`

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/323720043
---
 data/ibus.schemas.in      |  46 +++---
 setup/Makefile.am         |  26 ++--
 setup/emojilang.py        | 348 ++++++++++++++++++++++++++++++++++++++++++++++
 setup/main.py             |  41 ++++--
 setup/setup.ui            |  97 ++++++++-----
 tools/main.vala           |  38 ++++-
 ui/gtk3/emojier.vala      | 295 +++++++++++++++------------------------
 ui/gtk3/ibusemojidialog.h |  11 ++
 ui/gtk3/panel.vala        |  39 ++++--
 9 files changed, 671 insertions(+), 270 deletions(-)
 create mode 100644 setup/emojilang.py

diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
index 218d223..c0bbd6f 100644
--- a/data/ibus.schemas.in
+++ b/data/ibus.schemas.in
@@ -106,18 +106,6 @@
       </locale>
     </schema>
     <schema>
-      <key>/schemas/desktop/ibus/general/hotkey/emoji</key>
-      <applyto>/desktop/ibus/general/hotkey/emoji</applyto>
-      <owner>ibus</owner>
-      <type>list</type>
-      <list_type>string</list_type>
-      <default>[&lt;Control&gt;&lt;Shift&gt;e]</default>
-      <locale name="C">
-        <short>Emoji shortcut keys for gtk_accelerator_parse</short>
-          <long>The shortcut keys for turning emoji typing on or off</long>
-      </locale>
-    </schema>
-    <schema>
       <key>/schemas/desktop/ibus/general/hotkey/enable_unconditional</key>
       <applyto>/desktop/ibus/general/hotkey/enable_unconditional</applyto>
       <owner>ibus</owner>
@@ -366,8 +354,20 @@
       </locale>
     </schema>
     <schema>
-      <key>/schemas/desktop/ibus/panel/emoji_font</key>
-      <applyto>/desktop/ibus/panel/emoji_font</applyto>
+      <key>/schemas/desktop/ibus/panel/emoji/hotkey</key>
+      <applyto>/desktop/ibus/panel/emoji/hotkey</applyto>
+      <owner>ibus</owner>
+      <type>list</type>
+      <list_type>string</list_type>
+      <default>[&lt;Control&gt;&lt;Shift&gt;e]</default>
+      <locale name="C">
+        <short>Emoji shortcut keys for gtk_accelerator_parse</short>
+          <long>The shortcut keys for turning emoji typing on or off</long>
+      </locale>
+    </schema>
+    <schema>
+      <key>/schemas/desktop/ibus/panel/emoji/font</key>
+      <applyto>/desktop/ibus/panel/emoji/font</applyto>
       <owner>ibus</owner>
       <type>string</type>
       <default>Monospace 16</default>
@@ -377,8 +377,22 @@
       </locale>
     </schema>
     <schema>
-      <key>/schemas/desktop/ibus/panel/emoji_favorites</key>
-      <applyto>/desktop/ibus/panel/emoji_favorites</applyto>
+      <key>/schemas/desktop/ibus/panel/emoji/lang</key>
+      <applyto>/desktop/ibus/panel/emoji/lang</applyto>
+      <owner>ibus</owner>
+      <type>string</type>
+      <default>en</default>
+      <locale name="C">
+        <short>Default language for emoji dictionary</short>
+	    <long>Choose a default language of emoji dictionaries on
+	          the emoji dialog. The value $lang is applied to
+                  /usr/share/unicode/cldr/common/annotations/$lang.xml
+            </long>
+      </locale>
+    </schema>
+    <schema>
+      <key>/schemas/desktop/ibus/panel/emoji/favorites</key>
+      <applyto>/desktop/ibus/panel/emoji/favorites</applyto>
       <owner>ibus</owner>
       <type>list</type>
       <default>[]</default>
diff --git a/setup/Makefile.am b/setup/Makefile.am
index 2d822d2..b7dd755 100644
--- a/setup/Makefile.am
+++ b/setup/Makefile.am
@@ -3,7 +3,8 @@
 # ibus - The Input Bus
 #
 # Copyright (c) 2007-2014 Peng Huang <shawn.p.huang@gmail.com>
-# Copyright (c) 2007-2014 Red Hat, Inc.
+# Copyright (c) 2015-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2007-2017 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
@@ -21,19 +22,20 @@
 # USA
 
 ibussetup_PYTHON = \
-	main.py \
-	i18n.py \
-	icon.py \
-	enginecombobox.py \
-	enginedialog.py \
-	enginetreeview.py \
-	engineabout.py \
-	keyboardshortcut.py \
-	$(NULL)
+    emojilang.py \
+    enginecombobox.py \
+    enginedialog.py \
+    enginetreeview.py \
+    engineabout.py \
+    i18n.py \
+    icon.py \
+    keyboardshortcut.py \
+    main.py \
+    $(NULL)
 
 ibussetup_DATA = \
-	setup.ui \
-	$(NULL)
+    setup.ui \
+    $(NULL)
 
 bin_SCRIPTS = ibus-setup
 ibussetupdir = $(pkgdatadir)/setup
diff --git a/setup/emojilang.py b/setup/emojilang.py
new file mode 100644
index 0000000..8250589
--- /dev/null
+++ b/setup/emojilang.py
@@ -0,0 +1,348 @@
+# vim:set et sts=4 sw=4:
+# -*- coding: utf-8 -*-
+#
+# ibus - The Input Bus
+#
+# Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# for python2
+from __future__ import print_function
+
+__all__ = (
+    "EmojiLangButton",
+);
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import IBus
+
+import functools
+import gettext
+import i18n
+import locale
+import os
+
+from icon import load_icon
+from i18n import _, N_
+
+ROW_TRAVEL_DIRECTION_NONE,      \
+ROW_TRAVEL_DIRECTION_FORWARD,   \
+ROW_TRAVEL_DIRECTION_BACKWARD = list(range(3))
+
+class LanguageString:
+    def __init__(self, id, trans = ""):
+        self.id = id
+        self.trans = trans
+
+class EmojiLangChooser(Gtk.Dialog):
+    __gtype_name__ = 'EmojiLangChooser'
+    __initial_languages = [ IBus.get_language_name('en_US'),
+                            IBus.get_language_name('en_GB'),
+                            IBus.get_language_name('de_DE'),
+                            IBus.get_language_name('fr_FR'),
+                            IBus.get_language_name('es_ES'),
+                            IBus.get_language_name('zh_CN'),
+                            IBus.get_language_name('ja_JP'),
+                            IBus.get_language_name('ru_RU'),
+                            IBus.get_language_name('ar_EG') ]
+
+
+    def __init__(self, id = None, transient_for = None):
+        super(EmojiLangChooser, self).__init__(
+                title = _("Select a language"),
+                transient_for = transient_for,
+                resizable = True)
+        buttons = (_("_Cancel"), Gtk.ResponseType.CANCEL,
+                   _("_OK"), Gtk.ResponseType.APPLY)
+        self.add_buttons(*buttons)
+
+        if id == None:
+            id = 'en'
+        self.__id = id
+        self.__engines_for_lang = {}
+        self.__untrans_for_lang = {}
+        self.__langs = {}
+        self.__lang_list = []
+
+        self.__scrolled = Gtk.ScrolledWindow(
+                hscrollbar_policy = Gtk.PolicyType.NEVER,
+                vscrollbar_policy = Gtk.PolicyType.NEVER,
+                shadow_type = Gtk.ShadowType.IN,
+                margin_left = 6,
+                margin_right = 6,
+                margin_top = 6,
+                margin_bottom = 6)
+        self.vbox.add(self.__scrolled)
+        viewport = Gtk.Viewport()
+        self.__scrolled.add(viewport)
+        self.__list = Gtk.ListBox(vexpand = True,
+                                  halign = Gtk.Align.FILL,
+                                  valign = Gtk.Align.FILL)
+        viewport.add(self.__list)
+
+        self.__adjustment = self.__scrolled.get_vadjustment()
+        self.__list.set_adjustment(self.__adjustment)
+        self.__list.set_filter_func(self.__list_filter, None)
+        self.__list.connect('row-activated', self.__row_activated)
+
+        self.__showing_extra = False
+        self.__more_row = self.__more_row_new()
+        self.__load_lang_list()
+        self.__show_lang_rows()
+        self.show_all()
+
+
+    def __load_lang_list(self):
+        dictdir = os.path.dirname(__file__) + '/../dicts'
+        for filename in os.listdir(dictdir):
+            suffix = '.dict'
+            if not filename.endswith(suffix):
+                continue
+            lang_id = filename[0:len(filename) - len(suffix)]
+            prefix = 'emoji-'
+            if not lang_id.startswith(prefix):
+                continue
+            lang_id = lang_id[len(prefix):]
+            lang = LanguageString(lang_id, IBus.get_language_name(lang_id))
+            self.__lang_list.append(lang)
+        if len(self.__lang_list) == 0:
+            print("Not found dicts in %s" % dictdir, file=sys.stderr)
+            lang = LanguageString('en', IBus.get_language_name('en'))
+            self.__lang_list.append(lang)
+            return
+
+        def cmp_lang(a, b):
+            label_a = a.trans + a.id
+            label_b = b.trans + b.id
+            return (label_a > label_b) - (label_a < label_b)
+
+        self.__lang_list.sort(key = functools.cmp_to_key(cmp_lang))
+
+        loc = locale.getlocale()[0]
+        # None on C locale
+        if loc == None or loc == 'C':
+            loc = 'en_US'
+        index = 0
+        for lang in self.__lang_list:
+            # move current language to the first place
+            if lang.trans == IBus.get_language_name(loc):
+                self.__lang_list.remove(lang)
+                self.__lang_list.insert(index, lang)
+                index += 1
+
+        for lang in self.__lang_list:
+            # move English to the second place
+            if lang.trans == IBus.get_language_name('en'):
+                self.__lang_list.remove(lang)
+                self.__lang_list.insert(index, lang)
+                index += 1
+
+
+    def __list_filter(self, row, data):
+        if row.id == self.__id:
+            self.__list.select_row(row)
+        if row == self.__more_row:
+            return not self.__showing_extra
+        if not self.__showing_extra and row.is_extra:
+            return False
+        return True
+
+
+    def __row_activated(self, box, row):
+        if row == self.__more_row:
+            self.__show_more()
+            return
+        self.__id = row.id
+
+
+    def __padded_label_new(self, text, icon, alignment, direction):
+        hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
+
+        if direction == ROW_TRAVEL_DIRECTION_BACKWARD:
+            rtl = (Gtk.Widget.get_default_direction() == \
+                   Gtk.TextDirection.RTL)
+            if rtl:
+                arrow = Gtk.Image.new_from_icon_name(
+                    'go-previous-rtl-symbolic', Gtk.IconSize.MENU)
+            else:
+                arrow = Gtk.Image.new_from_icon_name(
+                    'go-previous-symbolic', Gtk.IconSize.MENU)
+            hbox.pack_start(arrow, False, True, 0)
+
+        if icon != None:
+            pixbuf = load_icon(icon, Gtk.IconSize.LARGE_TOOLBAR)
+            image = Gtk.Image(pixbuf = pixbuf)
+            hbox.pack_start(image, False, True, 0)
+
+        label = Gtk.Label(label = text)
+        label.set_halign(alignment)
+        label.set_valign(Gtk.Align.CENTER)
+        label.set_margin_left(20)
+        label.set_margin_right(20)
+        label.set_margin_top(6)
+        label.set_margin_bottom(6)
+        hbox.pack_start(label, True, True, 0)
+        return hbox
+
+
+    def __list_box_row_new(self, lang):
+        row = Gtk.ListBoxRow()
+        row.trans = lang.trans
+        row.id = lang.id
+        row.is_extra = False
+        return row
+
+
+    def __lang_row_new(self, lang, prev_lang):
+        row = self.__list_box_row_new(lang)
+        label = lang.trans
+        if lang.id == self.__id:
+            row.is_extra = False
+        elif prev_lang != None and label == prev_lang.trans:
+            label = "%s (%s)" % (lang.trans, lang.id)
+            row.is_extra = True
+        elif not self.__showing_extra and \
+           lang.trans not in self.__initial_languages:
+            row.is_extra = True
+        widget = self.__padded_label_new(label,
+                                         None,
+                                         Gtk.Align.CENTER,
+                                         ROW_TRAVEL_DIRECTION_NONE)
+        row.add(widget)
+        return row
+
+
+    def __more_row_new(self):
+        row = Gtk.ListBoxRow()
+        row.id = None
+        hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
+        row.add(hbox)
+        row.set_tooltip_text(_("More…"))
+        arrow = Gtk.Image.new_from_icon_name('view-more-symbolic',
+                                             Gtk.IconSize.MENU)
+        arrow.set_margin_left(20)
+        arrow.set_margin_right(20)
+        arrow.set_margin_top(6)
+        arrow.set_margin_bottom(6)
+        arrow.set_halign(Gtk.Align.CENTER)
+        arrow.set_valign(Gtk.Align.CENTER)
+        hbox.pack_start(arrow, True, True, 0)
+        return row
+
+
+    def __set_fixed_size(self):
+        if self.__scrolled.get_policy()[0] == Gtk.PolicyType.AUTOMATIC:
+            return
+        (width, height) = self.get_size()
+        self.set_size_request(width, height)
+        self.__scrolled.set_policy(Gtk.PolicyType.AUTOMATIC,
+                                   Gtk.PolicyType.AUTOMATIC)
+
+
+    def __remove_all_children(self):
+        for l in self.__list.get_children():
+            self.__list.remove(l)
+
+
+    def __show_lang_rows(self):
+        self.__remove_all_children()
+        prev_lang = None
+        for lang in self.__lang_list:
+            row = self.__lang_row_new(lang, prev_lang)
+            self.__list.add(row)
+            prev_lang = lang
+        self.__list.add(self.__more_row)
+        self.__list.show_all()
+        self.__adjustment.set_value(self.__adjustment.get_lower())
+        self.__list.invalidate_filter()
+        self.__list.set_selection_mode(Gtk.SelectionMode.SINGLE)
+
+
+    def __show_more(self):
+        self.__set_fixed_size()
+        self.__showing_extra = True
+        self.__list.invalidate_filter()
+
+
+    def get_selected_lang(self):
+        return self.__id
+
+
+class EmojiLangButton(Gtk.Button):
+    __gtype_name__ = 'EmojiLangButton'
+    __gproperties__ = {
+        'lang' : (
+            str,
+            'lang',
+            'lang for emojo-*.dict',
+            'en',
+            GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE)
+    }
+
+
+    def __init__(self):
+        super(EmojiLangButton, self).__init__()
+        self.__lang = ''
+
+
+    def do_get_property(self, prop):
+        if prop.name == 'lang':
+            return self.__lang
+        else:
+            raise AttributeError('unknown property %s' % prop.name)
+
+
+    def do_set_property(self, prop, value):
+        if prop.name == 'lang':
+            self.set_lang(value)
+        else:
+            raise AttributeError('unknown property %s' % prop.name)
+
+
+    def do_clicked(self):
+        dialog = EmojiLangChooser(id = self.__lang,
+                                  transient_for = self.get_toplevel())
+        id = dialog.run()
+        if id != Gtk.ResponseType.APPLY:
+            dialog.destroy()
+            return
+        self.set_lang(dialog.get_selected_lang())
+        dialog.destroy()
+
+
+    def set_lang(self, lang):
+        self.__lang = lang
+        self.notify("lang")
+        self.set_label(IBus.get_language_name(lang))
+
+
+    def get_lang(self, lang):
+        return self.__lang
+
+
+GObject.type_register(EmojiLangButton)
+
+
+if __name__ == "__main__":
+        dialog = EmojiLangChooser()
+        id = dialog.run()
+        if id != Gtk.ResponseType.APPLY:
+            dialog.destroy()
+            import sys
+            sys.exit(0)
+        print("Selected language:", dialog.get_selected_lang())
diff --git a/setup/main.py b/setup/main.py
index 09b0ebd..7839cea 100644
--- a/setup/main.py
+++ b/setup/main.py
@@ -51,6 +51,7 @@ from os import path
 import i18n
 import keyboardshortcut
 import locale
+from emojilang import EmojiLangButton
 from enginecombobox import EngineComboBox
 from enginedialog import EngineDialog
 from enginetreeview import EngineTreeView
@@ -92,6 +93,8 @@ class Setup(object):
                 schema = "org.freedesktop.ibus.general.hotkey");
         self.__settings_panel = Gio.Settings(
                 schema = "org.freedesktop.ibus.panel");
+        self.__settings_emoji = Gio.Settings(
+                schema = "org.freedesktop.ibus.panel.emoji");
 
         # IBus.Bus() calls ibus_bus_new().
         # Gtk.Builder().add_from_file() also calls ibus_bus_new_async()
@@ -122,7 +125,10 @@ class Setup(object):
         self.__init_hotkey(name, label)
 
     def __init_hotkey(self, name, label, comment=None):
-        shortcuts = self.__settings_hotkey.get_strv(name)
+        if name == 'emoji':
+            shortcuts = self.__settings_emoji.get_strv('hotkey')
+        else:
+            shortcuts = self.__settings_hotkey.get_strv(name)
         button = self.__builder.get_object("button_%s" % label)
         entry = self.__builder.get_object("entry_%s" % label)
         entry.set_text("; ".join(shortcuts))
@@ -130,8 +136,12 @@ class Setup(object):
         if comment != None:
             tooltip += "\n" + comment
         entry.set_tooltip_text(tooltip)
-        button.connect("clicked", self.__shortcut_button_clicked_cb,
-                name, "general/hotkey", label, entry)
+        if name == 'emoji':
+            button.connect("clicked", self.__shortcut_button_clicked_cb,
+                    'hotkey', 'panel/' + name, label, entry)
+        else:
+            button.connect("clicked", self.__shortcut_button_clicked_cb,
+                    name, "general/hotkey", label, entry)
 
     def __init_panel(self):
         # lookup table orientation
@@ -169,21 +179,27 @@ class Setup(object):
 
         self.__fontbutton_custom_font = self.__builder.get_object(
                 "fontbutton_custom_font")
-        self.__fontbutton_emoji_font = self.__builder.get_object(
-                "fontbutton_emoji_font")
-        self.__fontbutton_emoji_font.set_preview_text("πŸ™‚πŸŽπŸšƒπŸ’“πŸ“§βš½πŸ³");
         self.__settings_panel.bind('custom-font',
                                     self.__fontbutton_custom_font,
                                    'font-name',
                                    Gio.SettingsBindFlags.DEFAULT)
-        self.__settings_panel.bind('emoji-font',
-                                    self.__fontbutton_emoji_font,
-                                   'font-name',
-                                   Gio.SettingsBindFlags.DEFAULT)
         self.__settings_panel.bind('use-custom-font',
                                     self.__fontbutton_custom_font,
                                    'sensitive',
                                    Gio.SettingsBindFlags.GET)
+        self.__fontbutton_emoji_font = self.__builder.get_object(
+                'fontbutton_emoji_font')
+        self.__fontbutton_emoji_font.set_preview_text('πŸ™‚πŸŽπŸšƒπŸ’“πŸ“§βš½πŸ³');
+        self.__settings_emoji.bind('font',
+                                    self.__fontbutton_emoji_font,
+                                   'font-name',
+                                   Gio.SettingsBindFlags.DEFAULT)
+        self.__button_emoji_lang = self.__builder.get_object(
+                'button_emoji_lang')
+        self.__settings_emoji.bind('lang',
+                                    self.__button_emoji_lang,
+                                   'lang',
+                                   Gio.SettingsBindFlags.DEFAULT)
 
         # show icon on system tray
         self.__checkbutton_show_icon_on_systray = self.__builder.get_object(
@@ -458,7 +474,10 @@ class Setup(object):
         dialog.destroy()
         if id != Gtk.ResponseType.OK:
             return
-        self.__settings_hotkey.set_strv(name, shortcuts)
+        if section == 'panel/emoji':
+            self.__settings_emoji.set_strv(name, shortcuts)
+        else:
+            self.__settings_hotkey.set_strv(name, shortcuts)
         text = "; ".join(shortcuts)
         entry.set_text(text)
         tooltip = "\n".join(shortcuts)
diff --git a/setup/setup.ui b/setup/setup.ui
index d5ee392..4ef3423 100644
--- a/setup/setup.ui
+++ b/setup/setup.ui
@@ -661,38 +661,11 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkBox" id="fontbutton_box">
-                            <property name="orientation">vertical</property>
+                          <object class="GtkFontButton" id="fontbutton_custom_font">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <child>
-                              <object class="GtkFontButton" id="fontbutton_custom_font">
-                              <property name="use_action_appearance">False</property>
-                              <property name="visible">True</property>
-                              <property name="can_focus">True</property>
-                              <property name="receives_default">True</property>
-                              <property name="use_action_appearance">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkFontButton" id="fontbutton_emoji_font">
-                              <property name="use_action_appearance">False</property>
-                              <property name="visible">True</property>
-                              <property name="can_focus">True</property>
-                              <property name="receives_default">True</property>
-                              <property name="use_action_appearance">False</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="use_action_appearance">False</property>
                           </object>
                           <packing>
                             <property name="left_attach">1</property>
@@ -702,6 +675,68 @@
                             <property name="y_options">GTK_FILL</property>
                           </packing>
                         </child>
+                        <child>
+                          <object class="GtkLabel" id="label_emoji_font">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="tooltip_text" translatable="yes">Set a font of emoji candidates on the emoji dialog</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Emoji font:</property>
+                            <property name="justify">right</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">7</property>
+                            <property name="bottom_attach">8</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFontButton" id="fontbutton_emoji_font">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="use_action_appearance">False</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">7</property>
+                            <property name="bottom_attach">8</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label_emoji_lang">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="tooltip_text" translatable="yes">Set a language of emoji annotations on the emoji dialog</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Emoji annotation language:</property>
+                            <property name="justify">right</property>
+                          </object>
+                          <packing>
+                            <property name="top_attach">8</property>
+                            <property name="bottom_attach">9</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="EmojiLangButton" id="button_emoji_lang">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="use_action_appearance">False</property>
+                          </object>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">8</property>
+                            <property name="bottom_attach">9</property>
+                            <property name="y_options">GTK_FILL</property>
+                          </packing>
+                        </child>
                       </object>
                     </child>
                     <child type="label">
diff --git a/tools/main.vala b/tools/main.vala
index fd9fd0e..2bf1dc7 100644
--- a/tools/main.vala
+++ b/tools/main.vala
@@ -31,6 +31,8 @@ bool name_only = false;
 /* system() exists as a public API. */
 bool is_system = false;
 string cache_file = null;
+string emoji_font = null;
+string annotation_lang = null;
 
 class EngineList {
     public IBus.EngineDesc[] data = {};
@@ -342,12 +344,40 @@ private void run_dialog(IBus.Emojier emojier) {
 }
 
 int emoji_dialog(string[] argv) {
+    const OptionEntry[] options = {
+        { "font", 0, 0, OptionArg.STRING, out emoji_font,
+          N_("FONT for emoji chracters on emoji dialog."), "FONT" },
+        { "lang", 0, 0, OptionArg.STRING, out annotation_lang,
+          N_("LANG for annotations on emoji dialog. E.g. \"en\""), "LANG" },
+        { null }
+    };
+
+    var option = new OptionContext();
+    option.add_main_entries(options, Config.GETTEXT_PACKAGE);
+
+    try {
+        option.parse(ref argv);
+    } catch (OptionError e) {
+        stderr.printf("%s\n", e.message);
+        return Posix.EXIT_FAILURE;
+    }
+
     Gtk.init(ref argv);
-    GLib.Settings settings_panel =
-            new GLib.Settings("org.freedesktop.ibus.panel");
-    string emoji_font = settings_panel.get_string("emoji-font");
+    if (emoji_font == null) {
+        GLib.Settings settings_emoji =
+                new GLib.Settings("org.freedesktop.ibus.panel.emoji");
+        emoji_font = settings_emoji.get_string("font");
+    }
+    if (annotation_lang == null) {
+        GLib.Settings settings_emoji =
+                new GLib.Settings("org.freedesktop.ibus.panel.emoji");
+        annotation_lang = settings_emoji.get_string("lang");
+    }
     IBus.Emojier emojier = new IBus.Emojier();
-    emojier.set_emoji_font(emoji_font);
+    if (emoji_font != null && emoji_font != "")
+        emojier.set_emoji_font(emoji_font);
+    if (annotation_lang != null && annotation_lang != "")
+        emojier.set_annotation_lang(annotation_lang);
     if (emojier.has_loaded_emoji_dict()) {
         run_dialog(emojier);
     } else {
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index b1dc50c..20c1378 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -42,14 +42,11 @@ class IBusEmojier : Gtk.Window {
         }
     }
     private class EBoxRow : Gtk.ListBoxRow {
-        public EBoxRow(string text,
-                       string id="") {
+        public EBoxRow(string text) {
             this.text = text;
-            this.id = id;
         }
 
         public string text { get; set; }
-        public string id { get; set; }
     }
     private class EScrolledWindow : Gtk.ScrolledWindow {
         public EScrolledWindow(Gtk.Adjustment? hadjustment=null,
@@ -132,6 +129,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
     private class ETitleLabel : Gtk.Box {
+        private Gtk.Label m_label;
         private Gtk.Button m_close_button;
         private ulong m_close_handler;
 
@@ -142,14 +140,14 @@ class IBusEmojier : Gtk.Window {
                 orientation : Gtk.Orientation.HORIZONTAL,
                 spacing : 0
             );
-            Gtk.Label label = new Gtk.Label(text);
-            label.set_halign(align);
-            label.set_valign(align);
-            label.set_margin_start(20);
-            label.set_margin_end(20);
-            label.set_margin_top(6);
-            label.set_margin_bottom(6);
-            pack_start(label, true, true, 0);
+            m_label = new Gtk.Label(text);
+            m_label.set_halign(align);
+            m_label.set_valign(align);
+            m_label.set_margin_start(20);
+            m_label.set_margin_end(20);
+            m_label.set_margin_top(6);
+            m_label.set_margin_bottom(6);
+            pack_start(m_label, true, true, 0);
             IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU);
             m_close_button = new Gtk.Button();
             m_close_button.add(icon);
@@ -170,6 +168,9 @@ class IBusEmojier : Gtk.Window {
                 m_close_handler = 0;
             }
         }
+        public void set_label(string str) {
+            m_label.set_label(str);
+        }
     }
 
     private enum TravelDirection {
@@ -177,11 +178,6 @@ class IBusEmojier : Gtk.Window {
         BACKWARD,
     }
 
-    private enum CategoryType {
-        EMOJI,
-        LANG,
-    }
-
     private const uint EMOJI_GRID_PAGE = 10;
     private ThemedRGBA m_rgba;
     private Gtk.Box m_vbox;
@@ -190,14 +186,11 @@ class IBusEmojier : Gtk.Window {
     private string? m_backward;
     private EScrolledWindow? m_scrolled_window = null;
     private EListBox m_list_box;
-    private CategoryType m_current_category_type = CategoryType.EMOJI;
     private bool m_is_running = false;
     private string m_input_context_path = "";
     private GLib.MainLoop? m_loop;
     private string? m_result;
-    private GLib.SList<string> m_lang_list;
     private string m_current_lang_id = "en";
-    private string m_current_language = "English";
     private string? m_unicode_point = null;
     private bool m_candidate_panel_is_visible;
     private GLib.HashTable<string, GLib.SList>?
@@ -215,6 +208,7 @@ class IBusEmojier : Gtk.Window {
     private bool m_enter_notify_enable = true;
     private uint m_entry_notify_show_id;
     private uint m_entry_notify_disable_id;
+    private uint m_reload_emoji_dict_id;
 
     public signal void candidate_clicked(uint index, uint button, uint state);
     public signal void loaded_emoji_dict();
@@ -323,50 +317,13 @@ class IBusEmojier : Gtk.Window {
             hide_candidate_panel();
         });
 
-        GLib.Idle.add(() => {
-            m_lang_list = read_lang_list();
+        m_reload_emoji_dict_id = GLib.Idle.add(() => {
             reload_emoji_dict();
+            m_reload_emoji_dict_id = 0;
             return false;
         });
     }
 
-    private GLib.SList<string> read_lang_list() {
-        GLib.SList<string> lang_list = new GLib.SList<string>();
-        const string dict_path = Config.PKGDATADIR + "/dicts";
-        GLib.Dir dir = null;
-        try {
-            dir = GLib.Dir.open(dict_path);
-        } catch (GLib.FileError e) {
-            warning("Error loading %s: %s", dict_path, e.message);
-            return lang_list;
-        }
-        string name;
-        while ((name = dir.read_name()) != null) {
-            const string dict_suffix = ".dict";
-            const string dict_prefix = "emoji-";
-            if (name.has_suffix(dict_suffix)) {
-                name = name[0:name.length - dict_suffix.length];
-                if (name.has_prefix(dict_prefix)) {
-                    name = name[dict_prefix.length:name.length];
-                    lang_list.append(name);
-                } else {
-                    warning("Need %s prefix in the filename: %s/%s%s",
-                            dict_prefix, dict_path, name, dict_suffix);
-                }
-            } else {
-                warning("Need %s extention in the filename: %s/%s",
-                        dict_suffix, dict_path, name);
-            }
-        }
-        lang_list.sort((a, b) => {
-            string a_lang = IBus.get_language_name(a);
-            string b_lang = IBus.get_language_name(b);
-            a_lang = "%s (%s)".printf(a_lang, a);
-            b_lang = "%s (%s)".printf(b_lang, b);
-            return GLib.strcmp(a_lang, b_lang);
-        });
-        return lang_list;
-    }
 
     private void reload_emoji_dict() {
         init_emoji_dict();
@@ -382,6 +339,7 @@ class IBusEmojier : Gtk.Window {
         loaded_emoji_dict();
     }
 
+
     private void init_emoji_dict() {
         m_annotation_to_emojis_dict =
                 new GLib.HashTable<string, GLib.SList>(GLib.str_hash,
@@ -394,6 +352,7 @@ class IBusEmojier : Gtk.Window {
                                                        GLib.str_equal);
     }
 
+
     private void make_emoji_dict(string lang) {
         GLib.SList<IBus.EmojiData> emoji_list = IBus.EmojiData.load(
                     Config.PKGDATADIR + "/dicts/emoji-" + lang + ".dict");
@@ -412,6 +371,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     private void update_annotation_to_emojis_dict(IBus.EmojiData data) {
         string emoji = data.get_emoji();
         unowned GLib.SList<string> annotations = data.get_annotations();
@@ -432,6 +392,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     private string utf8_down(string str) {
         GLib.StringBuilder buff = new GLib.StringBuilder();
         int length = str.char_count();
@@ -442,6 +403,7 @@ class IBusEmojier : Gtk.Window {
         return buff.str;
     }
 
+
     private string utf8_title(string str) {
         StringBuilder buff = new StringBuilder();
         int length = str.char_count();
@@ -456,6 +418,7 @@ class IBusEmojier : Gtk.Window {
         return buff.str;
     }
 
+
     private void update_emoji_to_data_dict(IBus.EmojiData data,
                                            string         lang) {
         string emoji = data.get_emoji();
@@ -464,10 +427,11 @@ class IBusEmojier : Gtk.Window {
             unowned GLib.SList<string> annotations = data.get_annotations();
             var words = description.split(" ");
             // If the description has less than 3 words, add it to annotations
+            // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
             if (words.length < 3 &&
                 annotations.find_custom(
                         description,
-                        (GLib.CompareFunc<string>)GLib.strcmp) == null) {
+                        GLib.strcmp) == null) {
                 annotations.append(description);
                 data.set_annotations(annotations.copy_deep(GLib.strdup));
             }
@@ -485,18 +449,20 @@ class IBusEmojier : Gtk.Window {
             unowned GLib.SList<string> annotations = data.get_annotations();
             var words = trans_description.split(" ");
             // If the description has less than 3 words, add it to annotations
+            // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
             if (words.length < 3 &&
                 annotations.find_custom(
                         trans_description,
-                        (GLib.CompareFunc<string>)GLib.strcmp) == null) {
+                        GLib.strcmp) == null) {
                 annotations.append(trans_description);
             }
             unowned GLib.SList<string> en_annotations
                 = en_data.get_annotations();
             foreach (string annotation in en_annotations) {
+                // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
                 if (annotations.find_custom(
                             annotation,
-                            (GLib.CompareFunc<string>)GLib.strcmp) == null) {
+                            GLib.strcmp) == null) {
                     annotations.append(annotation.dup());
                 }
             }
@@ -504,6 +470,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     private void update_category_to_emojis_dict(IBus.EmojiData data,
                                                 string         lang) {
         string emoji = data.get_emoji();
@@ -525,29 +492,12 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     private void set_fixed_size() {
-        if (!m_candidate_panel_is_visible &&
-            m_current_category_type == CategoryType.LANG) {
-            Gtk.PolicyType vpolicy;
-            m_scrolled_window.get_policy(null, out vpolicy);
-            if (vpolicy == Gtk.PolicyType.AUTOMATIC)
-                return;
-            int width, height;
-            get_size(out width, out height);
-            set_size_request(width, height);
-            if (m_scrolled_window != null) {
-                m_scrolled_window.set_policy(Gtk.PolicyType.NEVER,
-                                             Gtk.PolicyType.AUTOMATIC);
-            }
-        } else {
-            resize(20, 1);
-            if (m_scrolled_window != null) {
-                m_scrolled_window.set_policy(Gtk.PolicyType.NEVER,
-                                             Gtk.PolicyType.NEVER);
-            }
-        }
+        resize(20, 1);
     }
 
+
     private void remove_all_children() {
         foreach (Gtk.Widget w in m_vbox.get_children()) {
             if (w.name == "IBusEmojierEntry" ||
@@ -558,51 +508,16 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
-    private void activated_language(EBoxRow row) {
-        m_category_active_index = 0;
-        if (m_current_lang_id != row.id) {
-            m_current_lang_id = row.id;
-            m_current_language = row.text;
-            reload_emoji_dict();
-        }
-        m_current_category_type = CategoryType.EMOJI;
-        show_category_list();
-    }
 
     private void show_category_list() {
         remove_all_children();
         m_scrolled_window = new EScrolledWindow();
         set_fixed_size();
-        EPaddedLabel label;
-        if (m_current_category_type == CategoryType.EMOJI) {
-            label = new EPaddedLabel(m_current_language, Gtk.Align.CENTER);
-        } else if (m_current_category_type == CategoryType.LANG) {
-            label = new EPaddedLabel(m_current_language,
-                                     Gtk.Align.CENTER,
-                                     TravelDirection.BACKWARD);
-        } else {
-            label = new EPaddedLabel("", Gtk.Align.CENTER);
-        }
-        Gtk.Button button = new Gtk.Button();
-        button.add(label);
-        m_vbox.add(button);
-        button.show_all();
-        if (m_current_category_type == CategoryType.EMOJI) {
-            button.button_press_event.connect((e) => {
-                m_category_active_index = 0;
-                m_current_category_type = CategoryType.LANG;
-                show_category_list();
-                return true;
-            });
-        } else if (m_current_category_type == CategoryType.LANG) {
-            button.button_press_event.connect((e) => {
-                m_category_active_index = 0;
-                m_current_category_type = CategoryType.EMOJI;
-                show_category_list();
-                return true;
-            });
-        }
 
+        string language = "%s (%s)".printf(
+            _("Emoji Dialog"),
+            IBus.get_language_name(m_current_lang_id));
+        m_title.set_label(language);
         m_vbox.add(m_scrolled_window);
         Gtk.Viewport viewport = new Gtk.Viewport(null, null);
         m_scrolled_window.add(viewport);
@@ -611,59 +526,38 @@ class IBusEmojier : Gtk.Window {
         viewport.add(m_list_box);
         Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
         m_list_box.set_adjustment(adjustment);
-        if (m_current_category_type == CategoryType.EMOJI) {
-            m_list_box.row_activated.connect((box, gtkrow) => {
-                m_category_active_index = 0;
-                EBoxRow row = gtkrow as EBoxRow;
-                show_emoji_for_category(row);
-            });
+        m_list_box.row_activated.connect((box, gtkrow) => {
+            m_category_active_index = 0;
+            EBoxRow row = gtkrow as EBoxRow;
+            show_emoji_for_category(row);
+        });
 
-            uint n = 1;
-            if (m_favorites.length > 0) {
-                EBoxRow row = new EBoxRow("@favorites");
-                EPaddedLabel widget =
-                        new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER);
-                row.add(widget);
-                m_list_box.add(row);
-                if (n++ == m_category_active_index)
-                    m_list_box.select_row(row);
-            }
-            GLib.List<unowned string> categories =
-                    m_category_to_emojis_dict.get_keys();
-            categories.sort((a, b) => {
-                return GLib.strcmp(_(a), _(b));
-            });
-            foreach (unowned string category in categories) {
-                EBoxRow row = new EBoxRow(category);
-                string locale_category = _(category);
-                EPaddedLabel widget =
-                        new EPaddedLabel(utf8_title(locale_category),
-                                         Gtk.Align.CENTER);
-                row.add(widget);
-                m_list_box.add(row);
-                if (n++ == m_category_active_index)
-                    m_list_box.select_row(row);
-            }
-        } else if (m_current_category_type == CategoryType.LANG) {
-            m_list_box.row_activated.connect((box, gtkrow) => {
-                activated_language(gtkrow as EBoxRow);
-            });
-            uint n = 1;
-            string prev_language = null;
-            foreach (unowned string id in m_lang_list) {
-                string language = IBus.get_language_name(id);
-                if (prev_language == language)
-                    language = "%s (%s)".printf(language, id);
-                else
-                    prev_language = language;
-                EBoxRow row = new EBoxRow(language, id);
-                EPaddedLabel widget =
-                        new EPaddedLabel(language, Gtk.Align.CENTER);
-                row.add(widget);
-                m_list_box.add(row);
-                if (n++ == m_category_active_index)
-                    m_list_box.select_row(row);
-            }
+        uint n = 1;
+        if (m_favorites.length > 0) {
+            EBoxRow row = new EBoxRow("@favorites");
+            EPaddedLabel widget =
+                    new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER);
+            row.add(widget);
+            m_list_box.add(row);
+            if (n++ == m_category_active_index)
+                m_list_box.select_row(row);
+        }
+        GLib.List<unowned string> categories =
+                m_category_to_emojis_dict.get_keys();
+        // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
+        categories.sort((a, b) => {
+            return GLib.strcmp(_(a), _(b));
+        });
+        foreach (unowned string category in categories) {
+            EBoxRow row = new EBoxRow(category);
+            string locale_category = _(category);
+            EPaddedLabel widget =
+                    new EPaddedLabel(utf8_title(locale_category),
+                                     Gtk.Align.CENTER);
+            row.add(widget);
+            m_list_box.add(row);
+            if (n++ == m_category_active_index)
+                m_list_box.select_row(row);
         }
 
         m_scrolled_window.show_all();
@@ -673,6 +567,7 @@ class IBusEmojier : Gtk.Window {
         m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
     }
 
+
     private void show_emoji_for_category(EBoxRow row) {
         if (row.text == "@favorites") {
             m_lookup_table.clear();
@@ -694,6 +589,7 @@ class IBusEmojier : Gtk.Window {
         show_candidate_panel();
     }
 
+
     private void show_arrow_buttons() {
         Gtk.Button next_button = new Gtk.Button();
         next_button.clicked.connect(() => {
@@ -729,6 +625,7 @@ class IBusEmojier : Gtk.Window {
         buttons_hbox.show_all();
     }
 
+
     private bool check_unicode_point(string? annotation=null) {
         bool check_xdigit_only = true;
         if (annotation == null) {
@@ -758,6 +655,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     public void update_candidate_window() {
         string annotation = m_entry.get_text();
         if (annotation.length == 0) {
@@ -788,6 +686,7 @@ class IBusEmojier : Gtk.Window {
         show_candidate_panel();
     }
 
+
     private void show_candidate_panel() {
         remove_all_children();
         set_fixed_size();
@@ -848,6 +747,8 @@ class IBusEmojier : Gtk.Window {
                 // enter_notify_event conflicts with keyboard operations.
                 if (!m_enter_notify_enable)
                     return true;
+                if (m_lookup_table.get_cursor_pos() == index)
+                    return true;
                 m_lookup_table.set_cursor_pos(index);
                 if (m_entry_notify_show_id > 0) {
                         GLib.Source.remove(m_entry_notify_show_id);
@@ -927,6 +828,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     private void hide_candidate_panel() {
         m_enter_notify_enable = true;
         m_candidate_panel_is_visible = false;
@@ -934,6 +836,7 @@ class IBusEmojier : Gtk.Window {
             show_category_list();
     }
 
+
     private bool if_in_range_of_lookup(uint keyval) {
         if (!m_candidate_panel_is_visible)
             return false;
@@ -957,6 +860,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     private void set_number_on_lookup(uint keyval) {
         if (keyval == Gdk.Key.@0)
             keyval = Gdk.Key.@9 + 1;
@@ -969,6 +873,7 @@ class IBusEmojier : Gtk.Window {
         m_result = text.text;
     }
 
+
     private void enter_notify_disable_with_timer() {
         // Enable keyboard operation and disable mouse operation.
         m_enter_notify_enable = false;
@@ -982,6 +887,7 @@ class IBusEmojier : Gtk.Window {
         });
     }
 
+
     private void candidate_panel_cursor_down() {
         enter_notify_disable_with_timer();
         uint ncandidates = m_lookup_table.get_number_of_candidates();
@@ -996,6 +902,7 @@ class IBusEmojier : Gtk.Window {
         show_candidate_panel();
     }
 
+
     private void candidate_panel_cursor_up() {
         enter_notify_disable_with_timer();
         int ncandidates = (int)m_lookup_table.get_number_of_candidates();
@@ -1013,6 +920,7 @@ class IBusEmojier : Gtk.Window {
         show_candidate_panel();
     }
 
+
     private void category_list_cursor_move(uint keyval) {
         GLib.List<weak Gtk.Widget> list = m_list_box.get_children();
         if (keyval == Gdk.Key.Down) {
@@ -1027,6 +935,7 @@ class IBusEmojier : Gtk.Window {
         show_category_list();
     }
 
+
     private bool key_press_cursor_horizontal(uint keyval,
                                              uint modifiers) {
         assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right);
@@ -1061,6 +970,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     private bool key_press_cursor_vertical(uint keyval) {
         assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up);
 
@@ -1075,6 +985,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     private bool key_press_cursor_home_end(uint keyval,
                                            uint modifiers) {
         assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End);
@@ -1107,14 +1018,11 @@ class IBusEmojier : Gtk.Window {
         return false;
     }
 
+
     private bool key_press_cursor_escape() {
         if (m_candidate_panel_is_visible) {
             hide_candidate_panel();
             return true;
-        } else if (m_current_category_type == CategoryType.LANG) {
-            m_current_category_type = CategoryType.EMOJI;
-            show_candidate_panel();
-            return true;
         } else if (m_entry.get_text().length == 0) {
             m_loop.quit();
             hide_candidate_panel();
@@ -1124,6 +1032,7 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     private void entry_enter_keyval(uint keyval) {
         unichar ch = IBus.keyval_to_unicode(keyval);
         if (ch.iscntrl())
@@ -1145,6 +1054,7 @@ class IBusEmojier : Gtk.Window {
         m_entry.set_position(pos);
     }
 
+
     public string run(Gdk.Event event,
                       string    input_context_path) {
         assert (m_loop == null);
@@ -1178,7 +1088,6 @@ class IBusEmojier : Gtk.Window {
             keyboard = device.get_associated_device();
         }
 
-        m_current_category_type = CategoryType.EMOJI;
         show_category_list();
         m_entry.set_activates_default(true);
         show_all();
@@ -1229,12 +1138,14 @@ class IBusEmojier : Gtk.Window {
         return m_result;
     }
 
+
     /* override virtual functions */
     public override void show() {
         base.show();
         set_focus_visible(true);
     }
 
+
     public override bool key_press_event(Gdk.EventKey event) {
         uint keyval = event.keyval;
         uint modifiers = event.state;
@@ -1263,10 +1174,7 @@ class IBusEmojier : Gtk.Window {
             } else if (m_category_active_index > 0) {
                 Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row();
                 EBoxRow row = gtkrow as EBoxRow;
-                if (m_current_category_type == CategoryType.EMOJI)
-                    show_emoji_for_category(row);
-                else if (m_current_category_type == CategoryType.LANG)
-                    activated_language(row);
+                show_emoji_for_category(row);
             }
             return true;
         case Gdk.Key.BackSpace:
@@ -1366,27 +1274,33 @@ class IBusEmojier : Gtk.Window {
         return true;
     }
 
+
     public bool is_running() {
         return m_is_running;
     }
 
+
     public string get_input_context_path() {
         return m_input_context_path;
     }
 
+
     public string get_selected_string() {
         return m_result;
     }
 
+
     public void reset() {
         m_input_context_path = "";
         m_result = null;
     }
 
+
     public void set_emoji_font(string emoji_font) {
         m_emoji_font = emoji_font;
     }
 
+
     public void set_favorites(string[]? unowned_favorites) {
         m_favorites = {};
         foreach (string favorite in unowned_favorites) {
@@ -1394,6 +1308,7 @@ class IBusEmojier : Gtk.Window {
         }
     }
 
+
     public bool has_loaded_emoji_dict() {
         if (m_emoji_to_data_dict == null)
             return false;
@@ -1402,4 +1317,20 @@ class IBusEmojier : Gtk.Window {
             return false;
         return true;
     }
+
+
+    public void set_annotation_lang(string lang) {
+        if (m_current_lang_id == lang)
+            return;
+        if (m_reload_emoji_dict_id > 0) {
+            GLib.Source.remove(m_reload_emoji_dict_id);
+            m_reload_emoji_dict_id = 0;
+        }
+        m_current_lang_id = lang;
+        m_reload_emoji_dict_id = GLib.Idle.add(() => {
+            reload_emoji_dict();
+            m_reload_emoji_dict_id = 0;
+            return false;
+        });
+    }
 }
diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h
index c36060c..0f84a48 100644
--- a/ui/gtk3/ibusemojidialog.h
+++ b/ui/gtk3/ibusemojidialog.h
@@ -149,5 +149,16 @@ void          ibus_emojier_set_favorites          (IBusEmojier* self,
  */
 gboolean      ibus_emojier_has_loaded_emoji_dict  (IBusEmojier* self);
 
+/**
+ * ibus_emojier_set_annotation_lang:
+ * @self: An #IBusEmojier
+ * @lang: A langauge id for emoji annotations.
+ *
+ * Set a language id for emoji annotations. #IBusEmojier will load
+ * $PKGDATADIR/dicts/emoji-@lang.dict. The default is "en".
+ */
+void          ibus_emojier_set_annotation_lang    (IBusEmojier* self,
+                                                   const gchar* lang);
+
 G_END_DECLS
 #endif
diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
index 0982134..7350dcc 100644
--- a/ui/gtk3/panel.vala
+++ b/ui/gtk3/panel.vala
@@ -54,6 +54,7 @@ class Panel : IBus.PanelService {
     private GLib.Settings m_settings_general = null;
     private GLib.Settings m_settings_hotkey = null;
     private GLib.Settings m_settings_panel = null;
+    private GLib.Settings m_settings_emoji = null;
     private IconType m_icon_type = IconType.STATUS_ICON;
     private Indicator m_indicator;
 #if INDICATOR
@@ -161,6 +162,7 @@ class Panel : IBus.PanelService {
         m_settings_hotkey =
                 new GLib.Settings("org.freedesktop.ibus.general.hotkey");
         m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel");
+        m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji");
 
         m_settings_general.changed["preload-engines"].connect((key) => {
                 update_engines(m_settings_general.get_strv(key),
@@ -193,19 +195,10 @@ class Panel : IBus.PanelService {
                 bind_switch_shortcut();
         });
 
-        m_settings_hotkey.changed["emoji"].connect((key) => {
-                unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING);
-                bind_emoji_shortcut();
-        });
-
         m_settings_panel.changed["custom-font"].connect((key) => {
                 set_custom_font();
         });
 
-        m_settings_panel.changed["emoji-font"].connect((key) => {
-                set_custom_font();
-        });
-
         m_settings_panel.changed["use-custom-font"].connect((key) => {
                 set_custom_font();
         });
@@ -239,9 +232,22 @@ class Panel : IBus.PanelService {
                 set_property_icon_delay_time();
         });
 
-        m_settings_panel.changed["emoji-favorites"].connect((key) => {
+        m_settings_emoji.changed["hotkey"].connect((key) => {
+                unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING);
+                bind_emoji_shortcut();
+        });
+
+        m_settings_emoji.changed["font"].connect((key) => {
+                set_custom_font();
+        });
+
+        m_settings_emoji.changed["favorites"].connect((key) => {
                 set_emoji_favorites();
         });
+
+        m_settings_emoji.changed["lang"].connect((key) => {
+                set_emoji_lang();
+        });
     }
 
 #if INDICATOR
@@ -398,7 +404,7 @@ class Panel : IBus.PanelService {
 
     private void bind_emoji_shortcut() {
 #if EMOJI_DICT
-        string[] accelerators = m_settings_hotkey.get_strv("emoji");
+        string[] accelerators = m_settings_emoji.get_strv("hotkey");
 
         var keybinding_manager = KeybindingManager.get_instance();
 
@@ -584,9 +590,9 @@ class Panel : IBus.PanelService {
             return;
         }
 
-        string emoji_font = m_settings_panel.get_string("emoji-font");
+        string emoji_font = m_settings_emoji.get_string("font");
         if (emoji_font == null) {
-            warning("No config panel:emoji-font.");
+            warning("No config emoji:font.");
             return;
         }
         m_emojier.set_emoji_font(emoji_font);
@@ -760,7 +766,11 @@ class Panel : IBus.PanelService {
     }
 
     private void set_emoji_favorites() {
-        m_emojier.set_favorites(m_settings_panel.get_strv("emoji-favorites"));
+        m_emojier.set_favorites(m_settings_emoji.get_strv("favorites"));
+    }
+
+    private void set_emoji_lang() {
+        m_emojier.set_annotation_lang(m_settings_emoji.get_string("lang"));
     }
 
     private int compare_versions(string version1, string version2) {
@@ -877,6 +887,7 @@ class Panel : IBus.PanelService {
         set_xkb_icon_rgba();
         set_property_icon_delay_time();
         set_emoji_favorites();
+        set_emoji_lang();
     }
 
     private void engine_contexts_insert(IBus.EngineDesc engine) {
-- 
2.9.3