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

From 164300758c70fd3a590788e038de31b6c53d458a Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 28 Mar 2017 12:15:26 +0900
Subject: [PATCH] data: Fix a typo

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/321800043
---
 data/ibus.schemas.in | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in
index c0bbd6f..096dd71 100644
--- a/data/ibus.schemas.in
+++ b/data/ibus.schemas.in
@@ -386,7 +386,7 @@
         <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
+                  /usr/share/ibus/dicts/emoji-$lang.dict
             </long>
       </locale>
     </schema>
-- 
2.9.3

From c3168d4701eb4e89094249abaa4f0f83ab24149b Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 29 Mar 2017 13:01:28 +0900
Subject: [PATCH] Fix IBusEmojiDialog_1_0_gir_LIBS for --as-needed LDFLAGS

The order gets omitted libibus-1.0.la

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

Review URL: https://codereview.appspot.com/324720043
---
 ui/gtk3/Makefile.am | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index b055f67..7122ff3 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -205,7 +205,7 @@ IBusEmojiDialog_1_0_gir_SCANNERFLAGS = \
     $(NULL)
 IBusEmojiDialog-1.0.gir: $(libibus_emoji_dialog) Makefile
 IBusEmojiDialog_1_0_gir_INCLUDES = Gtk-3.0 GLib-2.0 GObject-2.0 Gio-2.0
-IBusEmojiDialog_1_0_gir_LIBS = $(libibus) $(libibus_emoji_dialog)
+IBusEmojiDialog_1_0_gir_LIBS = $(libibus_emoji_dialog) $(libibus)
 IBusEmojiDialog_1_0_gir_FILES =                      \
     $(addprefix $(srcdir)/,$(introspection_sources)) \
     $(NULL)
-- 
2.9.3

From 8b6f9fa531aa9d9b5d6c0184b2294d071f920d7f Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Thu, 30 Mar 2017 12:08:39 +0900
Subject: [PATCH] ui/gtk3: Enable to type multiple code points on Emojier

- Can type multiple Unicode characters with digits and Shift-space
  keys.  E.g. "1f468 1f468 1f466"
- Always show Unicode points of the selected emoji
- Removed a function to commit an emoji by typing a digit char since
  it conflicts with digit annotations, code points but it had enabled
  with single digits only so not so useful.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/321820043
---
 ui/gtk3/emojier.vala | 207 ++++++++++++++++++++++++---------------------------
 1 file changed, 98 insertions(+), 109 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 20c1378..8a2726c 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -97,12 +97,27 @@ class IBusEmojier : Gtk.Window {
                 set_label(text);
         }
     }
-    private class EPaddedLabel : Gtk.Box {
+    private class EPaddedLabel : Gtk.Label {
         public EPaddedLabel(string          text,
-                            Gtk.Align       align,
-                            TravelDirection direction=TravelDirection.NONE) {
+                            Gtk.Align       align) {
             GLib.Object(
                 name : "IBusEmojierPaddedLabel",
+                halign : align,
+                valign : Gtk.Align.CENTER,
+                margin_start : 20,
+                margin_end : 20,
+                margin_top : 6,
+                margin_bottom : 6
+            );
+            set_text(text);
+        }
+    }
+    private class EPaddedLabelBox : Gtk.Box {
+        public EPaddedLabelBox(string          text,
+                               Gtk.Align       align,
+                               TravelDirection direction=TravelDirection.NONE) {
+            GLib.Object(
+                name : "IBusEmojierPaddedLabelBox",
                 orientation : Gtk.Orientation.HORIZONTAL,
                 spacing : 0
             );
@@ -118,36 +133,29 @@ class IBusEmojier : Gtk.Window {
                 }
                 pack_start(icon, false, true, 0);
             }
-            Gtk.Label label = new Gtk.Label(text);
-            label.set_halign(align);
-            label.set_valign(Gtk.Align.CENTER);
-            label.set_margin_start(20);
-            label.set_margin_end(20);
-            label.set_margin_top(6);
-            label.set_margin_bottom(6);
+            EPaddedLabel label = new EPaddedLabel(text, align);
             pack_start(label, true, true, 0);
         }
     }
-    private class ETitleLabel : Gtk.Box {
-        private Gtk.Label m_label;
+    private class ETitleLabelBox : Gtk.Box {
+        EPaddedLabel m_lang_label;
         private Gtk.Button m_close_button;
         private ulong m_close_handler;
 
-        public ETitleLabel(string    text,
-                           Gtk.Align align) {
+        public ETitleLabelBox(string    text,
+                              Gtk.Align align) {
             GLib.Object(
-                name : "IBusEmojierTitleLabel",
+                name : "IBusEmojierTitleLabelBox",
                 orientation : Gtk.Orientation.HORIZONTAL,
                 spacing : 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);
+            EPaddedLabel label = new EPaddedLabel(text, align);
+            pack_start(label, true, true, 0);
+            Gtk.Separator separator =
+                    new Gtk.Separator (Gtk.Orientation.VERTICAL);
+            pack_start(separator, false, true, 0);
+            m_lang_label = new EPaddedLabel("", align);
+            pack_start(m_lang_label, false, true, 0);
             IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU);
             m_close_button = new Gtk.Button();
             m_close_button.add(icon);
@@ -168,8 +176,8 @@ class IBusEmojier : Gtk.Window {
                 m_close_handler = 0;
             }
         }
-        public void set_label(string str) {
-            m_label.set_label(str);
+        public void set_lang_label(string str) {
+            m_lang_label.set_label(str);
         }
     }
 
@@ -181,7 +189,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 ETitleLabelBox m_title;
     private EEntry m_entry;
     private string? m_backward;
     private EScrolledWindow? m_scrolled_window = null;
@@ -278,8 +286,8 @@ class IBusEmojier : Gtk.Window {
         m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
         add(m_vbox);
 
-        m_title = new ETitleLabel(_("Emoji Dialog"),
-                                  Gtk.Align.CENTER);
+        m_title = new ETitleLabelBox(_("Emoji Dialog"),
+                                     Gtk.Align.CENTER);
         m_vbox.add(m_title);
         m_entry = new EEntry();
         m_entry.set_placeholder_text(_("Type annotation or choose emoji"));
@@ -419,6 +427,21 @@ class IBusEmojier : Gtk.Window {
     }
 
 
+    private string utf8_code_point(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("U+%04X".printf(ch));
+            else
+                buff.append(" %04X".printf(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();
@@ -501,7 +524,7 @@ class IBusEmojier : Gtk.Window {
     private void remove_all_children() {
         foreach (Gtk.Widget w in m_vbox.get_children()) {
             if (w.name == "IBusEmojierEntry" ||
-                w.name == "IBusEmojierTitleLabel") {
+                w.name == "IBusEmojierTitleLabelBox") {
                 continue;
             }
             w.destroy();
@@ -514,10 +537,9 @@ class IBusEmojier : Gtk.Window {
         m_scrolled_window = new EScrolledWindow();
         set_fixed_size();
 
-        string language = "%s (%s)".printf(
-            _("Emoji Dialog"),
-            IBus.get_language_name(m_current_lang_id));
-        m_title.set_label(language);
+        string language =
+            IBus.get_language_name(m_current_lang_id);
+        m_title.set_lang_label(language);
         m_vbox.add(m_scrolled_window);
         Gtk.Viewport viewport = new Gtk.Viewport(null, null);
         m_scrolled_window.add(viewport);
@@ -535,8 +557,8 @@ class IBusEmojier : Gtk.Window {
         uint n = 1;
         if (m_favorites.length > 0) {
             EBoxRow row = new EBoxRow("@favorites");
-            EPaddedLabel widget =
-                    new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER);
+            EPaddedLabelBox widget =
+                    new EPaddedLabelBox(_("Favorites"), Gtk.Align.CENTER);
             row.add(widget);
             m_list_box.add(row);
             if (n++ == m_category_active_index)
@@ -551,9 +573,9 @@ class IBusEmojier : Gtk.Window {
         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);
+            EPaddedLabelBox widget =
+                    new EPaddedLabelBox(utf8_title(locale_category),
+                                        Gtk.Align.CENTER);
             row.add(widget);
             m_list_box.add(row);
             if (n++ == m_category_active_index)
@@ -626,18 +648,23 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    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;
-        }
+    private bool check_unicode_point() {
+        string annotation = m_entry.get_text();
+        m_unicode_point = null;
         GLib.StringBuilder buff = new GLib.StringBuilder();
+        GLib.StringBuilder retval = new GLib.StringBuilder();
         for (int i = 0; i < annotation.char_count(); i++) {
             unichar ch = annotation.get_char(i);
             if (ch == 0)
                 return false;
+            if (ch.isspace()) {
+                unichar code = (unichar)buff.str.to_ulong(null, 16);
+                buff.erase();
+                if (!code.validate())
+                    return false;
+                retval.append(code.to_string());
+                continue;
+            }
             if (!ch.isxdigit())
                 return false;
             buff.append_unichar(ch);
@@ -645,9 +672,8 @@ class IBusEmojier : Gtk.Window {
         unichar code = (unichar)buff.str.to_ulong(null, 16);
         if (!code.validate())
             return false;
-        if (check_xdigit_only)
-            return true;
-        m_unicode_point = code.to_string();
+        retval.append(code.to_string());
+        m_unicode_point = retval.str;
         if (m_unicode_point == null)
             return true;
         IBus.Text text = new IBus.Text.from_string(m_unicode_point);
@@ -699,9 +725,10 @@ class IBusEmojier : Gtk.Window {
         if (m_backward != null) {
             string backward_desc =
                     "%s (%u / %u)".printf(m_backward, cursor, ncandidates - 1);
-            EPaddedLabel label = new EPaddedLabel(backward_desc,
-                                                  Gtk.Align.CENTER,
-                                                  TravelDirection.BACKWARD);
+            EPaddedLabelBox label =
+                    new EPaddedLabelBox(backward_desc,
+                                        Gtk.Align.CENTER,
+                                        TravelDirection.BACKWARD);
             Gtk.Button button = new Gtk.Button();
             button.add(label);
             m_vbox.add(button);
@@ -776,27 +803,24 @@ class IBusEmojier : Gtk.Window {
             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(
-                                m_unicode_point.get_char(0)),
-                        Gtk.Align.START);
-                m_vbox.add(widget);
-                widget.show_all();
-                if (data == null)
-                    return;
-            } else if (data == null) {
+            if (data == null) {
                 // TODO: Provide a custom description and annotation for
                 // the favorite emojis.
-                EPaddedLabel widget = new EPaddedLabel(
+                EPaddedLabelBox widget = new EPaddedLabelBox(
                         _("Description: %s").printf(_("None")),
                         Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
+                EPaddedLabelBox widget_code = new EPaddedLabelBox(
+                        _("Code point: %s").printf(
+                                utf8_code_point(candidate.text)),
+                        Gtk.Align.START);
+                m_vbox.add(widget_code);
+                widget_code.show_all();
                 return;
             } else {
                 unowned string description = data.get_description();
-                EPaddedLabel widget = new EPaddedLabel(
+                EPaddedLabelBox widget = new EPaddedLabelBox(
                         _("Description: %s").printf(description),
                         Gtk.Align.START);
                 m_vbox.add(widget);
@@ -812,19 +836,26 @@ class IBusEmojier : Gtk.Window {
                 else
                     buff.append_printf(" | %s", annotation);
                 if (buff.str.char_count() > 30) {
-                    EPaddedLabel widget = new EPaddedLabel(buff.str,
-                                                           Gtk.Align.START);
+                    EPaddedLabelBox widget =
+                            new EPaddedLabelBox(buff.str,
+                                                Gtk.Align.START);
                     m_vbox.add(widget);
                     widget.show_all();
                     buff.erase();
                 }
             }
             if (buff.str != "") {
-                EPaddedLabel widget = new EPaddedLabel(buff.str,
-                                                       Gtk.Align.START);
+                EPaddedLabelBox widget = new EPaddedLabelBox(buff.str,
+                                                             Gtk.Align.START);
                 m_vbox.add(widget);
                 widget.show_all();
             }
+            EPaddedLabelBox widget_code = new EPaddedLabelBox(
+                    _("Code point: %s").printf(
+                            utf8_code_point(candidate.text)),
+                    Gtk.Align.START);
+            m_vbox.add(widget_code);
+            widget_code.show_all();
         }
     }
 
@@ -837,43 +868,6 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private bool if_in_range_of_lookup(uint keyval) {
-        if (!m_candidate_panel_is_visible)
-            return false;
-        StringBuilder buffer_string = new StringBuilder(m_entry.get_text());
-        unichar ch = IBus.keyval_to_unicode (keyval);
-        buffer_string.append_unichar(ch);
-        if (check_unicode_point(buffer_string.str))
-            return false;
-        if (keyval < Gdk.Key.@0 || keyval > Gdk.Key.@9)
-            return false;
-        if (keyval == Gdk.Key.@0)
-            keyval = Gdk.Key.@9 + 1;
-        uint index = keyval - Gdk.Key.@1 + 1;
-        uint candidates = m_lookup_table.get_number_of_candidates();
-        uint cursor_pos = m_lookup_table.get_cursor_pos();
-        uint page_size = m_lookup_table.get_page_size();
-        if (index > uint.min(candidates - (cursor_pos / page_size) * page_size,
-                             page_size)) {
-            return false;
-        }
-        return true;
-    }
-
-
-    private void set_number_on_lookup(uint keyval) {
-        if (keyval == Gdk.Key.@0)
-            keyval = Gdk.Key.@9 + 1;
-        uint index = keyval - Gdk.Key.@1;
-        uint cursor_pos = m_lookup_table.get_cursor_pos();
-        uint cursor_in_page= m_lookup_table.get_cursor_in_page();
-        uint real_index = cursor_pos - cursor_in_page + index;
-        m_lookup_table.set_cursor_pos(real_index);
-        IBus.Text text = m_lookup_table.get_candidate(real_index);
-        m_result = text.text;
-    }
-
-
     private void enter_notify_disable_with_timer() {
         // Enable keyboard operation and disable mouse operation.
         m_enter_notify_enable = false;
@@ -1154,11 +1148,6 @@ class IBusEmojier : Gtk.Window {
          * key_release_event() so that this can know if the event
          * was handled by IME.
          */
-        if (if_in_range_of_lookup(keyval)) {
-            set_number_on_lookup(keyval);
-            m_loop.quit();
-            return true;
-        }
         switch (keyval) {
         case Gdk.Key.Escape:
             if (key_press_cursor_escape())
-- 
2.9.3

From cb0a36c254dc7a96b2a984e715f96cc2ec32e2d5 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 3 Apr 2017 12:24:27 +0900
Subject: [PATCH] src: Enable unicode_alt in EmojiOne json file

EmojiOne json file has unicode_alt property which includes
emoji modifer characters.

Review URL: https://codereview.appspot.com/316420043
---
 src/emoji-parser.c   | 20 +++++++++++++++++---
 src/ibusemoji.c      | 48 ++++++++++++++++++++++++++++++++++++++++++++----
 src/ibusemoji.h      | 14 ++++++++++++++
 ui/gtk3/emojier.vala |  9 ++++++---
 4 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/src/emoji-parser.c b/src/emoji-parser.c
index f9e3470..e5dce3f 100644
--- a/src/emoji-parser.c
+++ b/src/emoji-parser.c
@@ -40,6 +40,7 @@
 typedef struct _EmojiData EmojiData;
 struct _EmojiData {
     gchar      *emoji;
+    gchar      *emoji_alternates;
     GSList     *annotations;
     gboolean    is_annotation;
     gchar      *description;
@@ -54,6 +55,7 @@ reset_emoji_element (EmojiData *data)
     g_assert (data != NULL);
 
     g_clear_pointer (&data->emoji, g_free);
+    g_clear_pointer (&data->emoji_alternates, g_free);
     g_slist_free_full (data->annotations, g_free);
     data->annotations = NULL;
     g_clear_pointer (&data->description, g_free);
@@ -112,6 +114,10 @@ update_emoji_list (EmojiData *data)
                                      "category",
                                      data->category ? data->category
                                              : g_strdup (""),
+                                     "emoji-alternates",
+                                     data->emoji_alternates
+                                             ? data->emoji_alternates
+                                             : g_strdup (""),
                                      NULL);
         data->list = g_slist_append (data->list, emoji);
     }
@@ -271,7 +277,8 @@ failed_to_parse_unicode_annotations:
 
 static gboolean
 parse_emojione_unicode (JsonNode  *node,
-                        EmojiData *data)
+                        EmojiData *data,
+                        gboolean   is_alternates)
 {
     const gchar *str, *unicode;
     gchar *endptr = NULL;
@@ -305,7 +312,10 @@ parse_emojione_unicode (JsonNode  *node,
         endptr = NULL;
     }
 
-    data->emoji = g_string_free (emoji, FALSE);
+    if (is_alternates)
+        data->emoji_alternates = g_string_free (emoji, FALSE);
+    else
+        data->emoji = g_string_free (emoji, FALSE);
 
     return TRUE;
 }
@@ -480,7 +490,11 @@ parse_emojione_emoji_data (JsonNode    *node,
                            EmojiData   *data)
 {
     if (g_strcmp0 (member, "unicode") == 0)
-        return parse_emojione_unicode (node, data);
+        return parse_emojione_unicode (node, data, FALSE);
+    else if (g_strcmp0 (member, "unicode_alt") == 0)
+        return parse_emojione_unicode (node, data, TRUE);
+    else if (g_strcmp0 (member, "unicode_alternates") == 0)
+        return parse_emojione_unicode (node, data, TRUE);
     else if (g_strcmp0 (member, "shortname") == 0)
         return parse_emojione_shortname (node, data);
     else if (g_strcmp0 (member, "name") == 0)
diff --git a/src/ibusemoji.c b/src/ibusemoji.c
index c61cd70..4be092a 100644
--- a/src/ibusemoji.c
+++ b/src/ibusemoji.c
@@ -29,14 +29,15 @@
 #include "ibusinternal.h"
 
 #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
-#define IBUS_EMOJI_DATA_VERSION (2)
+#define IBUS_EMOJI_DATA_VERSION (3)
 
 enum {
     PROP_0 = 0,
     PROP_EMOJI,
     PROP_ANNOTATIONS,
     PROP_DESCRIPTION,
-    PROP_CATEGORY
+    PROP_CATEGORY,
+    PROP_EMOJI_ALTERNATES
 };
 
 struct _IBusEmojiDataPrivate {
@@ -44,6 +45,7 @@ struct _IBusEmojiDataPrivate {
     GSList     *annotations;
     gchar      *description;
     gchar      *category;
+    gchar      *emoji_alternates;
 };
 
 #define IBUS_EMOJI_DATA_GET_PRIVATE(o)  \
@@ -142,6 +144,19 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class)
                         "The emoji category",
                         "",
                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+    /**
+     * IBusEmojiData:emoji_alternates:
+     *
+     * The emoji alternate characters
+     */
+    g_object_class_install_property (gobject_class,
+                    PROP_EMOJI_ALTERNATES,
+                    g_param_spec_string ("emoji-alternates",
+                        "emoji alternate charasters",
+                        "The emoji alternate characters UTF-8",
+                        "",
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 }
 
 static void
@@ -180,19 +195,24 @@ ibus_emoji_data_set_property (IBusEmojiData *emoji,
         emoji->priv->emoji = g_value_dup_string (value);
         break;
     case PROP_ANNOTATIONS:
-        g_assert (emoji->priv->annotations == NULL);
+        if (emoji->priv->annotations)
+            g_slist_free_full (emoji->priv->annotations, g_free);
         emoji->priv->annotations =
                 g_slist_copy_deep (g_value_get_pointer (value),
                                    (GCopyFunc) g_strdup, NULL);
         break;
     case PROP_DESCRIPTION:
-        g_assert (emoji->priv->description == NULL);
+        g_free (emoji->priv->description);
         emoji->priv->description = g_value_dup_string (value);
         break;
     case PROP_CATEGORY:
         g_assert (emoji->priv->category == NULL);
         emoji->priv->category = g_value_dup_string (value);
         break;
+    case PROP_EMOJI_ALTERNATES:
+        g_assert (emoji->priv->emoji_alternates == NULL);
+        emoji->priv->emoji_alternates = g_value_dup_string (value);
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
     }
@@ -220,6 +240,9 @@ ibus_emoji_data_get_property (IBusEmojiData *emoji,
     case PROP_CATEGORY:
         g_value_set_string (value, ibus_emoji_data_get_category (emoji));
         break;
+    case PROP_EMOJI_ALTERNATES:
+        g_value_set_string (value, ibus_emoji_data_get_emoji_alternates(emoji));
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
     }
@@ -247,6 +270,9 @@ ibus_emoji_data_serialize (IBusEmojiData   *emoji,
     }
     g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description));
     g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category));
+    g_variant_builder_add (builder, "s",
+                           NOTNULL (emoji->priv->emoji_alternates));
+#undef NOTNULL
     return TRUE;
 }
 
@@ -277,6 +303,10 @@ ibus_emoji_data_deserialize (IBusEmojiData *emoji,
                                      &emoji->priv->description);
     ibus_g_variant_get_child_string (variant, retval++,
                                      &emoji->priv->category);
+    if (g_variant_n_children (variant) < retval + 1)
+        return retval;
+    ibus_g_variant_get_child_string (variant, retval++,
+                                     &emoji->priv->emoji_alternates);
     return retval;
 }
 
@@ -295,6 +325,7 @@ ibus_emoji_data_copy (IBusEmojiData       *dest,
                                                       NULL);
     dest->priv->description      = g_strdup (src->priv->description);
     dest->priv->category         = g_strdup (src->priv->category);
+    dest->priv->emoji_alternates = g_strdup (src->priv->emoji_alternates);
     return TRUE;
 }
 
@@ -314,6 +345,7 @@ ibus_emoji_data_new (const gchar *first_property_name, ...)
     g_assert (emoji->priv->emoji != NULL);
     g_assert (emoji->priv->description != NULL);
     g_assert (emoji->priv->category != NULL);
+    g_assert (emoji->priv->emoji_alternates != NULL);
     return emoji;
 }
 
@@ -370,6 +402,14 @@ ibus_emoji_data_get_category (IBusEmojiData *emoji)
     return emoji->priv->category;
 }
 
+const gchar *
+ibus_emoji_data_get_emoji_alternates (IBusEmojiData *emoji)
+{
+    g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
+
+    return emoji->priv->emoji_alternates;
+}
+
 
 static void
 variant_foreach_add_emoji (IBusEmojiData   *emoji,
diff --git a/src/ibusemoji.h b/src/ibusemoji.h
index eb24fdd..233cadd 100644
--- a/src/ibusemoji.h
+++ b/src/ibusemoji.h
@@ -156,6 +156,20 @@ const gchar *   ibus_emoji_data_get_category    (IBusEmojiData *emoji);
 
 
 /**
+ * ibus_emoji_data_get_emoji_alternates:
+ * @emoji : An #IBusEmojiData
+ *
+ * Gets the emoji alternate characters in #IBusEmojiData. It should not be
+ * freed. The alternates are defined in "unicode_alt" in EmojiOne json.
+ *
+ * Returns: emoji alternates property in #IBusEmojiData
+ *
+ */
+const gchar *   ibus_emoji_data_get_emoji_alternates
+                                                (IBusEmojiData *emoji);
+
+
+/**
  * ibus_emoji_dict_save:
  * @path: A path of the saved dictionary file.
  * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary
diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 8a2726c..7b6107f 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -381,7 +381,8 @@ class IBusEmojier : Gtk.Window {
 
 
     private void update_annotation_to_emojis_dict(IBus.EmojiData data) {
-        string emoji = data.get_emoji();
+        string emoji = (data.get_emoji_alternates() != "") ?
+                data.get_emoji_alternates() : data.get_emoji();
         unowned GLib.SList<string> annotations = data.get_annotations();
         foreach (string annotation in annotations) {
             bool has_emoji = false;
@@ -444,7 +445,8 @@ class IBusEmojier : Gtk.Window {
 
     private void update_emoji_to_data_dict(IBus.EmojiData data,
                                            string         lang) {
-        string emoji = data.get_emoji();
+        string emoji = (data.get_emoji_alternates() != "") ?
+                data.get_emoji_alternates() : data.get_emoji();
         if (lang == "en") {
             string description = utf8_down(data.get_description());
             unowned GLib.SList<string> annotations = data.get_annotations();
@@ -496,7 +498,8 @@ class IBusEmojier : Gtk.Window {
 
     private void update_category_to_emojis_dict(IBus.EmojiData data,
                                                 string         lang) {
-        string emoji = data.get_emoji();
+        string emoji = (data.get_emoji_alternates() != "") ?
+                data.get_emoji_alternates() : data.get_emoji();
         string category = data.get_category();
         if (lang == "en" && category != "") {
             bool has_emoji = false;
-- 
2.9.3

From 648f58a361ea1407f229ed1682486240cc2e5026 Mon Sep 17 00:00:00 2001
From: Peng Wu <alexepico@gmail.com>
Date: Mon, 10 Apr 2017 14:54:08 +0900
Subject: [PATCH] Make ibus emojier dialog as an unique application

Because wayland doesn't support clipboard persistence, just hide the dialog; will show the emojier dialog again, when run it again.

In next patches, ibus emoji command will just spawn the process of ibus-ui-emojier.

BUG=
R=takao.fujiwara1@gmail.com

Review URL: https://codereview.appspot.com/320450043

Patch from Peng Wu <alexepico@gmail.com>.
---
 po/POTFILES.in      |  1 +
 po/POTFILES.skip    |  1 +
 ui/gtk3/Makefile.am | 24 ++++++++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 469f90b..65a3526 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -65,3 +65,4 @@ ui/gtk3/property.vala
 ui/gtk3/propertypanel.vala
 ui/gtk3/separator.vala
 ui/gtk3/switcher.vala
+ui/gtk3/emojierapp.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index a818c48..891d2cc 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -21,3 +21,4 @@ ui/gtk3/property.c
 ui/gtk3/propertypanel.c
 ui/gtk3/separator.c
 ui/gtk3/switcher.c
+ui/gtk3/emojierapp.c
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 7122ff3..96ed3e6 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -108,6 +108,10 @@ endif
 
 libexec_PROGRAMS = ibus-ui-gtk3
 
+if ENABLE_EMOJI_DICT
+libexec_PROGRAMS += ibus-ui-emojier
+endif
+
 ibus_ui_gtk3_SOURCES = \
 	application.vala \
 	candidatearea.vala \
@@ -132,6 +136,26 @@ ibus_ui_gtk3_LDADD = \
 	$(AM_LDADD) \
 	$(NULL)
 
+if ENABLE_EMOJI_DICT
+AM_VALAFLAGS += \
+        --define=EMOJI_DICT \
+        --vapidir=$(top_builddir)/ui/gtk3 \
+        --vapidir=$(top_srcdir)/ui/gtk3 \
+        --pkg=ibus-emoji-dialog-1.0 \
+        --pkg=gtk+-3.0 \
+        $(NULL)
+
+
+ibus_ui_emojier_SOURCES = \
+	emojierapp.vala \
+	$(NULL)
+
+ibus_ui_emojier_LDADD = \
+	$(AM_LDADD) \
+	$(libibus_emoji_dialog) \
+	$(NULL)
+endif
+
 gen-%.xml.c: %.xml
 	echo "Building $@ from $<"
 	echo "const char * _$(subst -,_,$(subst .,_,$(basename $(notdir $<)))) = " > $@
-- 
2.9.3

From 2f8982a0a4b25f98c969c09b81724e44efc41c27 Mon Sep 17 00:00:00 2001
From: Peng Wu <alexepico@gmail.com>
Date: Mon, 10 Apr 2017 15:57:48 +0900
Subject: [PATCH] Make ibus emojier dialog as an unique application

Forgot to commit ui/gtk3/emojierapp.vala from the previous commit.

BUG=
R=takao.fujiwara1@gmail.com

Review URL: https://codereview.appspot.com/319650043

Patch from Peng Wu <alexepico@gmail.com>.
---
 ui/gtk3/emojierapp.vala | 149 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
 create mode 100644 ui/gtk3/emojierapp.vala

diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
new file mode 100644
index 0000000..4287bff
--- /dev/null
+++ b/ui/gtk3/emojierapp.vala
@@ -0,0 +1,149 @@
+/* vim:set et sts=4 sw=4:
+ *
+ * ibus - The Input Bus
+ *
+ * Copyright (c) 2017 Peng Wu <alexepico@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+string emoji_font = null;
+string annotation_lang = null;
+
+public class EmojiApplication : Application {
+    private IBus.Emojier emojier = new IBus.Emojier();
+
+    private EmojiApplication() {
+        Object(application_id: "org.freedesktop.ibus.panel.emojier",
+                flags: ApplicationFlags.HANDLES_COMMAND_LINE);
+        set_inactivity_timeout(100000);
+    }
+
+    private void show_dialog(ApplicationCommandLine command_line) {
+        Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
+        var display = Gdk.Display.get_default();
+        var device_manager = display.get_device_manager();
+        var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
+        event.set_device(device);
+        string emoji = emojier.run(event, "");
+        if (emoji == null) {
+            emojier.reset();
+            command_line.print("%s\n", _("Canceled to choose an emoji."));
+            return;
+        }
+        Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
+        clipboard.set_text(emoji, -1);
+        clipboard.store();
+        emojier.reset();
+        command_line.print("%s\n", _("Copied an emoji to your clipboard."));
+    }
+
+    public void activate_dialog(ApplicationCommandLine command_line) {
+        this.hold ();
+
+        // show dialog
+        if (emojier.has_loaded_emoji_dict()) {
+            show_dialog(command_line);
+        } else {
+            emojier.loaded_emoji_dict.connect(() => {
+                    // The signal is called when the language is changed.
+                    if (emojier.is_running())
+                    return;
+
+                    show_dialog(command_line);
+                    });
+        }
+
+        this.release ();
+    }
+
+    private int _command_line (ApplicationCommandLine command_line) {
+        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);
+
+        // We have to make an extra copy of the array,
+        // since .parse assumes that it can remove strings
+        // from the array without freeing them.
+        string[] args = command_line.get_arguments();
+        string*[] _args = new string[args.length];
+        for (int i = 0; i < args.length; i++) {
+            _args[i] = args[i];
+        }
+
+        try {
+            unowned string[] tmp = _args;
+            option.parse(ref tmp);
+        } catch (OptionError e) {
+            stderr.printf("%s\n", e.message);
+            return Posix.EXIT_FAILURE;
+        }
+
+        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");
+        }
+
+        if (emoji_font != null && emoji_font != "")
+            emojier.set_emoji_font(emoji_font);
+        if (annotation_lang != null && annotation_lang != "")
+            emojier.set_annotation_lang(annotation_lang);
+
+        activate_dialog(command_line);
+
+        return Posix.EXIT_SUCCESS;
+    }
+
+    public override int command_line (ApplicationCommandLine command_line) {
+        // keep the application running until we are done with this commandline
+        this.hold();
+        int result = _command_line(command_line);
+        this.release();
+        return result;
+    }
+
+    public static int main (string[] args) {
+        GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE,
+                Config.GLIB_LOCALE_DIR);
+        GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
+        GLib.Intl.textdomain(Config.GETTEXT_PACKAGE);
+
+        IBus.init();
+
+        Gtk.init(ref args);
+
+        EmojiApplication app = new EmojiApplication();
+        int status = app.run(args);
+        return status;
+    }
+
+}
-- 
2.9.3

From da33672bc738889dcd4ebbec51cb3aa67567f70c Mon Sep 17 00:00:00 2001
From: Peng Wu <alexepico@gmail.com>
Date: Thu, 13 Apr 2017 12:40:22 +0900
Subject: [PATCH] tools: spawn the process of ibus-ui-emojier

BUG=
R=Shawn.P.Huang@gmail.com, takao.fujiwara1@gmail.com

Review URL: https://codereview.appspot.com/324770043

Patch from Peng Wu <alexepico@gmail.com>.
---
 bindings/vala/config.vapi |  1 +
 tools/Makefile.am         |  1 +
 tools/main.vala           | 72 +++++++++--------------------------------------
 3 files changed, 15 insertions(+), 59 deletions(-)

diff --git a/bindings/vala/config.vapi b/bindings/vala/config.vapi
index f2195da..e3c43df 100644
--- a/bindings/vala/config.vapi
+++ b/bindings/vala/config.vapi
@@ -7,6 +7,7 @@ namespace Config
     public const string BINDIR;
     public const string DATADIR;
     public const string PKGDATADIR;
+    public const string LIBEXECDIR;
     public const string GETTEXT_PACKAGE;
     public const string GLIB_LOCALE_DIR;
 }
diff --git a/tools/Makefile.am b/tools/Makefile.am
index bd655af..9d542bd 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -39,6 +39,7 @@ AM_CFLAGS = \
 	@GIO2_CFLAGS@ \
 	@GTHREAD2_CFLAGS@ \
 	-DG_LOG_DOMAIN=\"IBUS\" \
+	-DLIBEXECDIR=\"$(libexecdir)\" \
 	-DIBUS_DISABLE_DEPRECATED \
 	-Wno-unused-variable \
 	-Wno-unused-but-set-variable \
diff --git a/tools/main.vala b/tools/main.vala
index 2bf1dc7..9aca4b0 100644
--- a/tools/main.vala
+++ b/tools/main.vala
@@ -324,73 +324,27 @@ int reset_config(string[] argv) {
 }
 
 #if EMOJI_DICT
-private void run_dialog(IBus.Emojier emojier) {
-    Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
-    var display = Gdk.Display.get_default();
-    var device_manager = display.get_device_manager();
-    var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
-    event.set_device(device);
-    string emoji = emojier.run(event, "");
-    if (emoji == null) {
-        emojier.reset();
-        print("%s\n", _("Canceled to choose an emoji."));
-        return;
-    }
-    Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
-    clipboard.set_text(emoji, -1);
-    clipboard.store();
-    emojier.reset();
-    print("%s\n", _("Copied an emoji to your clipboard."));
-}
-
 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 }
-    };
+    string cmd = Config.LIBEXECDIR + "/ibus-ui-emojier";
 
-    var option = new OptionContext();
-    option.add_main_entries(options, Config.GETTEXT_PACKAGE);
+    var file = File.new_for_path(cmd);
+    if (!file.query_exists())
+        cmd = "../ui/gtk3/ibus-ui-emojier";
+
+    argv[0] = cmd;
+
+    string[] env = Environ.get();
 
     try {
-        option.parse(ref argv);
-    } catch (OptionError e) {
+        // Non-blocking
+        Process.spawn_async(null, argv, env,
+                            SpawnFlags.SEARCH_PATH,
+                            null, null);
+    } catch (SpawnError e) {
         stderr.printf("%s\n", e.message);
         return Posix.EXIT_FAILURE;
     }
 
-    Gtk.init(ref argv);
-    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();
-    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 {
-        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();
-        });
-        loop.run();
-    }
     return Posix.EXIT_SUCCESS;
 }
 #endif
-- 
2.9.3

From 290f786b82158e7c087b81946727606cd3424e94 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Mon, 17 Apr 2017 16:00:26 +0900
Subject: [PATCH] ui/gtk3: Fix build failures

ibus-ui-emoijer needs to be built at last in ui/gtk3
Also fixed po/POTFILES.in for `make dist`
Now I moved libibusemojidialog.so and IBusEmojiDialog-1.0.[gir|typelib]
and ibus-emoji-dialog-1.0.vapi to noinst because now
ibus-ui-emojier is available and they are private libraries.

R=Shawn.P.Huang@gmail.com

Review URL: https://codereview.appspot.com/312610043
---
 po/POTFILES.in          |  1 +
 tools/Makefile.am       | 19 ++-----------
 ui/gtk3/Makefile.am     | 72 ++++++++++++++++++++++---------------------------
 ui/gtk3/emojierapp.vala | 33 +++++++++++++----------
 4 files changed, 54 insertions(+), 71 deletions(-)

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 65a3526..25be4f4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -31,6 +31,7 @@ ibus/object.py
 ibus/panel.py
 ibus/property.py
 ibus/utility.py
+setup/emojilang.py
 setup/engineabout.py
 setup/enginecombobox.py
 setup/enginedialog.py
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 9d542bd..5c18d3d 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -101,24 +101,9 @@ CLEANFILES = \
 
 if ENABLE_EMOJI_DICT
 if ENABLE_UI
-AM_CPPFLAGS += \
-	-I$(top_srcdir)/ui/gtk3 \
-	-I$(top_builddir)/ui/gtk3 \
-	$(NULL)
-AM_CFLAGS += \
-	@GTK3_CFLAGS@ \
-	$(NULL)
-AM_LDADD += \
-	@GTK3_LIBS@ \
-	$(libibus_emoji_dialog) \
-	$(NULL)
 AM_VALAFLAGS += \
-	--define=EMOJI_DICT \
-	--vapidir=$(top_builddir)/ui/gtk3 \
-	--vapidir=$(top_srcdir)/ui/gtk3 \
-	--pkg=ibus-emoji-dialog-1.0 \
-	--pkg=gtk+-3.0 \
-	$(NULL)
+    --define=EMOJI_DICT \
+    $(NULL)
 endif
 endif
 
diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 96ed3e6..6f0fb62 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -81,7 +81,9 @@ AM_VALAFLAGS = \
 	--target-glib="$(VALA_TARGET_GLIB_VERSION)" \
 	$(NULL)
 
+MAINTAINERCLEANFILES =
 CONFIG_CLEAN_FILES =
+noinst_DATA =
 
 if ENABLE_LIBNOTIFY
 AM_CFLAGS += \
@@ -108,10 +110,6 @@ endif
 
 libexec_PROGRAMS = ibus-ui-gtk3
 
-if ENABLE_EMOJI_DICT
-libexec_PROGRAMS += ibus-ui-emojier
-endif
-
 ibus_ui_gtk3_SOURCES = \
 	application.vala \
 	candidatearea.vala \
@@ -136,26 +134,6 @@ ibus_ui_gtk3_LDADD = \
 	$(AM_LDADD) \
 	$(NULL)
 
-if ENABLE_EMOJI_DICT
-AM_VALAFLAGS += \
-        --define=EMOJI_DICT \
-        --vapidir=$(top_builddir)/ui/gtk3 \
-        --vapidir=$(top_srcdir)/ui/gtk3 \
-        --pkg=ibus-emoji-dialog-1.0 \
-        --pkg=gtk+-3.0 \
-        $(NULL)
-
-
-ibus_ui_emojier_SOURCES = \
-	emojierapp.vala \
-	$(NULL)
-
-ibus_ui_emojier_LDADD = \
-	$(AM_LDADD) \
-	$(libibus_emoji_dialog) \
-	$(NULL)
-endif
-
 gen-%.xml.c: %.xml
 	echo "Building $@ from $<"
 	echo "const char * _$(subst -,_,$(subst .,_,$(basename $(notdir $<)))) = " > $@
@@ -185,11 +163,17 @@ EXTRA_DIST =                            \
     $(NULL)
 
 if ENABLE_EMOJI_DICT
-AM_VALAFLAGS += --define=EMOJI_DICT
+AM_VALAFLAGS += \
+    --define=EMOJI_DICT \
+    --vapidir=$(top_builddir)/ui/gtk3 \
+    --vapidir=$(top_srcdir)/ui/gtk3 \
+    --pkg=ibus-emoji-dialog-1.0 \
+    --pkg=gtk+-3.0 \
+    $(NULL)
 
 libibus_emoji_dialog = libibus-emoji-dialog-1.0.la
 
-lib_LTLIBRARIES = $(libibus_emoji_dialog)
+noinst_LTLIBRARIES = $(libibus_emoji_dialog)
 
 libibus_emoji_dialog_1_0_la_CFLAGS = $(AM_CFLAGS)
 libibus_emoji_dialog_1_0_la_LDFLAGS =   \
@@ -198,11 +182,22 @@ 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                         \
+    candidatearea.vala                  \
+    emojier.vala                        \
+    iconwidget.vala                     \
+    pango.vala                          \
+    separator.vala                      \
+    $(NULL)
+
+libexec_PROGRAMS += ibus-ui-emojier
+
+ibus_ui_emojier_SOURCES =                       \
+    $(libibus_emoji_dialog_1_0_la_SOURCES)      \
+    emojierapp.vala                             \
+    $(NULL)
+
+ibus_ui_emojier_LDADD =                         \
+    $(AM_LDADD)                                 \
     $(NULL)
 
 -include $(INTROSPECTION_MAKEFILE)
@@ -214,10 +209,6 @@ INTROSPECTION_COMPILER_ARGS =      \
     $(NULL)
 
 if HAVE_INTROSPECTION
-ibusincludedir = $(includedir)/ibus-@IBUS_API_VERSION@
-ibusinclude_HEADERS =                  \
-    $(emoji_headers)                   \
-    $(NULL)
 introspection_sources =                \
     $(emoji_headers)                   \
     $(NULL)
@@ -242,12 +233,13 @@ IBusEmojiDialog_1_0_gir_CFLAGS =       \
 INTROSPECTION_GIRS = IBusEmojiDialog-1.0.gir
 
 girdir = $(datadir)/gir-1.0
-dist_gir_DATA = $(INTROSPECTION_GIRS)
+noinst_DATA += $(INTROSPECTION_GIRS)
+CLEANFILES += $(INTROSPECTION_GIRS)
 
 typelibsdir = $(libdir)/girepository-1.0
-typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+noinst_DATA += $(INTROSPECTION_GIRS:.gir=.typelib)
+CLEANFILES += $(INTROSPECTION_GIRS:.gir=.typelib)
 
-CLEANFILES += $(dist_gir_DATA) $(typelibs_DATA)
 
 if ENABLE_VAPIGEN
 -include $(VAPIGEN_MAKEFILE)
@@ -261,9 +253,9 @@ ibus_emoji_dialog_1_0_vapi_METADATADIRS = $(srcdir)
 ibus_emoji_dialog_1_0_vapi_FILES = $(INTROSPECTION_GIRS)
 
 vapidir = $(datadir)/vala/vapi
-vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+noinst_DATA += $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
 
-MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS)
+MAINTAINERCLEANFILES += $(VAPIGEN_VAPIS)
 # for make distclean
 CONFIG_CLEAN_FILES += $(VAPIGEN_VAPIS)
 EXTRA_DIST += $(VAPIGEN_VAPIS)
diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
index 4287bff..6349940 100644
--- a/ui/gtk3/emojierapp.vala
+++ b/ui/gtk3/emojierapp.vala
@@ -24,52 +24,55 @@ string emoji_font = null;
 string annotation_lang = null;
 
 public class EmojiApplication : Application {
-    private IBus.Emojier emojier = new IBus.Emojier();
+    private IBusEmojier m_emojier = new IBusEmojier();
 
     private EmojiApplication() {
         Object(application_id: "org.freedesktop.ibus.panel.emojier",
-                flags: ApplicationFlags.HANDLES_COMMAND_LINE);
+               flags: ApplicationFlags.HANDLES_COMMAND_LINE);
         set_inactivity_timeout(100000);
     }
 
+
     private void show_dialog(ApplicationCommandLine command_line) {
         Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
         var display = Gdk.Display.get_default();
         var device_manager = display.get_device_manager();
         var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
         event.set_device(device);
-        string emoji = emojier.run(event, "");
+        string emoji = m_emojier.run(event, "");
         if (emoji == null) {
-            emojier.reset();
+            m_emojier.reset();
             command_line.print("%s\n", _("Canceled to choose an emoji."));
             return;
         }
         Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
         clipboard.set_text(emoji, -1);
         clipboard.store();
-        emojier.reset();
+        m_emojier.reset();
         command_line.print("%s\n", _("Copied an emoji to your clipboard."));
     }
 
+
     public void activate_dialog(ApplicationCommandLine command_line) {
         this.hold ();
 
         // show dialog
-        if (emojier.has_loaded_emoji_dict()) {
+        if (m_emojier.has_loaded_emoji_dict()) {
             show_dialog(command_line);
         } else {
-            emojier.loaded_emoji_dict.connect(() => {
-                    // The signal is called when the language is changed.
-                    if (emojier.is_running())
+            m_emojier.loaded_emoji_dict.connect(() => {
+                // The signal is called when the language is changed.
+                if (m_emojier.is_running())
                     return;
 
-                    show_dialog(command_line);
-                    });
+                show_dialog(command_line);
+            });
         }
 
         this.release ();
     }
 
+
     private int _command_line (ApplicationCommandLine command_line) {
         const OptionEntry[] options = {
             { "font", 0, 0, OptionArg.STRING, out emoji_font,
@@ -114,15 +117,16 @@ public class EmojiApplication : Application {
         }
 
         if (emoji_font != null && emoji_font != "")
-            emojier.set_emoji_font(emoji_font);
+            m_emojier.set_emoji_font(emoji_font);
         if (annotation_lang != null && annotation_lang != "")
-            emojier.set_annotation_lang(annotation_lang);
+            m_emojier.set_annotation_lang(annotation_lang);
 
         activate_dialog(command_line);
 
         return Posix.EXIT_SUCCESS;
     }
 
+
     public override int command_line (ApplicationCommandLine command_line) {
         // keep the application running until we are done with this commandline
         this.hold();
@@ -131,9 +135,10 @@ public class EmojiApplication : Application {
         return result;
     }
 
+
     public static int main (string[] args) {
         GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE,
-                Config.GLIB_LOCALE_DIR);
+                                 Config.GLIB_LOCALE_DIR);
         GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
         GLib.Intl.textdomain(Config.GETTEXT_PACKAGE);
 
-- 
2.9.3

From 4b8d7baf627fb3fc9e4dde74ba33a867091f95b7 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Tue, 18 Apr 2017 11:44:52 +0900
Subject: [PATCH] ui/gtk3: Mark LANG and FONT as translatable strings

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

Review URL: https://codereview.appspot.com/322880043
---
 ui/gtk3/emojierapp.vala | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
index 6349940..eac8d6a 100644
--- a/ui/gtk3/emojierapp.vala
+++ b/ui/gtk3/emojierapp.vala
@@ -76,11 +76,17 @@ public class EmojiApplication : Application {
     private int _command_line (ApplicationCommandLine command_line) {
         const OptionEntry[] options = {
             { "font", 0, 0, OptionArg.STRING, out emoji_font,
-                N_("FONT for emoji chracters on emoji dialog."),
-                "FONT" },
+                /* TRANSLATORS: "FONT" should be capital and translatable.
+                 * It's used for an argument command --font=FONT
+                 */
+                N_("\"FONT\" for emoji chracters on emoji dialog"),
+                N_("FONT") },
             { "lang", 0, 0, OptionArg.STRING, out annotation_lang,
-                N_("LANG for annotations on emoji dialog. E.g. \"en\""),
-                "LANG" },
+                /* TRANSLATORS: "LANG" should be capital and translatable.
+                 * It's used for an argument command --lang=LANG
+                 */
+                N_("\"LANG\" for annotations on emoji dialog. E.g. \"en\""),
+                N_("LANG") },
             { null }
         };
 
-- 
2.9.3

From c6cdf21c7364cbb1e848e44cab0bff270e432e82 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 19 Apr 2017 12:16:37 +0900
Subject: [PATCH] ui/gtk3: Change modal dialog to focused dialog

There are several problems with the current emoji modal dialog.
If keyboard is grabbed on the popup window, the focus out/in events
cannot be detected so the dialog cannot be closed by the focus changes.

If mouse operation is supported on the popup window, need a custom
GtkHeaderBar with extended handle.vala but the behavior is unclear. [1]

Also current popup window has several extensions for the keyboard grab.

If the closed button is needed on the popup window since the focus events
cannot be detected, I think there is no merit to use the popup window.
Now IBusEmojier simply uses the focused window.

[1] https://mail.gnome.org/archives/gtk-app-devel-list/2017-April/msg00017.html

R=Shawn.P.Huang@gmail.com, alexepico@gmail.com

Review URL: https://codereview.appspot.com/316510043
---
 ui/gtk3/emojier.vala      | 243 +++++++++++++++++++---------------------------
 ui/gtk3/emojierapp.vala   |  55 ++++-------
 ui/gtk3/ibusemojidialog.h |  54 +++++------
 ui/gtk3/panel.vala        |  73 ++++++++++----
 4 files changed, 194 insertions(+), 231 deletions(-)

diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala
index 7b6107f..5a9bf8f 100644
--- a/ui/gtk3/emojier.vala
+++ b/ui/gtk3/emojier.vala
@@ -137,47 +137,27 @@ class IBusEmojier : Gtk.Window {
             pack_start(label, true, true, 0);
         }
     }
-    private class ETitleLabelBox : Gtk.Box {
-        EPaddedLabel m_lang_label;
-        private Gtk.Button m_close_button;
-        private ulong m_close_handler;
+    private class ETitleLabelBox : Gtk.HeaderBar {
+        private Gtk.Label m_lang_label;
 
-        public ETitleLabelBox(string    text,
-                              Gtk.Align align) {
+        public ETitleLabelBox(string title) {
             GLib.Object(
                 name : "IBusEmojierTitleLabelBox",
-                orientation : Gtk.Orientation.HORIZONTAL,
-                spacing : 0
+                show_close_button: true,
+                decoration_layout: ":close",
+                title: title
             );
-            EPaddedLabel label = new EPaddedLabel(text, align);
-            pack_start(label, true, true, 0);
-            Gtk.Separator separator =
-                    new Gtk.Separator (Gtk.Orientation.VERTICAL);
-            pack_start(separator, false, true, 0);
-            m_lang_label = new EPaddedLabel("", align);
-            pack_start(m_lang_label, false, true, 0);
-            IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU);
-            m_close_button = new Gtk.Button();
-            m_close_button.add(icon);
-            pack_end(m_close_button, false, true, 0);
-        }
-        public void set_loop(GLib.MainLoop? loop) {
-            if (m_close_handler > 0)
-                GLib.SignalHandler.disconnect(m_close_button, m_close_handler);
-            m_close_handler = m_close_button.button_press_event.connect((e) => {
-                if (loop != null && loop.is_running())
-                    loop.quit();
-                return true;
-            });
-        }
-        public void unset_loop() {
-            if (m_close_handler > 0) {
-                GLib.SignalHandler.disconnect(m_close_button, m_close_handler);
-                m_close_handler = 0;
-            }
+            var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+            set_custom_title(vbox);
+            var label = new Gtk.Label(title);
+            label.get_style_context().add_class("title");
+            vbox.pack_start(label, true, false, 0);
+            m_lang_label = new Gtk.Label(null);
+            m_lang_label.get_style_context().add_class("subtitle");
+            vbox.pack_start(m_lang_label, true, false, 0);
         }
         public void set_lang_label(string str) {
-            m_lang_label.set_label(str);
+            m_lang_label.set_text(str);
         }
     }
 
@@ -186,7 +166,22 @@ class IBusEmojier : Gtk.Window {
         BACKWARD,
     }
 
-    private const uint EMOJI_GRID_PAGE = 10;
+    private static const uint EMOJI_GRID_PAGE = 10;
+
+    // Set the actual default values in the constructor
+    // because these fields are used for class_init() and static functions,
+    // e.g. set_emoji_font(), can be called before class_init() is called.
+    private static string m_current_lang_id;
+    private static string m_emoji_font;
+    private static string[] m_favorites;
+    private static int m_emoji_max_seq_len;
+    private static GLib.HashTable<string, GLib.SList>?
+            m_annotation_to_emojis_dict;
+    private static GLib.HashTable<string, IBus.EmojiData>?
+            m_emoji_to_data_dict;
+    private static GLib.HashTable<string, GLib.SList>?
+            m_category_to_emojis_dict;
+
     private ThemedRGBA m_rgba;
     private Gtk.Box m_vbox;
     private ETitleLabelBox m_title;
@@ -198,43 +193,38 @@ class IBusEmojier : Gtk.Window {
     private string m_input_context_path = "";
     private GLib.MainLoop? m_loop;
     private string? m_result;
-    private string m_current_lang_id = "en";
     private string? m_unicode_point = null;
     private bool m_candidate_panel_is_visible;
-    private GLib.HashTable<string, GLib.SList>?
-            m_annotation_to_emojis_dict = null;
-    private GLib.HashTable<string, IBus.EmojiData>?
-            m_emoji_to_data_dict = null;
-    private GLib.HashTable<string, GLib.SList>?
-            m_category_to_emojis_dict = null;
     int m_category_active_index;
-    private int m_emoji_max_seq_len = 0;
     private IBus.LookupTable m_lookup_table;
     private Gtk.Label[] m_candidates;
-    private string m_emoji_font = "Monospace 16";
-    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;
-    private uint m_reload_emoji_dict_id;
 
     public signal void candidate_clicked(uint index, uint button, uint state);
-    public signal void loaded_emoji_dict();
 
     public IBusEmojier() {
         GLib.Object(
-            type : Gtk.WindowType.POPUP,
+            type : Gtk.WindowType.TOPLEVEL,
             events : Gdk.EventMask.KEY_PRESS_MASK |
                      Gdk.EventMask.KEY_RELEASE_MASK |
-                     Gdk.EventMask.BUTTON_PRESS_MASK,
+                     Gdk.EventMask.BUTTON_PRESS_MASK |
+                     Gdk.EventMask.BUTTON_RELEASE_MASK,
             window_position : Gtk.WindowPosition.CENTER,
+            icon_name: "ibus-setup",
             accept_focus : true,
-            decorated : false,
-            modal : true,
             resizable : true,
             focus_visible : true
         );
 
+        if (m_current_lang_id == null)
+            m_current_lang_id = "en";
+        if (m_emoji_font == null)
+            m_emoji_font = "Monospace 16";
+        if (m_favorites == null)
+            m_favorites = {};
+
         Gdk.Display display = Gdk.Display.get_default();
         Gdk.Screen screen = (display != null) ?
                 display.get_default_screen() : null;
@@ -283,12 +273,11 @@ class IBusEmojier : Gtk.Window {
                 css_provider,
                 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 
+        m_title = new ETitleLabelBox(_("Emoji Chooser"));
+        set_titlebar(m_title);
         m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
         add(m_vbox);
 
-        m_title = new ETitleLabelBox(_("Emoji Dialog"),
-                                     Gtk.Align.CENTER);
-        m_vbox.add(m_title);
         m_entry = new EEntry();
         m_entry.set_placeholder_text(_("Type annotation or choose emoji"));
         m_vbox.add(m_entry);
@@ -306,8 +295,6 @@ class IBusEmojier : Gtk.Window {
         Atk.Object obj = m_entry.get_accessible();
         obj.set_role (Atk.Role.STATUSBAR);
 
-        grab_focus();
-
         // The constructor of IBus.LookupTable does not support more than
         // 16 pages.
         m_lookup_table = new IBus.LookupTable(1, 0, true, true);
@@ -325,15 +312,13 @@ class IBusEmojier : Gtk.Window {
             hide_candidate_panel();
         });
 
-        m_reload_emoji_dict_id = GLib.Idle.add(() => {
+        if (m_annotation_to_emojis_dict == null) {
             reload_emoji_dict();
-            m_reload_emoji_dict_id = 0;
-            return false;
-        });
+        }
     }
 
 
-    private void reload_emoji_dict() {
+    private static void reload_emoji_dict() {
         init_emoji_dict();
         make_emoji_dict("en");
         if (m_current_lang_id != "en") {
@@ -344,11 +329,10 @@ class IBusEmojier : Gtk.Window {
             }
             make_emoji_dict(m_current_lang_id);
         }
-        loaded_emoji_dict();
     }
 
 
-    private void init_emoji_dict() {
+    private static void init_emoji_dict() {
         m_annotation_to_emojis_dict =
                 new GLib.HashTable<string, GLib.SList>(GLib.str_hash,
                                                        GLib.str_equal);
@@ -361,7 +345,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private void make_emoji_dict(string lang) {
+    private static void make_emoji_dict(string lang) {
         GLib.SList<IBus.EmojiData> emoji_list = IBus.EmojiData.load(
                     Config.PKGDATADIR + "/dicts/emoji-" + lang + ".dict");
         if (emoji_list == null)
@@ -380,7 +364,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private void update_annotation_to_emojis_dict(IBus.EmojiData data) {
+    private static void update_annotation_to_emojis_dict(IBus.EmojiData data) {
         string emoji = (data.get_emoji_alternates() != "") ?
                 data.get_emoji_alternates() : data.get_emoji();
         unowned GLib.SList<string> annotations = data.get_annotations();
@@ -402,7 +386,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private string utf8_down(string str) {
+    private static string utf8_down(string str) {
         GLib.StringBuilder buff = new GLib.StringBuilder();
         int length = str.char_count();
         for (int i = 0; i < length; i++) {
@@ -413,7 +397,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private string utf8_title(string str) {
+    private static string utf8_title(string str) {
         StringBuilder buff = new StringBuilder();
         int length = str.char_count();
         for (int i = 0; i < length; i++) {
@@ -428,7 +412,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private string utf8_code_point(string str) {
+    private static string utf8_code_point(string str) {
         StringBuilder buff = new StringBuilder();
         int length = str.char_count();
         for (int i = 0; i < length; i++) {
@@ -443,8 +427,8 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private void update_emoji_to_data_dict(IBus.EmojiData data,
-                                           string         lang) {
+    private static void update_emoji_to_data_dict(IBus.EmojiData data,
+                                                  string         lang) {
         string emoji = (data.get_emoji_alternates() != "") ?
                 data.get_emoji_alternates() : data.get_emoji();
         if (lang == "en") {
@@ -496,8 +480,8 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    private void update_category_to_emojis_dict(IBus.EmojiData data,
-                                                string         lang) {
+    private static void update_category_to_emojis_dict(IBus.EmojiData data,
+                                                       string         lang) {
         string emoji = (data.get_emoji_alternates() != "") ?
                 data.get_emoji_alternates() : data.get_emoji();
         string category = data.get_category();
@@ -937,7 +921,8 @@ class IBusEmojier : Gtk.Window {
                                              uint modifiers) {
         assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right);
 
-        if (m_candidate_panel_is_visible) {
+        uint ncandidates = m_lookup_table.get_number_of_candidates();
+        if (m_candidate_panel_is_visible && ncandidates > 1) {
             enter_notify_disable_with_timer();
             if (keyval == Gdk.Key.Left)
                 m_lookup_table.cursor_up();
@@ -971,7 +956,8 @@ class IBusEmojier : Gtk.Window {
     private bool key_press_cursor_vertical(uint keyval) {
         assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up);
 
-        if (m_candidate_panel_is_visible) {
+        uint ncandidates = m_lookup_table.get_number_of_candidates();
+        if (m_candidate_panel_is_visible && ncandidates > 1) {
             if (keyval == Gdk.Key.Down)
                 candidate_panel_cursor_down();
             else if (keyval == Gdk.Key.Up)
@@ -987,12 +973,12 @@ class IBusEmojier : Gtk.Window {
                                            uint modifiers) {
         assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End);
 
-        if (m_candidate_panel_is_visible) {
+        uint ncandidates = m_lookup_table.get_number_of_candidates();
+        if (m_candidate_panel_is_visible && ncandidates > 1) {
             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();
@@ -1052,8 +1038,7 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    public string run(Gdk.Event event,
-                      string    input_context_path) {
+    public string run(string input_context_path) {
         assert (m_loop == null);
 
         m_is_running = true;
@@ -1066,65 +1051,22 @@ class IBusEmojier : Gtk.Window {
         resize(1, 1);
 
         m_entry.set_text("");
-        m_entry.grab_focus();
-
-        Gdk.Device device = event.get_device();
-        if (device == null) {
-            var display = get_display();
-            var device_manager = display.get_device_manager();
-            device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
-        }
-
-        Gdk.Device keyboard;
-        Gdk.Device pointer;
-        if (device.get_source() == Gdk.InputSource.KEYBOARD) {
-            keyboard = device;
-            pointer = device.get_associated_device();
-        } else {
-            pointer = device;
-            keyboard = device.get_associated_device();
-        }
+        //m_entry.grab_focus();
 
         show_category_list();
         m_entry.set_activates_default(true);
         show_all();
 
-        Gdk.GrabStatus status;
-        // Grab all keyboard events
-        status = keyboard.grab(get_window(),
-                               Gdk.GrabOwnership.NONE,
-                               true,
-                               Gdk.EventMask.KEY_PRESS_MASK |
-                               Gdk.EventMask.KEY_RELEASE_MASK,
-                               null,
-                               Gdk.CURRENT_TIME);
-        if (status != Gdk.GrabStatus.SUCCESS)
-            warning("Grab keyboard failed! status = %d", status);
-        // Grab all pointer events
-        status = pointer.grab(get_window(),
-                              Gdk.GrabOwnership.NONE,
-                              true,
-                              Gdk.EventMask.BUTTON_PRESS_MASK |
-                              Gdk.EventMask.BUTTON_RELEASE_MASK,
-                              null,
-                              Gdk.CURRENT_TIME);
-        if (status != Gdk.GrabStatus.SUCCESS)
-            warning("Grab pointer failed! status = %d", status);
-
         m_loop = new GLib.MainLoop();
-        m_title.set_loop(m_loop);
         m_loop.run();
-        m_title.unset_loop();
         m_loop = null;
 
-        keyboard.ungrab(Gdk.CURRENT_TIME);
-        pointer.ungrab(Gdk.CURRENT_TIME);
-
         // Need focus-out on Gtk.Entry to send the emoji to applications.
         Gdk.Event fevent = new Gdk.Event(Gdk.EventType.FOCUS_CHANGE);
         fevent.focus_change.in = 0;
         fevent.focus_change.window  = get_window();
         m_entry.send_focus_change(fevent);
+        fevent.focus_change.window  = null;
 
         hide();
         // Make sure the switcher is hidden before returning from this function.
@@ -1288,20 +1230,23 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    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) {
-            m_favorites += favorite;
-        }
+    public void present_centralize() {
+        present();
+        m_entry.set_activates_default(true);
+        Gtk.Allocation allocation;
+        get_allocation(out allocation);
+        Gdk.Screen screen = Gdk.Screen.get_default();
+        int monitor_num = screen.get_monitor_at_window(get_window());
+        Gdk.Rectangle monitor_area;
+        screen.get_monitor_geometry(monitor_num, out monitor_area);
+        int x = (monitor_area.x + monitor_area.width - allocation.width)/2;
+        int y = (monitor_area.y + monitor_area.height
+                 - allocation.height)/2;
+        move(x, y);
     }
 
 
-    public bool has_loaded_emoji_dict() {
+    public static bool has_loaded_emoji_dict() {
         if (m_emoji_to_data_dict == null)
             return false;
         GLib.List keys = m_emoji_to_data_dict.get_keys();
@@ -1311,18 +1256,26 @@ class IBusEmojier : Gtk.Window {
     }
 
 
-    public void set_annotation_lang(string lang) {
+    public static void set_annotation_lang(string? lang) {
+        if (lang == null || lang == "")
+            lang = "en";
         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;
-        });
+        reload_emoji_dict();
+    }
+
+
+    public static void set_emoji_font(string? emoji_font) {
+        return_if_fail(emoji_font != null && emoji_font != "");
+        m_emoji_font = emoji_font;
+    }
+
+
+    public static void set_favorites(string[]? unowned_favorites) {
+        m_favorites = {};
+        foreach (string favorite in unowned_favorites) {
+            m_favorites += favorite;
+        }
     }
 }
diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala
index eac8d6a..4564a25 100644
--- a/ui/gtk3/emojierapp.vala
+++ b/ui/gtk3/emojierapp.vala
@@ -24,7 +24,10 @@ string emoji_font = null;
 string annotation_lang = null;
 
 public class EmojiApplication : Application {
-    private IBusEmojier m_emojier = new IBusEmojier();
+    private IBusEmojier? m_emojier;
+    GLib.Settings m_settings_emoji =
+            new GLib.Settings("org.freedesktop.ibus.panel.emoji");
+
 
     private EmojiApplication() {
         Object(application_id: "org.freedesktop.ibus.panel.emojier",
@@ -34,41 +37,24 @@ public class EmojiApplication : Application {
 
 
     private void show_dialog(ApplicationCommandLine command_line) {
-        Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS);
-        var display = Gdk.Display.get_default();
-        var device_manager = display.get_device_manager();
-        var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data;
-        event.set_device(device);
-        string emoji = m_emojier.run(event, "");
+        m_emojier = new IBusEmojier();
+        string emoji = m_emojier.run("");
         if (emoji == null) {
-            m_emojier.reset();
+            m_emojier = null;
             command_line.print("%s\n", _("Canceled to choose an emoji."));
             return;
         }
         Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
         clipboard.set_text(emoji, -1);
         clipboard.store();
-        m_emojier.reset();
+        m_emojier = null;
         command_line.print("%s\n", _("Copied an emoji to your clipboard."));
     }
 
 
     public void activate_dialog(ApplicationCommandLine command_line) {
         this.hold ();
-
-        // show dialog
-        if (m_emojier.has_loaded_emoji_dict()) {
-            show_dialog(command_line);
-        } else {
-            m_emojier.loaded_emoji_dict.connect(() => {
-                // The signal is called when the language is changed.
-                if (m_emojier.is_running())
-                    return;
-
-                show_dialog(command_line);
-            });
-        }
-
+        show_dialog(command_line);
         this.release ();
     }
 
@@ -110,22 +96,18 @@ public class EmojiApplication : Application {
             return Posix.EXIT_FAILURE;
         }
 
-        if (emoji_font == null) {
-            GLib.Settings settings_emoji =
-                new GLib.Settings("org.freedesktop.ibus.panel.emoji");
-            emoji_font = settings_emoji.get_string("font");
+        if (m_emojier != null && m_emojier.is_running()) {
+            m_emojier.present_centralize();
+            return Posix.EXIT_SUCCESS;
         }
 
-        if (annotation_lang == null) {
-            GLib.Settings settings_emoji =
-                new GLib.Settings("org.freedesktop.ibus.panel.emoji");
-            annotation_lang = settings_emoji.get_string("lang");
-        }
+        if (emoji_font == null)
+            emoji_font = m_settings_emoji.get_string("font");
+        if (annotation_lang == null)
+            annotation_lang = m_settings_emoji.get_string("lang");
 
-        if (emoji_font != null && emoji_font != "")
-            m_emojier.set_emoji_font(emoji_font);
-        if (annotation_lang != null && annotation_lang != "")
-            m_emojier.set_annotation_lang(annotation_lang);
+        IBusEmojier.set_annotation_lang(annotation_lang);
+        IBusEmojier.set_emoji_font(emoji_font);
 
         activate_dialog(command_line);
 
@@ -156,5 +138,4 @@ public class EmojiApplication : Application {
         int status = app.run(args);
         return status;
     }
-
 }
diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h
index 0f84a48..1499a3c 100644
--- a/ui/gtk3/ibusemojidialog.h
+++ b/ui/gtk3/ibusemojidialog.h
@@ -69,7 +69,6 @@ IBusEmojier * ibus_emojier_new                    (void);
 /**
  * ibus_emojier_run:
  * @self: An #IBusEmojier
- * @event: An #GdkEvent
  * @input_context_path: An input context path of #IBusInputContext
  *                      of the focused application.
  *
@@ -78,7 +77,6 @@ IBusEmojier * ibus_emojier_new                    (void);
  * Returns: A selected emoji character.
  */
 gchar *       ibus_emojier_run                    (IBusEmojier* self,
-                                                   GdkEvent*    event,
                                                    const gchar*
                                                            input_context_path);
 
@@ -116,49 +114,49 @@ gchar *       ibus_emojier_get_selected_string    (IBusEmojier* self);
 void          ibus_emojier_reset                  (IBusEmojier* self);
 
 /**
- * ibus_emojier_set_emoji_font:
+ * ibus_emojier_present_centralize:
  * @self: An #IBusEmojier
- * @emoji_font: font name for emoji characters
  *
- * Set emoji font on the emoji dialog
+ * Move the window to the toplevel on the screen and centralize it.
  */
-void          ibus_emojier_set_emoji_font         (IBusEmojier* self,
-                                                   const gchar* emoji_font);
-
-#if 0
-/* TODO: set customized annotations */
-/**
- * ibus_emojier_set_favorites:
- * @self: An #IBusEmojier
- * @favorites: (array length=favorites_length): A custom emoji list.
- * @favorites_length: A length of @favorites
- *
- * Set emoji font on the emoji dialog
- */
-void          ibus_emojier_set_favorites          (IBusEmojier* self,
-                                                   gchar**      favorites,
-                                                   int
-                                                             favorites_length);
-#endif
+void          ibus_emojier_present_centralize     (IBusEmojier* self);
 
 /**
  * ibus_emojier_has_loaded_emoji_dict:
- * @self: An #IBusEmojier
  *
  * Returns: %TRUE if the emoji dict is loaded, otherwise %FALSE.
  */
-gboolean      ibus_emojier_has_loaded_emoji_dict  (IBusEmojier* self);
+gboolean      ibus_emojier_has_loaded_emoji_dict  (void);
 
 /**
  * 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);
+void          ibus_emojier_set_annotation_lang    (const gchar* lang);
 
+/**
+ * ibus_emojier_set_emoji_font:
+ * @emoji_font: font name for emoji characters
+ *
+ * Set emoji font on the emoji dialog
+ */
+void          ibus_emojier_set_emoji_font         (const gchar* emoji_font);
+
+#if 0
+/* TODO: set customized annotations */
+/**
+ * ibus_emojier_set_favorites:
+ * @favorites: (array length=favorites_length): A custom emoji list.
+ * @favorites_length: A length of @favorites
+ *
+ * Set emoji font on the emoji dialog
+ */
+void          ibus_emojier_set_favorites          (gchar**      favorites,
+                                                   int
+                                                             favorites_length);
+#endif
 G_END_DECLS
 #endif
diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala
index 7350dcc..2375734 100644
--- a/ui/gtk3/panel.vala
+++ b/ui/gtk3/panel.vala
@@ -73,7 +73,8 @@ class Panel : IBus.PanelService {
     private CandidatePanel m_candidate_panel;
     private Switcher m_switcher;
     private uint m_switcher_focus_set_engine_id;
-    private IBusEmojier m_emojier;
+    private IBusEmojier? m_emojier;
+    private uint m_emojier_set_emoji_lang_id;
     private uint m_emojier_focus_commit_text_id;
     private PropertyManager m_property_manager;
     private PropertyPanel m_property_panel;
@@ -137,7 +138,6 @@ class Panel : IBus.PanelService {
             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
         }
 
-        m_emojier = new IBusEmojier();
         bind_emoji_shortcut();
 
         m_property_manager = new PropertyManager();
@@ -595,7 +595,7 @@ class Panel : IBus.PanelService {
             warning("No config emoji:font.");
             return;
         }
-        m_emojier.set_emoji_font(emoji_font);
+        IBusEmojier.set_emoji_font(emoji_font);
 
         bool use_custom_font = m_settings_panel.get_boolean("use-custom-font");
 
@@ -766,11 +766,20 @@ class Panel : IBus.PanelService {
     }
 
     private void set_emoji_favorites() {
-        m_emojier.set_favorites(m_settings_emoji.get_strv("favorites"));
+        IBusEmojier.set_favorites(m_settings_emoji.get_strv("favorites"));
     }
 
     private void set_emoji_lang() {
-        m_emojier.set_annotation_lang(m_settings_emoji.get_string("lang"));
+        if (m_emojier_set_emoji_lang_id > 0) {
+            GLib.Source.remove(m_emojier_set_emoji_lang_id);
+            m_emojier_set_emoji_lang_id = 0;
+        }
+        m_emojier_set_emoji_lang_id = GLib.Idle.add(() => {
+            IBusEmojier.set_annotation_lang(
+                    m_settings_emoji.get_string("lang"));
+            m_emojier_set_emoji_lang_id = 0;
+            return false;
+        });
     }
 
     private int compare_versions(string version1, string version2) {
@@ -984,15 +993,24 @@ class Panel : IBus.PanelService {
         }
     }
 
-    private void handle_emoji_typing(Gdk.Event event) {
-        if (m_emojier.is_running())
-            return;
-        string emoji = m_emojier.run(event, m_real_current_context_path);
-        if (emoji == null)
+    private void show_emojier() {
+        m_emojier = new IBusEmojier();
+        string emoji = m_emojier.run(m_real_current_context_path);
+        if (emoji == null) {
+            m_emojier = null;
             return;
+        }
         this.emojier_focus_commit();
     }
 
+    private void handle_emoji_typing(Gdk.Event event) {
+        if (m_emojier != null && m_emojier.is_running()) {
+            m_emojier.present_centralize();
+            return;
+        }
+        show_emojier();
+    }
+
     private void run_preload_engines(IBus.EngineDesc[] engines, int index) {
         string[] names = {};
 
@@ -1361,29 +1379,34 @@ class Panel : IBus.PanelService {
         if (selected_engine == null &&
             prev_context_path != "" &&
             m_switcher.is_running()) {
-            if (m_switcher_focus_set_engine_id > 0) {
+            var context = GLib.MainContext.default();
+            if (m_switcher_focus_set_engine_id > 0 &&
+                context.find_source_by_id(m_switcher_focus_set_engine_id)
+                        != null) {
                 GLib.Source.remove(m_switcher_focus_set_engine_id);
             }
             m_switcher_focus_set_engine_id = GLib.Timeout.add(100, () => {
                 // focus_in is comming before switcher returns
                 switcher_focus_set_engine_real();
-                if (m_switcher_focus_set_engine_id > 0) {
-                    GLib.Source.remove(m_switcher_focus_set_engine_id);
-                    m_switcher_focus_set_engine_id = -1;
-                }
+                m_switcher_focus_set_engine_id = -1;
                 return false;
             });
         } else {
             if (switcher_focus_set_engine_real()) {
-                if (m_switcher_focus_set_engine_id > 0) {
+                var context = GLib.MainContext.default();
+                if (m_switcher_focus_set_engine_id > 0 &&
+                    context.find_source_by_id(m_switcher_focus_set_engine_id)
+                            != null) {
                     GLib.Source.remove(m_switcher_focus_set_engine_id);
-                    m_switcher_focus_set_engine_id = -1;
                 }
+                m_switcher_focus_set_engine_id = -1;
             }
         }
     }
 
     private bool emojier_focus_commit_real() {
+        if (m_emojier == null)
+            return true;
         string selected_string = m_emojier.get_selected_string();
         string prev_context_path = m_emojier.get_input_context_path();
         if (selected_string != null &&
@@ -1391,7 +1414,7 @@ class Panel : IBus.PanelService {
             prev_context_path == m_current_context_path) {
             IBus.Text text = new IBus.Text.from_string(selected_string);
             commit_text(text);
-            m_emojier.reset();
+            m_emojier = null;
             return true;
         }
 
@@ -1399,12 +1422,17 @@ class Panel : IBus.PanelService {
     }
 
     private void emojier_focus_commit() {
+        if (m_emojier == null)
+            return;
         string selected_string = m_emojier.get_selected_string();
         string prev_context_path = m_emojier.get_input_context_path();
         if (selected_string == null &&
             prev_context_path != "" &&
             m_emojier.is_running()) {
-            if (m_emojier_focus_commit_text_id > 0) {
+            var context = GLib.MainContext.default();
+            if (m_emojier_focus_commit_text_id > 0 &&
+                context.find_source_by_id(m_emojier_focus_commit_text_id)
+                        != null) {
                 GLib.Source.remove(m_emojier_focus_commit_text_id);
             }
             m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => {
@@ -1415,10 +1443,13 @@ class Panel : IBus.PanelService {
             });
         } else {
             if (emojier_focus_commit_real()) {
-                if (m_emojier_focus_commit_text_id > 0) {
+                var context = GLib.MainContext.default();
+                if (m_emojier_focus_commit_text_id > 0 &&
+                    context.find_source_by_id(m_emojier_focus_commit_text_id)
+                            != null) {
                     GLib.Source.remove(m_emojier_focus_commit_text_id);
-                    m_emojier_focus_commit_text_id = -1;
                 }
+                m_emojier_focus_commit_text_id = -1;
             }
         }
     }
-- 
2.9.3

From 39744bf98c4d6c2677b1e44cdb40cde7d0029a25 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 19 Apr 2017 15:47:36 +0900
Subject: [PATCH] ui/gtk3: Fix build failures

---
 ui/gtk3/Makefile.am | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am
index 6f0fb62..d2ae0d6 100644
--- a/ui/gtk3/Makefile.am
+++ b/ui/gtk3/Makefile.am
@@ -163,13 +163,7 @@ EXTRA_DIST =                            \
     $(NULL)
 
 if ENABLE_EMOJI_DICT
-AM_VALAFLAGS += \
-    --define=EMOJI_DICT \
-    --vapidir=$(top_builddir)/ui/gtk3 \
-    --vapidir=$(top_srcdir)/ui/gtk3 \
-    --pkg=ibus-emoji-dialog-1.0 \
-    --pkg=gtk+-3.0 \
-    $(NULL)
+AM_VALAFLAGS += --define=EMOJI_DICT
 
 libibus_emoji_dialog = libibus-emoji-dialog-1.0.la
 
-- 
2.9.3