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