Blame bindings/vala/emojier.vala

Packit 3ff832
/* vim:set et sts=4 sw=4:
Packit 3ff832
 *
Packit 3ff832
 * ibus - The Input Bus
Packit 3ff832
 *
Packit 3ff832
 * Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com>
Packit 3ff832
 *
Packit 3ff832
 * This library is free software; you can redistribute it and/or
Packit 3ff832
 * modify it under the terms of the GNU Lesser General Public
Packit 3ff832
 * License as published by the Free Software Foundation; either
Packit 3ff832
 * version 2.1 of the License, or (at your option) any later version.
Packit 3ff832
 *
Packit 3ff832
 * This library is distributed in the hope that it will be useful,
Packit 3ff832
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 3ff832
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 3ff832
 * Lesser General Public License for more details.
Packit 3ff832
 *
Packit 3ff832
 * You should have received a copy of the GNU Lesser General Public
Packit 3ff832
 * License along with this library; if not, write to the Free Software
Packit 3ff832
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
Packit 3ff832
 * USA
Packit 3ff832
 */
Packit 3ff832
Packit 3ff832
public class IBusEmojier : Gtk.ApplicationWindow {
Packit 3ff832
    private class EEntry : Gtk.SearchEntry {
Packit 3ff832
        public EEntry() {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierEntry",
Packit 3ff832
                margin_start : 6,
Packit 3ff832
                margin_end : 6,
Packit 3ff832
                margin_top : 6,
Packit 3ff832
                margin_bottom : 6
Packit 3ff832
            );
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EListBox : Gtk.ListBox {
Packit 3ff832
        public EListBox() {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                vexpand : true,
Packit 3ff832
                halign : Gtk.Align.FILL,
Packit 3ff832
                valign : Gtk.Align.FILL
Packit 3ff832
            );
Packit 3ff832
            this.motion_notify_event.connect((e) => {
Packit 3ff832
#if VALA_0_24
Packit 3ff832
                Gdk.EventMotion pe = e;
Packit 3ff832
#else
Packit 3ff832
                Gdk.EventMotion *pe = &e;
Packit 3ff832
#endif
Packit 3ff832
                if (m_mouse_x == pe.x_root && m_mouse_y == pe.y_root)
Packit 3ff832
                    return false;
Packit 3ff832
                m_mouse_x = pe.x_root;
Packit 3ff832
                m_mouse_y = pe.y_root;
Packit 3ff832
                var row = this.get_row_at_y((int)e.y);
Packit 3ff832
                if (row != null)
Packit 3ff832
                    this.select_row(row);
Packit 3ff832
                return false;
Packit 3ff832
            });
Packit 3ff832
            this.enter_notify_event.connect((e) => {
Packit 3ff832
                // avoid gtk_button_update_state()
Packit 3ff832
                return true;
Packit 3ff832
            });
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EBoxRow : Gtk.ListBoxRow {
Packit 3ff832
        public EBoxRow(string text) {
Packit 3ff832
            this.text = text;
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        public string text { get; set; }
Packit 3ff832
    }
Packit 3ff832
    private class EScrolledWindow : Gtk.ScrolledWindow {
Packit 3ff832
        public EScrolledWindow(Gtk.Adjustment? hadjustment=null,
Packit 3ff832
                               Gtk.Adjustment? vadjustment=null) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                hscrollbar_policy : Gtk.PolicyType.NEVER,
Packit 3ff832
                vscrollbar_policy : Gtk.PolicyType.NEVER,
Packit 3ff832
                shadow_type : Gtk.ShadowType.IN,
Packit 3ff832
                margin_start : 6,
Packit 3ff832
                margin_end : 6,
Packit 3ff832
                margin_top : 6,
Packit 3ff832
                margin_bottom : 6
Packit 3ff832
            );
Packit 3ff832
            if (hadjustment != null)
Packit 3ff832
                set_hadjustment(hadjustment);
Packit 3ff832
            if (vadjustment != null)
Packit 3ff832
                set_hadjustment(vadjustment);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EGrid : Gtk.Grid {
Packit 3ff832
        public EGrid() {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                row_homogeneous : false,
Packit 3ff832
                vexpand : true,
Packit 3ff832
                halign : Gtk.Align.FILL,
Packit 3ff832
                valign : Gtk.Align.FILL,
Packit 3ff832
                row_spacing : 5,
Packit 3ff832
                column_spacing : 5,
Packit 3ff832
                border_width : 2
Packit 3ff832
            );
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EWhiteLabel : Gtk.Label {
Packit 3ff832
        private int m_minimum_width = 0;
Packit 3ff832
        private int m_natural_width = 0;
Packit 3ff832
        private int m_minimum_height = 0;
Packit 3ff832
        private int m_natural_height = 0;
Packit 3ff832
        public EWhiteLabel(string text) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierWhiteLabel"
Packit 3ff832
            );
Packit 3ff832
            set_label(text);
Packit 3ff832
        }
Packit 3ff832
        public override void get_preferred_width(out int minimum_width,
Packit 3ff832
                                                 out int natural_width) {
Packit 3ff832
            if (m_minimum_height == 0 && m_natural_height == 0) {
Packit 3ff832
                base.get_preferred_height(out m_minimum_height,
Packit 3ff832
                                          out m_natural_height);
Packit 3ff832
            }
Packit 3ff832
            var text = get_label();
Packit 3ff832
            var ch = text.get_char();
Packit 3ff832
            if (text.length == 1 && ch == '\t') {
Packit 3ff832
                m_minimum_width = minimum_width = m_minimum_height;
Packit 3ff832
                m_natural_width = natural_width = m_natural_height;
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            base.get_preferred_width(out minimum_width, out natural_width);
Packit 3ff832
            if (text.length == 1 && (ch == '\n' || ch == '\r')) {
Packit 3ff832
                minimum_width /= 2;
Packit 3ff832
                natural_width /= 2;
Packit 3ff832
                m_minimum_width = minimum_width;
Packit 3ff832
                m_natural_width = natural_width;
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            if (minimum_width < m_minimum_height)
Packit 3ff832
                minimum_width = m_minimum_height;
Packit 3ff832
            if (natural_width < m_natural_height)
Packit 3ff832
                natural_width = m_natural_height;
Packit 3ff832
            m_minimum_width = minimum_width;
Packit 3ff832
            m_natural_width = natural_width;
Packit 3ff832
        }
Packit 3ff832
        public override void get_preferred_height(out int minimum_height,
Packit 3ff832
                                                  out int natural_height) {
Packit 3ff832
            if (m_minimum_width == 0 && m_natural_width == 0) {
Packit 3ff832
                base.get_preferred_width(out m_minimum_width,
Packit 3ff832
                                         out m_natural_width);
Packit 3ff832
            }
Packit 3ff832
            var text = get_label();
Packit 3ff832
            var ch = text.get_char();
Packit 3ff832
            if (text.length == 1 && ch == '\v') {
Packit 3ff832
                m_minimum_height = minimum_height = m_minimum_width;
Packit 3ff832
                m_natural_height = natural_height = m_natural_width;
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            base.get_preferred_height(out minimum_height, out natural_height);
Packit 3ff832
            if (text.length == 1 && (ch == '\n' || ch == '\r')) {
Packit 3ff832
                minimum_height /= 2;
Packit 3ff832
                natural_height /= 2;
Packit 3ff832
                m_minimum_height = minimum_height;
Packit 3ff832
                m_natural_height = natural_height;
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            m_minimum_height = minimum_height;
Packit 3ff832
            m_natural_height = natural_height;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class ESelectedLabel : EWhiteLabel {
Packit 3ff832
        public ESelectedLabel(string text) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierSelectedLabel"
Packit 3ff832
            );
Packit 3ff832
            if (text != "")
Packit 3ff832
                set_label(text);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EGoldLabel : EWhiteLabel {
Packit 3ff832
        public EGoldLabel(string text) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierGoldLabel"
Packit 3ff832
            );
Packit 3ff832
            if (text != "")
Packit 3ff832
                set_label(text);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EPaddedLabel : Gtk.Label {
Packit 3ff832
        public EPaddedLabel(string          text,
Packit 3ff832
                            Gtk.Align       align) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierPaddedLabel",
Packit 3ff832
                halign : align,
Packit 3ff832
                valign : Gtk.Align.CENTER,
Packit 3ff832
                margin_start : 20,
Packit 3ff832
                margin_end : 20,
Packit 3ff832
                margin_top : 6,
Packit 3ff832
                margin_bottom : 6
Packit 3ff832
            );
Packit 3ff832
            set_text(text);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class EPaddedLabelBox : Gtk.Box {
Packit 3ff832
        public EPaddedLabelBox(string          text,
Packit 3ff832
                               Gtk.Align       align,
Packit 3ff832
                               TravelDirection direction=TravelDirection.NONE,
Packit 3ff832
                               string?         caption=null) {
Packit 3ff832
            GLib.Object(
Packit 3ff832
                name : "IBusEmojierPaddedLabelBox",
Packit 3ff832
                orientation : Gtk.Orientation.HORIZONTAL,
Packit 3ff832
                spacing : 0
Packit 3ff832
            );
Packit 3ff832
            if (direction == TravelDirection.BACKWARD) {
Packit 3ff832
                IconWidget icon;
Packit 3ff832
                if (Gtk.Widget.get_default_direction() ==
Packit 3ff832
                    Gtk.TextDirection.RTL) {
Packit 3ff832
                    icon = new IconWidget("go-previous-rtl-symbolic",
Packit 3ff832
                                          Gtk.IconSize.MENU);
Packit 3ff832
                } else {
Packit 3ff832
                    icon = new IconWidget("go-previous-symbolic",
Packit 3ff832
                                          Gtk.IconSize.MENU);
Packit 3ff832
                }
Packit 3ff832
                pack_start(icon, false, true, 0);
Packit 3ff832
            }
Packit 3ff832
            EPaddedLabel label = new EPaddedLabel(text, align);
Packit 3ff832
            pack_start(label, true, true, 0);
Packit 3ff832
            if (caption != null) {
Packit 3ff832
                EPaddedLabel label_r = new EPaddedLabel(caption,
Packit 3ff832
                                                        Gtk.Align.END);
Packit 3ff832
                pack_end(label_r, true, true, 0);
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
    private class LoadProgressObject : GLib.Object {
Packit 3ff832
        public LoadProgressObject() {
Packit 3ff832
        }
Packit 3ff832
        public signal void deserialize_unicode(uint done, uint total);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private enum TravelDirection {
Packit 3ff832
        NONE,
Packit 3ff832
        BACKWARD,
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
    public const uint BUTTON_CLOSE_BUTTON = 1000;
Packit 3ff832
Packit 3ff832
    private const uint EMOJI_GRID_PAGE = 10;
Packit 3ff832
    private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites");
Packit 3ff832
    private const string EMOJI_CATEGORY_OTHERS = N_("Others");
Packit 3ff832
    private const string EMOJI_CATEGORY_UNICODE = N_("Open Unicode choice");
Packit 3ff832
    private const unichar[] EMOJI_VARIANT_LIST = {
Packit 3ff832
            0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff, 0x200d };
Packit 3ff832
Packit 3ff832
    // Set the actual default values in the constructor
Packit 3ff832
    // because these fields are used for class_init() and static functions,
Packit 3ff832
    // e.g. set_emoji_font(), can be called before class_init() is called.
Packit 3ff832
    private static string m_current_lang_id;
Packit 3ff832
    private static string m_emoji_font_family;
Packit 3ff832
    private static int m_emoji_font_size;
Packit 3ff832
    private static bool m_emoji_font_changed = false;
Packit 3ff832
    private static string[] m_favorites;
Packit 3ff832
    private static string[] m_favorite_annotations;
Packit 3ff832
    private static int m_emoji_max_seq_len;
Packit 3ff832
    private static bool m_has_partial_match;
Packit 3ff832
    private static uint m_partial_match_length;
Packit 3ff832
    private static uint m_partial_match_condition;
Packit 3ff832
    private static bool m_show_emoji_variant = false;
Packit 3ff832
    private static int m_default_window_width;
Packit 3ff832
    private static int m_default_window_height;
Packit 3ff832
    private static GLib.HashTable<string, GLib.SList<string>>?
Packit 3ff832
            m_annotation_to_emojis_dict;
Packit 3ff832
    private static GLib.HashTable<string, IBus.EmojiData>?
Packit 3ff832
            m_emoji_to_data_dict;
Packit 3ff832
    private static GLib.HashTable<string, GLib.SList<string>>?
Packit 3ff832
            m_category_to_emojis_dict;
Packit 3ff832
    private static GLib.HashTable<string, GLib.SList<string>>?
Packit 3ff832
            m_emoji_to_emoji_variants_dict;
Packit 3ff832
    private static GLib.HashTable<unichar, IBus.UnicodeData>?
Packit 3ff832
            m_unicode_to_data_dict;
Packit 3ff832
    private static GLib.HashTable<string, GLib.SList<unichar>>?
Packit 3ff832
            m_name_to_unicodes_dict;
Packit 3ff832
    private static GLib.SList<IBus.UnicodeBlock> m_unicode_block_list;
Packit 3ff832
    private static bool m_show_unicode = false;
Packit 3ff832
    private static LoadProgressObject m_unicode_progress_object;
Packit 3ff832
    private static bool m_loaded_unicode = false;
Packit 3ff832
    private static string m_warning_message = "";
Packit 3ff832
Packit 3ff832
    private ThemedRGBA m_rgba;
Packit 3ff832
    private Gtk.Box m_vbox;
Packit 3ff832
    private EEntry m_entry;
Packit 3ff832
    /* If emojier is emoji category list or Unicode category list,
Packit 3ff832
     * m_annotation is "" and preedit is also "".
Packit 3ff832
     * If emojier is candidate mode, m_annotation is an annotation and
Packit 3ff832
     * get_current_candidate() returns the current emoji.
Packit 3ff832
     * But the current preedit can be "" in candidate mode in case that
Packit 3ff832
     * Unicode candidate window has U+0000.
Packit 3ff832
     */
Packit 3ff832
    private string m_annotation = "";
Packit 3ff832
    private string? m_backward;
Packit 3ff832
    private int m_backward_index = -1;
Packit 3ff832
    private EScrolledWindow? m_scrolled_window = null;
Packit 3ff832
    private EListBox m_list_box;
Packit 3ff832
    private bool m_is_running = false;
Packit 3ff832
    private string m_input_context_path = "";
Packit 3ff832
    private GLib.MainLoop? m_loop;
Packit 3ff832
    private string? m_result;
Packit 3ff832
    /* If m_candidate_panel_is_visible is true, emojier is candidate mode and
Packit 3ff832
     * the emoji lookup window is visible.
Packit 3ff832
     * If m_candidate_panel_is_visible is false, the emoji lookup window is
Packit 3ff832
     * not visible but the mode is not clear.
Packit 3ff832
     */
Packit 3ff832
    private bool m_candidate_panel_is_visible;
Packit 3ff832
    /* If m_candidate_panel_mode is true, emojier is candidate mode and
Packit 3ff832
     * it does not depend on whether the window is visible or not.
Packit 3ff832
     * I.E. the first candidate does not show the lookup window and the
Packit 3ff832
     * second one shows the window.
Packit 3ff832
     * If m_candidate_panel_mode is false, emojier is emoji category list or
Packit 3ff832
     * Unicode category list.
Packit 3ff832
     */
Packit 3ff832
    private bool m_candidate_panel_mode;
Packit 3ff832
    private int m_category_active_index = -1;
Packit 3ff832
    private IBus.LookupTable m_lookup_table;
Packit 3ff832
    private Gtk.Label[] m_candidates;
Packit 3ff832
    private bool m_enter_notify_enable = true;
Packit 3ff832
    private uint m_entry_notify_show_id;
Packit 3ff832
    private uint m_entry_notify_disable_id;
Packit 3ff832
    protected static double m_mouse_x;
Packit 3ff832
    protected static double m_mouse_y;
Packit 3ff832
    private Gtk.ProgressBar m_unicode_progress_bar;
Packit 3ff832
    private uint m_unicode_progress_id;
Packit 3ff832
    private Gtk.Label m_unicode_percent_label;
Packit 3ff832
    private double m_unicode_percent;
Packit 3ff832
    private Gdk.Rectangle m_cursor_location;
Packit 3ff832
    private bool m_is_up_side_down = false;
Packit 3ff832
    private uint m_redraw_window_id;
Packit 3ff832
Packit 3ff832
    public signal void candidate_clicked(uint index, uint button, uint state);
Packit 3ff832
Packit 3ff832
    public IBusEmojier() {
Packit 3ff832
        GLib.Object(
Packit 3ff832
            type : Gtk.WindowType.POPUP
Packit 3ff832
        );
Packit 3ff832
Packit 3ff832
        // GLib.ActionEntry accepts const variables only.
Packit 3ff832
        var action = new GLib.SimpleAction.stateful(
Packit 3ff832
                "variant",
Packit 3ff832
                null,
Packit 3ff832
                new GLib.Variant.boolean(m_show_emoji_variant));
Packit 3ff832
        action.activate.connect(check_action_variant_cb);
Packit 3ff832
        add_action(action);
Packit 3ff832
        action = new GLib.SimpleAction("close", null);
Packit 3ff832
        action.activate.connect(action_close_cb);
Packit 3ff832
        add_action(action);
Packit 3ff832
        if (m_current_lang_id == null)
Packit 3ff832
            m_current_lang_id = "en";
Packit 3ff832
        if (m_emoji_font_family == null) {
Packit 3ff832
            m_emoji_font_family = "Monospace";
Packit 3ff832
            m_emoji_font_changed = true;
Packit 3ff832
        }
Packit 3ff832
        if (m_emoji_font_size == 0) {
Packit 3ff832
            m_emoji_font_size = 16;
Packit 3ff832
            m_emoji_font_changed = true;
Packit 3ff832
        }
Packit 3ff832
        if (m_favorites == null)
Packit 3ff832
            m_favorites = {};
Packit 3ff832
        if (m_favorite_annotations == null)
Packit 3ff832
            m_favorite_annotations = {};
Packit 3ff832
Packit 3ff832
        set_css_data();
Packit 3ff832
Packit 3ff832
        m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
Packit 3ff832
        add(m_vbox);
Packit 3ff832
Packit 3ff832
        m_entry = new EEntry();
Packit 3ff832
        m_entry.set_placeholder_text(_("Type annotation or choose emoji"));
Packit 3ff832
        //m_vbox.add(m_entry);
Packit 3ff832
        m_entry.changed.connect(() => {
Packit 3ff832
            update_candidate_window();
Packit 3ff832
        });
Packit 3ff832
        m_entry.icon_release.connect((icon_pos, event) => {
Packit 3ff832
            hide_candidate_panel();
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
        /* Set the accessible role of the label to a status bar so it
Packit 3ff832
         * will emit name changed events that can be used by screen
Packit 3ff832
         * readers.
Packit 3ff832
         */
Packit 3ff832
        Atk.Object obj = m_entry.get_accessible();
Packit 3ff832
        obj.set_role (Atk.Role.STATUSBAR);
Packit 3ff832
Packit 3ff832
        // The constructor of IBus.LookupTable does not support more than
Packit 3ff832
        // 16 pages.
Packit 3ff832
        m_lookup_table = new IBus.LookupTable(1, 0, true, true);
Packit 3ff832
        m_lookup_table.set_page_size(EMOJI_GRID_PAGE * EMOJI_GRID_PAGE);
Packit 3ff832
Packit 3ff832
        hide.connect(() => {
Packit 3ff832
            if (m_loop != null && m_loop.is_running())
Packit 3ff832
                m_loop.quit();
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
        size_allocate.connect((w, a) => {
Packit 3ff832
            adjust_window_position();
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
        candidate_clicked.connect((i, b, s) => {
Packit 3ff832
            if (m_input_context_path != "")
Packit 3ff832
                candidate_panel_select_index(i, b);
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
Packit 3ff832
        if (m_annotation_to_emojis_dict == null) {
Packit 3ff832
            reload_emoji_dict();
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        get_load_progress_object();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void reload_emoji_dict() {
Packit 3ff832
        init_emoji_dict();
Packit 3ff832
        make_emoji_dict("en");
Packit 3ff832
        if (m_current_lang_id != "en") {
Packit 3ff832
            var lang_ids = m_current_lang_id.split("_");
Packit 3ff832
            if (lang_ids.length > 1) {
Packit 3ff832
                string sub_id = lang_ids[0];
Packit 3ff832
                make_emoji_dict(sub_id);
Packit 3ff832
            }
Packit 3ff832
            make_emoji_dict(m_current_lang_id);
Packit 3ff832
        }
Packit 3ff832
        update_favorite_emoji_dict();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void init_emoji_dict() {
Packit 3ff832
        m_annotation_to_emojis_dict =
Packit 3ff832
                new GLib.HashTable<string, GLib.SList<string>>(GLib.str_hash,
Packit 3ff832
                                                               GLib.str_equal);
Packit 3ff832
        m_emoji_to_data_dict =
Packit 3ff832
                new GLib.HashTable<string, IBus.EmojiData>(GLib.str_hash,
Packit 3ff832
                                                           GLib.str_equal);
Packit 3ff832
        m_category_to_emojis_dict =
Packit 3ff832
                new GLib.HashTable<string, GLib.SList<string>>(GLib.str_hash,
Packit 3ff832
                                                               GLib.str_equal);
Packit 3ff832
        m_emoji_to_emoji_variants_dict =
Packit 3ff832
                new GLib.HashTable<string, GLib.SList<string>>(GLib.str_hash,
Packit 3ff832
                                                               GLib.str_equal);
Packit 3ff832
        m_unicode_to_data_dict =
Packit 3ff832
                new GLib.HashTable<unichar, IBus.UnicodeData>(
Packit 3ff832
                        GLib.direct_hash,
Packit 3ff832
                        GLib.direct_equal);
Packit 3ff832
        m_name_to_unicodes_dict =
Packit 3ff832
                new GLib.HashTable<string, GLib.SList<unichar>>(GLib.str_hash,
Packit 3ff832
                                                                GLib.str_equal);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void make_emoji_dict(string lang) {
Packit 3ff832
        GLib.SList<IBus.EmojiData> emoji_list = IBus.EmojiData.load(
Packit 3ff832
                    Config.PKGDATADIR + "/dicts/emoji-" + lang + ".dict");
Packit 3ff832
        if (emoji_list == null)
Packit 3ff832
            return;
Packit 3ff832
        foreach (IBus.EmojiData data in emoji_list) {
Packit 3ff832
            update_emoji_to_data_dict(data, lang);
Packit 3ff832
            update_annotation_to_emojis_dict(data);
Packit 3ff832
            update_category_to_emojis_dict(data, lang);
Packit 3ff832
        }
Packit 3ff832
        GLib.List<unowned string> annotations =
Packit 3ff832
                m_annotation_to_emojis_dict.get_keys();
Packit 3ff832
        foreach (unowned string annotation in annotations) {
Packit 3ff832
            if (m_emoji_max_seq_len < annotation.length)
Packit 3ff832
                m_emoji_max_seq_len = annotation.length;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void update_annotation_to_emojis_dict(IBus.EmojiData data) {
Packit 3ff832
        string emoji = data.get_emoji();
Packit 3ff832
        unowned GLib.SList<string> annotations = data.get_annotations();
Packit 3ff832
        foreach (string annotation in annotations) {
Packit 3ff832
            bool has_emoji = false;
Packit 3ff832
            GLib.SList<string> hits =
Packit 3ff832
                    m_annotation_to_emojis_dict.lookup(annotation).copy_deep(
Packit 3ff832
                            GLib.strdup);
Packit 3ff832
            foreach (string hit_emoji in hits) {
Packit 3ff832
                if (hit_emoji == emoji) {
Packit 3ff832
                    has_emoji = true;
Packit 3ff832
                    break;
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            if (!has_emoji) {
Packit 3ff832
                hits.append(emoji);
Packit 3ff832
                m_annotation_to_emojis_dict.replace(
Packit 3ff832
                        annotation,
Packit 3ff832
                        hits.copy_deep(GLib.strdup));
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static string utf8_code_point(string str) {
Packit 3ff832
        var buff = new GLib.StringBuilder();
Packit 3ff832
        int length = str.char_count();
Packit 3ff832
        if (length == 0) {
Packit 3ff832
            buff.append("U+%04X".printf(0));
Packit 3ff832
            return buff.str;
Packit 3ff832
        }
Packit 3ff832
        for (int i = 0; i < length; i++) {
Packit 3ff832
            unichar ch = str.get_char(0);
Packit 3ff832
            if (i == 0)
Packit 3ff832
                buff.append("U+%04X".printf(ch));
Packit 3ff832
            else
Packit 3ff832
                buff.append(" %04X".printf(ch));
Packit 3ff832
            str = str.next_char();
Packit 3ff832
        }
Packit 3ff832
        return buff.str;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static string utf8_entity(string str) {
Packit 3ff832
        var buff = new GLib.StringBuilder();
Packit 3ff832
        int length = str.char_count();
Packit 3ff832
        for (int i = 0; i < length; i++) {
Packit 3ff832
            unichar ch = str.get_char(0);
Packit 3ff832
            switch(ch) {
Packit 3ff832
            case '<':
Packit 3ff832
                buff.append("<");
Packit 3ff832
                break;
Packit 3ff832
            case '>':
Packit 3ff832
                buff.append(">");
Packit 3ff832
                break;
Packit 3ff832
            case '&':
Packit 3ff832
                buff.append("&");
Packit 3ff832
                break;
Packit 3ff832
            default:
Packit 3ff832
                buff.append_unichar(ch);
Packit 3ff832
                break;
Packit 3ff832
            }
Packit 3ff832
            str = str.next_char();
Packit 3ff832
        }
Packit 3ff832
        return buff.str;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void
Packit 3ff832
    update_annotations_with_description (IBus.EmojiData data,
Packit 3ff832
                                         string         description) {
Packit 3ff832
        GLib.SList<string> annotations =
Packit 3ff832
                data.get_annotations().copy_deep(GLib.strdup);
Packit 3ff832
        bool update_annotations = false;
Packit 3ff832
        string former = null;
Packit 3ff832
        string later = null;
Packit 3ff832
        int index = description.index_of(": ");
Packit 3ff832
        if (index > 0) {
Packit 3ff832
            former = description.substring(0, index);
Packit 3ff832
            if (annotations.find_custom(former, GLib.strcmp) == null) {
Packit 3ff832
                annotations.append(former);
Packit 3ff832
                update_annotations = true;
Packit 3ff832
            }
Packit 3ff832
            later = description.substring(index + 2);
Packit 3ff832
        } else {
Packit 3ff832
            later = description.dup();
Packit 3ff832
        }
Packit 3ff832
        var words = later.split(" ");
Packit 3ff832
        // If the description has less than 3 words, add it to annotations
Packit 3ff832
        // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
Packit 3ff832
        if (words.length < 3 &&
Packit 3ff832
            annotations.find_custom(
Packit 3ff832
                    later,
Packit 3ff832
                    GLib.strcmp) == null) {
Packit 3ff832
            annotations.append(later);
Packit 3ff832
            update_annotations = true;
Packit 3ff832
        }
Packit 3ff832
        if (update_annotations)
Packit 3ff832
            data.set_annotations(annotations.copy_deep(GLib.strdup));
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void update_emoji_to_data_dict(IBus.EmojiData data,
Packit 3ff832
                                                  string         lang) {
Packit 3ff832
        string emoji = data.get_emoji();
Packit 3ff832
        if (lang == "en") {
Packit 3ff832
            string description = data.get_description().down();
Packit 3ff832
            update_annotations_with_description (data, description);
Packit 3ff832
            m_emoji_to_data_dict.replace(emoji, data);
Packit 3ff832
        } else {
Packit 3ff832
            unowned IBus.EmojiData? en_data = null;
Packit 3ff832
            en_data = m_emoji_to_data_dict.lookup(emoji);
Packit 3ff832
            if (en_data == null) {
Packit 3ff832
                m_emoji_to_data_dict.insert(emoji, data);
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            string trans_description = data.get_description();
Packit 3ff832
            en_data.set_description(trans_description);
Packit 3ff832
            trans_description = trans_description.down();
Packit 3ff832
            update_annotations_with_description (data, trans_description);
Packit 3ff832
            unowned GLib.SList<string> annotations = data.get_annotations();
Packit 3ff832
            unowned GLib.SList<string> en_annotations
Packit 3ff832
                = en_data.get_annotations();
Packit 3ff832
            foreach (string annotation in en_annotations) {
Packit 3ff832
                // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
Packit 3ff832
                if (annotations.find_custom(
Packit 3ff832
                            annotation,
Packit 3ff832
                            GLib.strcmp) == null) {
Packit 3ff832
                    annotations.append(annotation.dup());
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            en_data.set_annotations(annotations.copy_deep(GLib.strdup));
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void update_category_to_emojis_dict(IBus.EmojiData data,
Packit 3ff832
                                                       string         lang) {
Packit 3ff832
        string emoji = data.get_emoji();
Packit 3ff832
        string category = data.get_category();
Packit 3ff832
        if (category == "")
Packit 3ff832
            category = EMOJI_CATEGORY_OTHERS;
Packit 3ff832
        if (lang == "en") {
Packit 3ff832
            bool has_variant = false;
Packit 3ff832
            foreach (unichar ch in EMOJI_VARIANT_LIST) {
Packit 3ff832
                if (emoji.index_of_char(ch) >= 0) {
Packit 3ff832
                    has_variant = true;
Packit 3ff832
                    break;
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            // If emoji includes variants (skin colors and items),
Packit 3ff832
            // it's escaped in m_emoji_to_emoji_variants_dict and
Packit 3ff832
            // not shown by default.
Packit 3ff832
            if (has_variant) {
Packit 3ff832
                unichar base_ch = emoji.get_char();
Packit 3ff832
                string base_emoji  = base_ch.to_string();
Packit 3ff832
                var buff = new GLib.StringBuilder();
Packit 3ff832
                buff.append_unichar(base_ch);
Packit 3ff832
                buff.append_unichar(0xfe0f);
Packit 3ff832
                if (m_emoji_to_data_dict.lookup(buff.str) != null)
Packit 3ff832
                    base_emoji = buff.str;
Packit 3ff832
                GLib.SList<string>? variants =
Packit 3ff832
                        m_emoji_to_emoji_variants_dict.lookup(
Packit 3ff832
                                base_emoji).copy_deep(GLib.strdup);
Packit 3ff832
                if (variants.find_custom(emoji, GLib.strcmp) == null) {
Packit 3ff832
                    if (variants == null)
Packit 3ff832
                        variants.append(base_emoji);
Packit 3ff832
                    variants.append(emoji);
Packit 3ff832
                    m_emoji_to_emoji_variants_dict.replace(
Packit 3ff832
                            base_emoji,
Packit 3ff832
                            variants.copy_deep(GLib.strdup));
Packit 3ff832
                }
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
            bool has_emoji = false;
Packit 3ff832
            GLib.SList<string> hits =
Packit 3ff832
                    m_category_to_emojis_dict.lookup(category).copy_deep(
Packit 3ff832
                            GLib.strdup);
Packit 3ff832
            foreach (string hit_emoji in hits) {
Packit 3ff832
                if (hit_emoji == emoji) {
Packit 3ff832
                    has_emoji = true;
Packit 3ff832
                    break;
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            if (!has_emoji) {
Packit 3ff832
                hits.append(emoji);
Packit 3ff832
                m_category_to_emojis_dict.replace(category,
Packit 3ff832
                                                  hits.copy_deep(GLib.strdup));
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void make_unicode_block_dict() {
Packit 3ff832
        m_unicode_block_list = IBus.UnicodeBlock.load(
Packit 3ff832
                    Config.PKGDATADIR + "/dicts/unicode-blocks.dict");
Packit 3ff832
        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
Packit 3ff832
            unowned string name = block.get_name();
Packit 3ff832
            if (m_emoji_max_seq_len < name.length)
Packit 3ff832
                m_emoji_max_seq_len = name.length;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void make_unicode_name_dict(Object source_object) {
Packit 3ff832
        IBus.UnicodeData.load_async(
Packit 3ff832
                    Config.PKGDATADIR + "/dicts/unicode-names.dict",
Packit 3ff832
                    source_object,
Packit 3ff832
                    null,
Packit 3ff832
        (IBus.UnicodeDataLoadAsyncFinish)make_unicode_name_dict_finish);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
    private static void
Packit 3ff832
    make_unicode_name_dict_finish(GLib.SList<IBus.UnicodeData> unicode_list) {
Packit 3ff832
        if (unicode_list == null)
Packit 3ff832
            return;
Packit 3ff832
        foreach (IBus.UnicodeData data in unicode_list) {
Packit 3ff832
            update_unicode_to_data_dict(data);
Packit 3ff832
            update_name_to_unicodes_dict(data);
Packit 3ff832
        }
Packit 3ff832
        GLib.List<unowned string> names =
Packit 3ff832
                m_name_to_unicodes_dict.get_keys();
Packit 3ff832
        foreach (unowned string name in names) {
Packit 3ff832
            if (m_emoji_max_seq_len < name.length)
Packit 3ff832
                m_emoji_max_seq_len = name.length;
Packit 3ff832
        }
Packit 3ff832
        m_loaded_unicode = true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void update_unicode_to_data_dict(IBus.UnicodeData data) {
Packit 3ff832
        unichar code = data.get_code();
Packit 3ff832
        m_unicode_to_data_dict.replace(code, data);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static void update_name_to_unicodes_dict(IBus.UnicodeData data) {
Packit 3ff832
        unichar code = data.get_code();
Packit 3ff832
        string[] names = {data.get_name().down(), data.get_alias().down()};
Packit 3ff832
        foreach (unowned string name in names) {
Packit 3ff832
            if (name == "")
Packit 3ff832
                continue;
Packit 3ff832
            bool has_code = false;
Packit 3ff832
            GLib.SList<unichar> hits =
Packit 3ff832
                    m_name_to_unicodes_dict.lookup(name).copy();
Packit 3ff832
            foreach (unichar hit_code in hits) {
Packit 3ff832
                if (hit_code == code) {
Packit 3ff832
                    has_code = true;
Packit 3ff832
                    break;
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            if (!has_code) {
Packit 3ff832
                hits.append(code);
Packit 3ff832
                m_name_to_unicodes_dict.replace(name, hits.copy());
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void set_css_data() {
Packit 3ff832
        Gdk.Display display = Gdk.Display.get_default();
Packit 3ff832
        Gdk.Screen screen = (display != null) ?
Packit 3ff832
                display.get_default_screen() : null;
Packit 3ff832
Packit 3ff832
        if (screen == null) {
Packit 3ff832
            warning("Could not open display.");
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        // Set en locale because de_DE's decimal_point is ',' instead of '.'
Packit 3ff832
        string? backup_locale =
Packit 3ff832
            Intl.setlocale(LocaleCategory.NUMERIC, null).dup();
Packit 3ff832
        if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) {
Packit 3ff832
          if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) {
Packit 3ff832
              if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) {
Packit 3ff832
                  warning("You don't install either en_US.UTF-8 or C.UTF-8 " +
Packit 3ff832
                          "or C locale");
Packit 3ff832
              }
Packit 3ff832
          }
Packit 3ff832
        }
Packit 3ff832
        if (m_rgba == null)
Packit 3ff832
            m_rgba = new ThemedRGBA(this);
Packit 3ff832
        uint bg_red = (uint)(m_rgba.normal_bg.red * 255);
Packit 3ff832
        uint bg_green = (uint)(m_rgba.normal_bg.green * 255);
Packit 3ff832
        uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255);
Packit 3ff832
        double bg_alpha = m_rgba.normal_bg.alpha;
Packit 3ff832
        string data =
Packit 3ff832
                "#IBusEmojierWhiteLabel { background-color: " +
Packit 3ff832
                        "rgba(%u, %u, %u, %lf); ".printf(
Packit 3ff832
                        bg_red, bg_green, bg_blue, bg_alpha) +
Packit 3ff832
                "font-family: %s; font-size: %dpt; ".printf(
Packit 3ff832
                        m_emoji_font_family, m_emoji_font_size) +
Packit 3ff832
                "border-width: 4px; border-radius: 3px; } ";
Packit 3ff832
Packit 3ff832
        uint fg_red = (uint)(m_rgba.selected_fg.red * 255);
Packit 3ff832
        uint fg_green = (uint)(m_rgba.selected_fg.green * 255);
Packit 3ff832
        uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255);
Packit 3ff832
        double fg_alpha = m_rgba.selected_fg.alpha;
Packit 3ff832
        bg_red = (uint)(m_rgba.selected_bg.red * 255);
Packit 3ff832
        bg_green = (uint)(m_rgba.selected_bg.green * 255);
Packit 3ff832
        bg_blue = (uint)(m_rgba.selected_bg.blue * 255);
Packit 3ff832
        bg_alpha = m_rgba.selected_bg.alpha;
Packit 3ff832
        data += "#IBusEmojierSelectedLabel { color: " +
Packit 3ff832
                        "rgba(%u, %u, %u, %lf); ".printf(
Packit 3ff832
                        fg_red, fg_green, fg_blue, fg_alpha) +
Packit 3ff832
                "font-family: %s; font-size: %dpt; ".printf(
Packit 3ff832
                        m_emoji_font_family, m_emoji_font_size) +
Packit 3ff832
                "background-color: " +
Packit 3ff832
                        "rgba(%u, %u, %u, %lf); ".printf(
Packit 3ff832
                        bg_red, bg_green, bg_blue, bg_alpha) +
Packit 3ff832
                "border-width: 4px; border-radius: 3px; }";
Packit 3ff832
        data += "#IBusEmojierGoldLabel { color: " +
Packit 3ff832
                        "rgba(%u, %u, %u, %lf); ".printf(
Packit 3ff832
                        fg_red, fg_green, fg_blue, fg_alpha) +
Packit 3ff832
                "font-family: %s; font-size: %dpt; ".printf(
Packit 3ff832
                        m_emoji_font_family, m_emoji_font_size) +
Packit 3ff832
                "background-color: #b09c5f; " +
Packit 3ff832
                "border-width: 4px; border-radius: 3px; }";
Packit 3ff832
Packit 3ff832
        Gtk.CssProvider css_provider = new Gtk.CssProvider();
Packit 3ff832
        try {
Packit 3ff832
            css_provider.load_from_data(data, -1);
Packit 3ff832
        } catch (GLib.Error e) {
Packit 3ff832
            warning("Failed css_provider_from_data: %s", e.message);
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        if (backup_locale != null)
Packit 3ff832
            Intl.setlocale(LocaleCategory.NUMERIC, backup_locale);
Packit 3ff832
        else
Packit 3ff832
            Intl.setlocale(LocaleCategory.NUMERIC, "");
Packit 3ff832
Packit 3ff832
        Gtk.StyleContext.add_provider_for_screen(
Packit 3ff832
                screen,
Packit 3ff832
                css_provider,
Packit 3ff832
                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void set_fixed_size() {
Packit 3ff832
        resize(20, 1);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void remove_all_children() {
Packit 3ff832
        if (m_list_box != null) {
Packit 3ff832
            foreach (Gtk.Widget w in m_list_box.get_children()) {
Packit 3ff832
                w.destroy();
Packit 3ff832
            }
Packit 3ff832
            m_list_box = null;
Packit 3ff832
        }
Packit 3ff832
        foreach (Gtk.Widget w in m_vbox.get_children()) {
Packit 3ff832
            if (w.name == "IBusEmojierEntry" ||
Packit 3ff832
                w.name == "IBusEmojierTitleLabelBox") {
Packit 3ff832
                continue;
Packit 3ff832
            }
Packit 3ff832
            w.destroy();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void clamp_page() {
Packit 3ff832
        Gtk.ListBoxRow row;
Packit 3ff832
        if (m_category_active_index >= 0) {
Packit 3ff832
            row = m_list_box.get_row_at_index(m_category_active_index);
Packit 3ff832
            m_list_box.select_row(row);
Packit 3ff832
        } else {
Packit 3ff832
            row = m_list_box.get_row_at_index(0);
Packit 3ff832
        }
Packit 3ff832
        Gtk.Allocation alloc = { 0, 0, 0, 0 };
Packit 3ff832
        row.get_allocation(out alloc);
Packit 3ff832
        var adjustment = m_scrolled_window.get_vadjustment();
Packit 3ff832
        adjustment.clamp_page(alloc.y, alloc.y + alloc.height);
Packit 3ff832
        return_val_if_fail(m_category_active_index >= 0, false);
Packit 3ff832
        m_lookup_table.set_cursor_pos((uint)m_category_active_index);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_category_list() {
Packit 3ff832
        // Do not call remove_all_children() to work adjustment.clamp_page()
Packit 3ff832
        // with PageUp/Down.
Packit 3ff832
        // After show_candidate_panel() is called, m_category_active_index
Packit 3ff832
        // is saved for Escape key but m_list_box is null by
Packit 3ff832
        // remove_all_children().
Packit 3ff832
        if (m_category_active_index >= 0 && m_list_box != null) {
Packit 3ff832
            var row = m_list_box.get_row_at_index(m_category_active_index);
Packit 3ff832
            m_list_box.select_row(row);
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        if (m_category_active_index < 0)
Packit 3ff832
            m_category_active_index = 0;
Packit 3ff832
        remove_all_children();
Packit 3ff832
        m_scrolled_window = new EScrolledWindow();
Packit 3ff832
        set_fixed_size();
Packit 3ff832
Packit 3ff832
        m_vbox.add(m_scrolled_window);
Packit 3ff832
        Gtk.Viewport viewport = new Gtk.Viewport(null, null);
Packit 3ff832
        m_scrolled_window.add(viewport);
Packit 3ff832
Packit 3ff832
        m_list_box = new EListBox();
Packit 3ff832
        viewport.add(m_list_box);
Packit 3ff832
        Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
Packit 3ff832
        m_list_box.set_adjustment(adjustment);
Packit 3ff832
        m_list_box.row_activated.connect((box, gtkrow) => {
Packit 3ff832
            m_category_active_index = gtkrow.get_index();
Packit 3ff832
            EBoxRow row = gtkrow as EBoxRow;
Packit 3ff832
            show_emoji_for_category(row.text);
Packit 3ff832
            show_all();
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
        uint ncandidates = m_lookup_table.get_number_of_candidates();
Packit 3ff832
        for (uint i = 0; i < ncandidates; i++) {
Packit 3ff832
            string category = m_lookup_table.get_candidate(i).text;
Packit 3ff832
            EBoxRow row = new EBoxRow(category);
Packit 3ff832
            EPaddedLabelBox widget =
Packit 3ff832
                    new EPaddedLabelBox(_(category), Gtk.Align.CENTER);
Packit 3ff832
            row.add(widget);
Packit 3ff832
            m_list_box.add(row);
Packit 3ff832
            if (i == m_category_active_index)
Packit 3ff832
                m_list_box.select_row(row);
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        m_scrolled_window.show_all();
Packit 3ff832
        if (m_category_active_index == -1)
Packit 3ff832
            m_list_box.unselect_all();
Packit 3ff832
        m_list_box.invalidate_filter();
Packit 3ff832
        m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_emoji_for_category(string category) {
Packit 3ff832
        if (category == EMOJI_CATEGORY_FAVORITES) {
Packit 3ff832
            m_lookup_table.clear();
Packit 3ff832
            m_candidate_panel_mode = true;
Packit 3ff832
            foreach (unowned string favorate in m_favorites) {
Packit 3ff832
                IBus.Text text = new IBus.Text.from_string(favorate);
Packit 3ff832
                m_lookup_table.append_candidate(text);
Packit 3ff832
            }
Packit 3ff832
            m_backward = category;
Packit 3ff832
        } else if (category == EMOJI_CATEGORY_UNICODE) {
Packit 3ff832
            m_category_active_index = -1;
Packit 3ff832
            m_show_unicode = true;
Packit 3ff832
            update_unicode_blocks();
Packit 3ff832
            return;
Packit 3ff832
        } else {
Packit 3ff832
            unowned GLib.SList<unowned string> emojis =
Packit 3ff832
                    m_category_to_emojis_dict.lookup(category);
Packit 3ff832
            m_lookup_table.clear();
Packit 3ff832
            m_candidate_panel_mode = true;
Packit 3ff832
            foreach (unowned string emoji in emojis) {
Packit 3ff832
                IBus.Text text = new IBus.Text.from_string(emoji);
Packit 3ff832
                m_lookup_table.append_candidate(text);
Packit 3ff832
            }
Packit 3ff832
            m_backward = category;
Packit 3ff832
        }
Packit 3ff832
        m_annotation = m_lookup_table.get_candidate(0).text;
Packit 3ff832
        // Restore the cursor position before the special table of
Packit 3ff832
        // emoji variants is shown.
Packit 3ff832
        if (m_backward_index >= 0) {
Packit 3ff832
            m_lookup_table.set_cursor_pos((uint)m_backward_index);
Packit 3ff832
            m_backward_index = -1;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_emoji_variants(GLib.SList<string>? emojis) {
Packit 3ff832
        m_backward_index = (int)m_lookup_table.get_cursor_pos();
Packit 3ff832
        m_lookup_table.clear();
Packit 3ff832
        foreach (unowned string emoji in emojis) {
Packit 3ff832
            IBus.Text text = new IBus.Text.from_string(emoji);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_unicode_blocks() {
Packit 3ff832
        // Do not call remove_all_children() to work adjustment.clamp_page()
Packit 3ff832
        // with PageUp/Down.
Packit 3ff832
        // After show_candidate_panel() is called, m_category_active_index
Packit 3ff832
        // is saved for Escape key but m_list_box is null by
Packit 3ff832
        // remove_all_children().
Packit 3ff832
        if (m_category_active_index >= 0 && m_list_box != null) {
Packit 3ff832
            var row = m_list_box.get_row_at_index(m_category_active_index);
Packit 3ff832
            m_list_box.select_row(row);
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        if (m_category_active_index < 0)
Packit 3ff832
            m_category_active_index = 0;
Packit 3ff832
        m_show_unicode = true;
Packit 3ff832
        if (m_default_window_width == 0 && m_default_window_height == 0)
Packit 3ff832
            get_size(out m_default_window_width, out m_default_window_height);
Packit 3ff832
        remove_all_children();
Packit 3ff832
        set_fixed_size();
Packit 3ff832
Packit 3ff832
        EPaddedLabelBox label =
Packit 3ff832
                new EPaddedLabelBox(_("Bring back emoji choice"),
Packit 3ff832
                                    Gtk.Align.CENTER,
Packit 3ff832
                                    TravelDirection.BACKWARD);
Packit 3ff832
        Gtk.Button button = new Gtk.Button();
Packit 3ff832
        button.add(label);
Packit 3ff832
        m_vbox.add(button);
Packit 3ff832
        button.show_all();
Packit 3ff832
        button.button_press_event.connect((w, e) => {
Packit 3ff832
            m_category_active_index = -1;
Packit 3ff832
            m_show_unicode = false;
Packit 3ff832
            hide_candidate_panel();
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        });
Packit 3ff832
        m_scrolled_window = new EScrolledWindow();
Packit 3ff832
        m_vbox.add(m_scrolled_window);
Packit 3ff832
        Gtk.Viewport viewport = new Gtk.Viewport(null, null);
Packit 3ff832
        m_scrolled_window.add(viewport);
Packit 3ff832
Packit 3ff832
        m_list_box = new EListBox();
Packit 3ff832
        viewport.add(m_list_box);
Packit 3ff832
        Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment();
Packit 3ff832
        m_list_box.set_adjustment(adjustment);
Packit 3ff832
        m_list_box.row_activated.connect((box, gtkrow) => {
Packit 3ff832
            m_category_active_index = gtkrow.get_index();
Packit 3ff832
            EBoxRow row = gtkrow as EBoxRow;
Packit 3ff832
            show_unicode_for_block(row.text);
Packit 3ff832
            show_candidate_panel();
Packit 3ff832
        });
Packit 3ff832
Packit 3ff832
        uint n = 0;
Packit 3ff832
        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
Packit 3ff832
            string name = block.get_name();
Packit 3ff832
            string caption = "U+%08X".printf(block.get_start());
Packit 3ff832
            EBoxRow row = new EBoxRow(name);
Packit 3ff832
            EPaddedLabelBox widget =
Packit 3ff832
                    new EPaddedLabelBox(_(name),
Packit 3ff832
                                        Gtk.Align.CENTER,
Packit 3ff832
                                        TravelDirection.NONE,
Packit 3ff832
                                        caption);
Packit 3ff832
            row.add(widget);
Packit 3ff832
            m_list_box.add(row);
Packit 3ff832
            if (n++ == m_category_active_index) {
Packit 3ff832
                m_list_box.select_row(row);
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        set_size_request(-1, m_default_window_height + 100);
Packit 3ff832
        m_scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Packit 3ff832
                                     Gtk.PolicyType.AUTOMATIC);
Packit 3ff832
        m_scrolled_window.show_all();
Packit 3ff832
        if (m_category_active_index == -1)
Packit 3ff832
            m_list_box.unselect_all();
Packit 3ff832
        m_list_box.invalidate_filter();
Packit 3ff832
        m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE);
Packit 3ff832
        Gtk.ListBoxRow row = m_list_box.get_row_at_index((int)n - 1);
Packit 3ff832
Packit 3ff832
        // If clamp_page() would be called without the allocation signal,
Packit 3ff832
        // the jumping page could be failed when returns from 
Packit 3ff832
        // show_unicode_for_block() with Escape key.
Packit 3ff832
        row.size_allocate.connect((w, a) => {
Packit 3ff832
            clamp_page();
Packit 3ff832
        });
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_unicode_for_block(string block_name) {
Packit 3ff832
        unichar start = 0;
Packit 3ff832
        unichar end = 0;
Packit 3ff832
        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
Packit 3ff832
            string name = block.get_name();
Packit 3ff832
            if (block_name == name) {
Packit 3ff832
                start = block.get_start();
Packit 3ff832
                end = block.get_end();
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        m_lookup_table.clear();
Packit 3ff832
        m_candidate_panel_mode = true;
Packit 3ff832
        for (unichar ch = start; ch < end; ch++) {
Packit 3ff832
            unowned IBus.UnicodeData? data =
Packit 3ff832
                    m_unicode_to_data_dict.lookup(ch);
Packit 3ff832
            if (data == null)
Packit 3ff832
                continue;
Packit 3ff832
            IBus.Text text = new IBus.Text.from_unichar(ch);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        m_backward = block_name;
Packit 3ff832
        if (m_lookup_table.get_number_of_candidates() > 0)
Packit 3ff832
            m_annotation = m_lookup_table.get_candidate(0).text;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_arrow_buttons() {
Packit 3ff832
        Gtk.Button next_button = new Gtk.Button();
Packit 3ff832
        next_button.clicked.connect(() => {
Packit 3ff832
            m_lookup_table.page_down();
Packit 3ff832
            show_candidate_panel();
Packit 3ff832
        });
Packit 3ff832
        next_button.set_image(new Gtk.Image.from_icon_name(
Packit 3ff832
                              "go-down",
Packit 3ff832
                              Gtk.IconSize.MENU));
Packit 3ff832
        next_button.set_relief(Gtk.ReliefStyle.NONE);
Packit 3ff832
        next_button.set_tooltip_text(_("Page Down"));
Packit 3ff832
Packit 3ff832
        Gtk.Button prev_button = new Gtk.Button();
Packit 3ff832
        prev_button.clicked.connect(() => {
Packit 3ff832
            m_lookup_table.page_up();
Packit 3ff832
            show_candidate_panel();
Packit 3ff832
        });
Packit 3ff832
        prev_button.set_image(new Gtk.Image.from_icon_name(
Packit 3ff832
                              "go-up",
Packit 3ff832
                              Gtk.IconSize.MENU));
Packit 3ff832
        prev_button.set_relief(Gtk.ReliefStyle.NONE);
Packit 3ff832
        prev_button.set_tooltip_text(_("Page Up"));
Packit 3ff832
Packit 3ff832
        var menu = new GLib.Menu();
Packit 3ff832
        menu.append(_("Show emoji variants"), "win.variant");
Packit 3ff832
        menu.append(_("Close"), "win.close");
Packit 3ff832
        var menu_button = new Gtk.MenuButton();
Packit 3ff832
        menu_button.set_direction(Gtk.ArrowType.NONE);
Packit 3ff832
        menu_button.set_valign(Gtk.Align.CENTER);
Packit 3ff832
        menu_button.set_menu_model(menu);
Packit 3ff832
        menu_button.set_relief(Gtk.ReliefStyle.NONE);
Packit 3ff832
        menu_button.set_tooltip_text(_("Menu"));
Packit 3ff832
Packit 3ff832
        IBus.Text text = this.get_title_text();
Packit 3ff832
        Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text);
Packit 3ff832
        Gtk.Label title_label = new Gtk.Label(text.get_text());
Packit 3ff832
        title_label.set_attributes(attrs);
Packit 3ff832
Packit 3ff832
        Gtk.Button? warning_button = null;
Packit 3ff832
        if (m_warning_message != "") { 
Packit 3ff832
            warning_button = new Gtk.Button();
Packit 3ff832
            warning_button.set_tooltip_text(
Packit 3ff832
                    _("Click to view a warning message"));
Packit 3ff832
            warning_button.set_image(new Gtk.Image.from_icon_name(
Packit 3ff832
                                  "dialog-warning",
Packit 3ff832
                                  Gtk.IconSize.MENU));
Packit 3ff832
            warning_button.set_relief(Gtk.ReliefStyle.NONE);
Packit 3ff832
            warning_button.clicked.connect(() => {
Packit 3ff832
                Gtk.Label warning_label = new Gtk.Label(m_warning_message);
Packit 3ff832
                warning_label.set_line_wrap(true);
Packit 3ff832
                warning_label.set_max_width_chars(40);
Packit 3ff832
                Gtk.Popover popover = new Gtk.Popover(warning_button);
Packit 3ff832
                popover.add(warning_label);
Packit 3ff832
                popover.show_all();
Packit 3ff832
                popover.popup();
Packit 3ff832
            });
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        Gtk.Box buttons_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
Packit 3ff832
        Gtk.Label state_label = new Gtk.Label(null);
Packit 3ff832
        state_label.set_size_request(10, -1);
Packit 3ff832
        state_label.set_halign(Gtk.Align.CENTER);
Packit 3ff832
        state_label.set_valign(Gtk.Align.CENTER);
Packit 3ff832
        buttons_hbox.pack_start(state_label, false, true, 0);
Packit 3ff832
        buttons_hbox.pack_start(prev_button, false, false, 0);
Packit 3ff832
        buttons_hbox.pack_start(next_button, false, false, 0);
Packit 3ff832
        buttons_hbox.pack_start(title_label, false, false, 0);
Packit 3ff832
        if (warning_button != null)
Packit 3ff832
            buttons_hbox.pack_start(warning_button, false, false, 0);
Packit 3ff832
        buttons_hbox.pack_end(menu_button, false, false, 0);
Packit 3ff832
        m_vbox.pack_start(buttons_hbox, false, false, 0);
Packit 3ff832
        buttons_hbox.show_all();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_unicode_progress_bar() {
Packit 3ff832
        m_unicode_progress_bar = new Gtk.ProgressBar();
Packit 3ff832
        m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE);
Packit 3ff832
        m_unicode_progress_bar.set_halign(Gtk.Align.CENTER);
Packit 3ff832
        m_unicode_progress_bar.set_valign(Gtk.Align.CENTER);
Packit 3ff832
        m_vbox.add(m_unicode_progress_bar);
Packit 3ff832
        m_unicode_progress_bar.show();
Packit 3ff832
        var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
Packit 3ff832
        hbox.set_halign(Gtk.Align.CENTER);
Packit 3ff832
        hbox.set_valign(Gtk.Align.CENTER);
Packit 3ff832
        m_vbox.add(hbox);
Packit 3ff832
        var label = new Gtk.Label(_("Loading a Unicode dictionary:"));
Packit 3ff832
        hbox.pack_start(label, false, true, 0);
Packit 3ff832
        m_unicode_percent_label = new Gtk.Label("");
Packit 3ff832
        hbox.pack_start(m_unicode_percent_label, false, true, 0);
Packit 3ff832
        hbox.show_all();
Packit 3ff832
Packit 3ff832
        m_unicode_progress_object.deserialize_unicode.connect((i, n) => {
Packit 3ff832
            m_unicode_percent = (double)i / n;
Packit 3ff832
        });
Packit 3ff832
        if (m_unicode_progress_id > 0) {
Packit 3ff832
            GLib.Source.remove(m_unicode_progress_id);
Packit 3ff832
        }
Packit 3ff832
        m_unicode_progress_id = GLib.Timeout.add(100, () => {
Packit 3ff832
            m_unicode_progress_id = 0;
Packit 3ff832
            m_unicode_progress_bar.set_fraction(m_unicode_percent);
Packit 3ff832
            m_unicode_percent_label.set_text(
Packit 3ff832
                    "%.0f%%\n".printf(m_unicode_percent * 100));
Packit 3ff832
            m_unicode_progress_bar.show();
Packit 3ff832
            m_unicode_percent_label.show();
Packit 3ff832
            if (m_loaded_unicode) {
Packit 3ff832
                show_candidate_panel();
Packit 3ff832
            }
Packit 3ff832
            return !m_loaded_unicode;
Packit 3ff832
        });
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static string? check_unicode_point(string annotation) {
Packit 3ff832
        string unicode_point = null;
Packit 3ff832
        // Add "0x" because uint64.ascii_strtoull() is not accessible
Packit 3ff832
        // and need to use uint64.parse()
Packit 3ff832
        var buff = new GLib.StringBuilder("0x");
Packit 3ff832
        var retval = new GLib.StringBuilder();
Packit 3ff832
        for (int i = 0; i < annotation.char_count(); i++) {
Packit 3ff832
            unichar ch = annotation.get_char(i);
Packit 3ff832
            if (ch == 0)
Packit 3ff832
                return null;
Packit 3ff832
            if (ch.isspace()) {
Packit 3ff832
                unichar code = (unichar)uint64.parse(buff.str);
Packit 3ff832
                buff.assign("0x");
Packit 3ff832
                if (!code.validate())
Packit 3ff832
                    return null;
Packit 3ff832
                retval.append(code.to_string());
Packit 3ff832
                continue;
Packit 3ff832
            }
Packit 3ff832
            if (!ch.isxdigit())
Packit 3ff832
                return null;
Packit 3ff832
            buff.append_unichar(ch);
Packit 3ff832
        }
Packit 3ff832
        unichar code = (unichar)uint64.parse(buff.str);
Packit 3ff832
        if (!code.validate())
Packit 3ff832
            return null;
Packit 3ff832
        retval.append(code.to_string());
Packit 3ff832
        unicode_point = retval.str;
Packit 3ff832
        if (unicode_point == null)
Packit 3ff832
            return null;
Packit 3ff832
        return unicode_point;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static GLib.SList<string>?
Packit 3ff832
    lookup_emojis_from_annotation(string annotation) {
Packit 3ff832
        GLib.SList<string>? total_emojis = null;
Packit 3ff832
        unowned GLib.SList<string>? sub_emojis = null;
Packit 3ff832
        unowned GLib.SList<unichar>? sub_exact_unicodes = null;
Packit 3ff832
        unowned GLib.SList<unichar>? sub_unicodes = null;
Packit 3ff832
        int length = annotation.length;
Packit 3ff832
        if (m_has_partial_match && length >= m_partial_match_length) {
Packit 3ff832
            GLib.SList<string>? sorted_emojis = null;
Packit 3ff832
            foreach (unowned string key in
Packit 3ff832
                     m_annotation_to_emojis_dict.get_keys()) {
Packit 3ff832
                if (key.length < length)
Packit 3ff832
                    continue;
Packit 3ff832
                bool matched = false;
Packit 3ff832
                switch(m_partial_match_condition) {
Packit 3ff832
                case 0:
Packit 3ff832
                    if (key.has_prefix(annotation))
Packit 3ff832
                        matched = true;
Packit 3ff832
                    break;
Packit 3ff832
                case 1:
Packit 3ff832
                    if (key.has_suffix(annotation))
Packit 3ff832
                        matched = true;
Packit 3ff832
                    break;
Packit 3ff832
                case 2:
Packit 3ff832
                    if (key.index_of(annotation) >= 0)
Packit 3ff832
                        matched = true;
Packit 3ff832
                    break;
Packit 3ff832
                default:
Packit 3ff832
                    break;
Packit 3ff832
                }
Packit 3ff832
                if (!matched)
Packit 3ff832
                    continue;
Packit 3ff832
                sub_emojis = m_annotation_to_emojis_dict.lookup(key);
Packit 3ff832
                foreach (unowned string emoji in sub_emojis) {
Packit 3ff832
                    if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
Packit 3ff832
                        sorted_emojis.insert_sorted(emoji, GLib.strcmp);
Packit 3ff832
                    }
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            foreach (string emoji in sorted_emojis) {
Packit 3ff832
                if (total_emojis.find_custom(emoji, GLib.strcmp) == null) {
Packit 3ff832
                    total_emojis.append(emoji);
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
        } else {
Packit 3ff832
            sub_emojis = m_annotation_to_emojis_dict.lookup(annotation);
Packit 3ff832
            foreach (unowned string emoji in sub_emojis)
Packit 3ff832
                total_emojis.append(emoji);
Packit 3ff832
        }
Packit 3ff832
        sub_exact_unicodes = m_name_to_unicodes_dict.lookup(annotation);
Packit 3ff832
        foreach (unichar code in sub_exact_unicodes) {
Packit 3ff832
            string ch = code.to_string();
Packit 3ff832
            if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
Packit 3ff832
                total_emojis.append(ch);
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        if (length >= m_partial_match_length) {
Packit 3ff832
            GLib.SList<string>? sorted_unicodes = null;
Packit 3ff832
            foreach (unowned string key in m_name_to_unicodes_dict.get_keys()) {
Packit 3ff832
                bool matched = false;
Packit 3ff832
                if (key.index_of(annotation) >= 0)
Packit 3ff832
                        matched = true;
Packit 3ff832
                if (!matched)
Packit 3ff832
                    continue;
Packit 3ff832
                sub_unicodes = m_name_to_unicodes_dict.lookup(key);
Packit 3ff832
                foreach (unichar code in sub_unicodes) {
Packit 3ff832
                    string ch = code.to_string();
Packit 3ff832
                    if (sorted_unicodes.find_custom(ch, GLib.strcmp) == null) {
Packit 3ff832
                        sorted_unicodes.insert_sorted(ch, GLib.strcmp);
Packit 3ff832
                    }
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            foreach (string ch in sorted_unicodes) {
Packit 3ff832
                if (total_emojis.find_custom(ch, GLib.strcmp) == null) {
Packit 3ff832
                    total_emojis.append(ch);
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        return total_emojis;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void update_candidate_window() {
Packit 3ff832
        string annotation = m_annotation;
Packit 3ff832
        if (annotation.length == 0) {
Packit 3ff832
            m_backward = null;
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        m_lookup_table.clear();
Packit 3ff832
        m_category_active_index = -1;
Packit 3ff832
        if (annotation.length > m_emoji_max_seq_len) {
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        string? unicode_point = check_unicode_point(annotation);
Packit 3ff832
        GLib.SList<string>? total_emojis = null;
Packit 3ff832
        if (annotation.ascii_casecmp("history") == 0) {
Packit 3ff832
            for (int i = 0; i < m_favorites.length; i++) {
Packit 3ff832
                total_emojis.append(m_favorites[i].dup());
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        if (total_emojis == null)
Packit 3ff832
            total_emojis = lookup_emojis_from_annotation(annotation);
Packit 3ff832
        if (total_emojis == null) {
Packit 3ff832
            /* Users can type title strings against lower case.
Packit 3ff832
             * E.g. "Smile" against "smile"
Packit 3ff832
             * But the dictionary has the case sensitive annotations.
Packit 3ff832
             * E.g. ":D" and ":q"
Packit 3ff832
             * So need to call lookup_emojis_from_annotation() twice.
Packit 3ff832
             */
Packit 3ff832
            annotation = annotation.down();
Packit 3ff832
            total_emojis = lookup_emojis_from_annotation(annotation);
Packit 3ff832
        }
Packit 3ff832
        if (total_emojis == null && unicode_point == null) {
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        if (unicode_point != null) {
Packit 3ff832
            IBus.Text text = new IBus.Text.from_string(unicode_point);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        foreach (unowned string emoji in total_emojis) {
Packit 3ff832
            IBus.Text text = new IBus.Text.from_string(emoji);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        m_candidate_panel_is_visible =
Packit 3ff832
            (m_lookup_table.get_number_of_candidates() > 0) ? true : false;
Packit 3ff832
        m_candidate_panel_mode = true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void update_category_list() {
Packit 3ff832
        // Always update m_lookup_table even if the contents are same
Packit 3ff832
        // because m_category_active_index needs to be kept after
Packit 3ff832
        // bring back this API from show_emoji_for_category().
Packit 3ff832
        reset_window_mode();
Packit 3ff832
        m_lookup_table.clear();
Packit 3ff832
        IBus.Text text;
Packit 3ff832
        if (m_favorites.length > 0) {
Packit 3ff832
            text = new IBus.Text.from_string(EMOJI_CATEGORY_FAVORITES);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        GLib.List<unowned string> categories =
Packit 3ff832
                m_category_to_emojis_dict.get_keys();
Packit 3ff832
        // FIXME: How to cast GLib.CompareFunc<string> to strcmp?
Packit 3ff832
        categories.sort((a, b) => {
Packit 3ff832
            if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS)
Packit 3ff832
                return 1;
Packit 3ff832
            else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS)
Packit 3ff832
                return -1;
Packit 3ff832
            return GLib.strcmp(_(a), _(b));
Packit 3ff832
        });
Packit 3ff832
        foreach (unowned string category in categories) {
Packit 3ff832
            // "Others" category includes next unicode chars and fonts do not
Packit 3ff832
            // support the base and varints yet.
Packit 3ff832
            if (category == EMOJI_CATEGORY_OTHERS)
Packit 3ff832
               continue;
Packit 3ff832
            text = new IBus.Text.from_string(category);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        if (m_unicode_block_list.length() > 0) {
Packit 3ff832
            text = new IBus.Text.from_string(EMOJI_CATEGORY_UNICODE);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        // Do not set m_category_active_index to 0 here so that
Packit 3ff832
        // show_category_list() handles it.
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void update_unicode_blocks() {
Packit 3ff832
        // Always update m_lookup_table even if the contents are same
Packit 3ff832
        // because m_category_active_index needs to be kept after
Packit 3ff832
        // bring back this API from show_emoji_for_category().
Packit 3ff832
        reset_window_mode();
Packit 3ff832
        m_lookup_table.clear();
Packit 3ff832
        m_show_unicode = true;
Packit 3ff832
        foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) {
Packit 3ff832
            string name = block.get_name();
Packit 3ff832
            IBus.Text text = new IBus.Text.from_string(name);
Packit 3ff832
            m_lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        // Do not set m_category_active_index to 0 here so that
Packit 3ff832
        // show_unicode_blocks() handles it.
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_code_point_description(string text) {
Packit 3ff832
        EPaddedLabelBox widget_code = new EPaddedLabelBox(
Packit 3ff832
                    _("Code point: %s").printf(utf8_code_point(text)),
Packit 3ff832
                    Gtk.Align.START);
Packit 3ff832
        m_vbox.add(widget_code);
Packit 3ff832
        widget_code.show_all();
Packit 3ff832
        if (m_emoji_to_emoji_variants_dict.lookup(text) != null) {
Packit 3ff832
            EPaddedLabelBox widget_has_variant = new EPaddedLabelBox(
Packit 3ff832
                    _("Has emoji variants"),
Packit 3ff832
                    Gtk.Align.START);
Packit 3ff832
            m_vbox.add(widget_has_variant);
Packit 3ff832
            widget_has_variant.show_all();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_candidate_panel() {
Packit 3ff832
        remove_all_children();
Packit 3ff832
        set_fixed_size();
Packit 3ff832
        if (m_emoji_font_changed) {
Packit 3ff832
            set_css_data();
Packit 3ff832
            m_emoji_font_changed = false;
Packit 3ff832
        }
Packit 3ff832
        uint page_size = m_lookup_table.get_page_size();
Packit 3ff832
        uint ncandidates = m_lookup_table.get_number_of_candidates();
Packit 3ff832
        uint cursor = m_lookup_table.get_cursor_pos();
Packit 3ff832
        uint page_start_pos = cursor / page_size * page_size;
Packit 3ff832
        uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates);
Packit 3ff832
        Gtk.Button? backward_button = null;
Packit 3ff832
        if (m_backward != null) {
Packit 3ff832
            string backward_desc = _(m_backward);
Packit 3ff832
            EPaddedLabelBox label =
Packit 3ff832
                    new EPaddedLabelBox(backward_desc,
Packit 3ff832
                                        Gtk.Align.CENTER,
Packit 3ff832
                                        TravelDirection.BACKWARD);
Packit 3ff832
            backward_button = new Gtk.Button();
Packit 3ff832
            backward_button.add(label);
Packit 3ff832
            backward_button.button_press_event.connect((w, e) => {
Packit 3ff832
                // Bring back to emoji candidate panel in case
Packit 3ff832
                // m_show_emoji_variant is enabled and shows variants.
Packit 3ff832
                if (m_backward_index >= 0 && m_backward != null) {
Packit 3ff832
                    show_emoji_for_category(m_backward);
Packit 3ff832
                    show_candidate_panel();
Packit 3ff832
                } else {
Packit 3ff832
                    hide_candidate_panel();
Packit 3ff832
                    show_all();
Packit 3ff832
                }
Packit 3ff832
                return true;
Packit 3ff832
            });
Packit 3ff832
        }
Packit 3ff832
        EGrid grid = new EGrid();
Packit 3ff832
        int n = 0;
Packit 3ff832
        for (uint i = page_start_pos; i < page_end_pos; i++) {
Packit 3ff832
            string text = m_lookup_table.get_candidate(i).text;
Packit 3ff832
            bool has_variant =
Packit 3ff832
                    (m_emoji_to_emoji_variants_dict.lookup(text) != null);
Packit 3ff832
            Gtk.Label label;
Packit 3ff832
            // If 'i' is the cursor position, use the selected color.
Packit 3ff832
            // If the emoji has emoji variants, use the gold color.
Packit 3ff832
            // Otherwise the white color.
Packit 3ff832
            if (i == cursor) {
Packit 3ff832
                label = new ESelectedLabel(text) as Gtk.Label;
Packit 3ff832
            } else if (m_show_emoji_variant && has_variant &&
Packit 3ff832
                       m_backward_index < 0) {
Packit 3ff832
                label = new EGoldLabel(text) as Gtk.Label;
Packit 3ff832
            } else {
Packit 3ff832
                label = new EWhiteLabel(text) as Gtk.Label;
Packit 3ff832
            }
Packit 3ff832
            if (text.char_count() > 2) {
Packit 3ff832
                string font_family = m_emoji_font_family;
Packit 3ff832
                int font_size = m_emoji_font_size - 2;
Packit 3ff832
                string emoji_font = "%s %d".printf(font_family, font_size);
Packit 3ff832
                string markup = "%s".
Packit 3ff832
                        printf(emoji_font, utf8_entity(text));
Packit 3ff832
                label.set_markup(markup);
Packit 3ff832
            }
Packit 3ff832
            label.set_halign(Gtk.Align.FILL);
Packit 3ff832
            label.set_valign(Gtk.Align.FILL);
Packit 3ff832
            Gtk.EventBox candidate_ebox = new Gtk.EventBox();
Packit 3ff832
            candidate_ebox.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
Packit 3ff832
            candidate_ebox.add(label);
Packit 3ff832
            // Make a copy of i to workaround a bug in vala.
Packit 3ff832
            // https://bugzilla.gnome.org/show_bug.cgi?id=628336
Packit 3ff832
            uint index = i;
Packit 3ff832
            candidate_ebox.button_press_event.connect((w, e) => {
Packit 3ff832
                candidate_clicked(index, e.button, e.state);
Packit 3ff832
                return true;
Packit 3ff832
            });
Packit 3ff832
            candidate_ebox.motion_notify_event.connect((e) => {
Packit 3ff832
                // m_enter_notify_enable is added because
Packit 3ff832
                // enter_notify_event conflicts with keyboard operations.
Packit 3ff832
                if (!m_enter_notify_enable)
Packit 3ff832
                    return false;
Packit 3ff832
                if (m_lookup_table.get_cursor_pos() == index)
Packit 3ff832
                    return false;
Packit 3ff832
#if VALA_0_24
Packit 3ff832
                Gdk.EventMotion pe = e;
Packit 3ff832
#else
Packit 3ff832
                Gdk.EventMotion *pe = &e;
Packit 3ff832
#endif
Packit 3ff832
                if (m_mouse_x == pe.x_root && m_mouse_y == pe.y_root)
Packit 3ff832
                    return false;
Packit 3ff832
                m_mouse_x = pe.x_root;
Packit 3ff832
                m_mouse_y = pe.y_root;
Packit 3ff832
Packit 3ff832
                m_lookup_table.set_cursor_pos(index);
Packit 3ff832
                if (m_entry_notify_show_id > 0 &&
Packit 3ff832
                    GLib.MainContext.default().find_source_by_id(
Packit 3ff832
                            m_entry_notify_show_id) != null) {
Packit 3ff832
                        GLib.Source.remove(m_entry_notify_show_id);
Packit 3ff832
                }
Packit 3ff832
                // If timeout is not added, memory leak happens and
Packit 3ff832
                // button_press_event signal does not work above.
Packit 3ff832
                m_entry_notify_show_id = GLib.Timeout.add(100, () => {
Packit 3ff832
                        show_candidate_panel();
Packit 3ff832
                        return false;
Packit 3ff832
                });
Packit 3ff832
                return false;
Packit 3ff832
            });
Packit 3ff832
            grid.attach(candidate_ebox,
Packit 3ff832
                        n % (int)EMOJI_GRID_PAGE, n / (int)EMOJI_GRID_PAGE,
Packit 3ff832
                        1, 1);
Packit 3ff832
            n++;
Packit 3ff832
Packit 3ff832
            m_candidates += label;
Packit 3ff832
        }
Packit 3ff832
        m_candidate_panel_is_visible = true;
Packit 3ff832
        if (!m_is_up_side_down) {
Packit 3ff832
            show_arrow_buttons();
Packit 3ff832
            if (backward_button != null) {
Packit 3ff832
                m_vbox.add(backward_button);
Packit 3ff832
                backward_button.show_all();
Packit 3ff832
            }
Packit 3ff832
            if (n > 0) {
Packit 3ff832
                m_vbox.add(grid);
Packit 3ff832
                grid.show_all();
Packit 3ff832
                show_description();
Packit 3ff832
            }
Packit 3ff832
            if (!m_loaded_unicode)
Packit 3ff832
                show_unicode_progress_bar();
Packit 3ff832
        } else {
Packit 3ff832
            if (!m_loaded_unicode)
Packit 3ff832
                show_unicode_progress_bar();
Packit 3ff832
            if (n > 0) {
Packit 3ff832
                show_description();
Packit 3ff832
                m_vbox.add(grid);
Packit 3ff832
                grid.show_all();
Packit 3ff832
            }
Packit 3ff832
            if (backward_button != null) {
Packit 3ff832
                m_vbox.add(backward_button);
Packit 3ff832
                backward_button.show_all();
Packit 3ff832
            }
Packit 3ff832
            show_arrow_buttons();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_description() {
Packit 3ff832
        uint cursor = m_lookup_table.get_cursor_pos();
Packit 3ff832
        string text = m_lookup_table.get_candidate(cursor).text;
Packit 3ff832
        unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text);
Packit 3ff832
        if (data != null) {
Packit 3ff832
            show_emoji_description(data, text);
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        if (text.char_count() <= 1) {
Packit 3ff832
            unichar code = text.get_char();
Packit 3ff832
            unowned IBus.UnicodeData? udata =
Packit 3ff832
                    m_unicode_to_data_dict.lookup(code);
Packit 3ff832
            if (udata != null) {
Packit 3ff832
                show_unicode_description(udata, text);
Packit 3ff832
                return;
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        EPaddedLabelBox widget = new EPaddedLabelBox(
Packit 3ff832
                _("Description: %s").printf(_("None")),
Packit 3ff832
                Gtk.Align.START);
Packit 3ff832
        m_vbox.add(widget);
Packit 3ff832
        widget.show_all();
Packit 3ff832
        show_code_point_description(text);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void show_emoji_description(IBus.EmojiData data,
Packit 3ff832
                                        string         text) {
Packit 3ff832
        unowned string description = data.get_description();
Packit 3ff832
        {
Packit 3ff832
            EPaddedLabelBox widget = new EPaddedLabelBox(
Packit 3ff832
                    _("Description: %s").printf(description),
Packit 3ff832
                    Gtk.Align.START);
Packit 3ff832
            m_vbox.add(widget);
Packit 3ff832
            widget.show_all();
Packit 3ff832
        }
Packit 3ff832
        unowned GLib.SList<unowned string>? annotations =
Packit 3ff832
                data.get_annotations();
Packit 3ff832
        var buff = new GLib.StringBuilder();
Packit 3ff832
        int i = 0;
Packit 3ff832
        foreach (unowned string annotation in annotations) {
Packit 3ff832
            if (i++ == 0)
Packit 3ff832
                buff.append_printf(_("Annotations: %s"), annotation);
Packit 3ff832
            else
Packit 3ff832
                buff.append_printf(" | %s", annotation);
Packit 3ff832
            if (buff.str.char_count() > 30) {
Packit 3ff832
                EPaddedLabelBox widget =
Packit 3ff832
                        new EPaddedLabelBox(buff.str,
Packit 3ff832
                                            Gtk.Align.START);
Packit 3ff832
                m_vbox.add(widget);
Packit 3ff832
                widget.show_all();
Packit 3ff832
                buff.erase();
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        if (buff.str != "") {
Packit 3ff832
            EPaddedLabelBox widget = new EPaddedLabelBox(buff.str,
Packit 3ff832
                                                         Gtk.Align.START);
Packit 3ff832
            m_vbox.add(widget);
Packit 3ff832
            widget.show_all();
Packit 3ff832
        }
Packit 3ff832
        show_code_point_description(text);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
    private void show_unicode_description(IBus.UnicodeData data,
Packit 3ff832
                                          string           text) {
Packit 3ff832
        unowned string name = data.get_name();
Packit 3ff832
        {
Packit 3ff832
            EPaddedLabelBox widget = new EPaddedLabelBox(
Packit 3ff832
                    _("Name: %s").printf(name),
Packit 3ff832
                    Gtk.Align.START);
Packit 3ff832
            m_vbox.add(widget);
Packit 3ff832
            widget.show_all();
Packit 3ff832
        }
Packit 3ff832
        unowned string alias = data.get_alias();
Packit 3ff832
        {
Packit 3ff832
            EPaddedLabelBox widget = new EPaddedLabelBox(
Packit 3ff832
                    _("Alias: %s").printf(alias),
Packit 3ff832
                    Gtk.Align.START);
Packit 3ff832
            m_vbox.add(widget);
Packit 3ff832
            widget.show_all();
Packit 3ff832
        }
Packit 3ff832
        show_code_point_description(text);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void hide_candidate_panel() {
Packit 3ff832
        hide();
Packit 3ff832
        m_enter_notify_enable = true;
Packit 3ff832
        m_annotation = "";
Packit 3ff832
        // Call remove_all_children() instead of show_category_list()
Packit 3ff832
        // so that show_category_list do not remove children with
Packit 3ff832
        // PageUp/PageDown.
Packit 3ff832
        remove_all_children();
Packit 3ff832
        if (m_show_unicode)
Packit 3ff832
            update_unicode_blocks();
Packit 3ff832
        else
Packit 3ff832
            update_category_list();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void enter_notify_disable_with_timer() {
Packit 3ff832
        // Enable keyboard operation and disable mouse operation.
Packit 3ff832
        m_enter_notify_enable = false;
Packit 3ff832
        if (m_entry_notify_disable_id > 0) {
Packit 3ff832
            GLib.Source.remove(m_entry_notify_disable_id);
Packit 3ff832
        }
Packit 3ff832
        // Bring back the mouse operation after a timeout.
Packit 3ff832
        m_entry_notify_show_id = GLib.Timeout.add(100, () => {
Packit 3ff832
            m_enter_notify_enable = true;
Packit 3ff832
            return false;
Packit 3ff832
        });
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void candidate_panel_select_index(uint index,
Packit 3ff832
                                              uint button) {
Packit 3ff832
        if (button == BUTTON_CLOSE_BUTTON) {
Packit 3ff832
            hide();
Packit 3ff832
            if (m_candidate_panel_mode &&
Packit 3ff832
                m_lookup_table.get_number_of_candidates() > 0) {
Packit 3ff832
                // Call remove_all_children() instead of show_category_list()
Packit 3ff832
                // so that show_category_list do not remove children with
Packit 3ff832
                // PageUp/PageDown.
Packit 3ff832
                remove_all_children();
Packit 3ff832
            }
Packit 3ff832
            m_result = "";
Packit 3ff832
            return;
Packit 3ff832
        }
Packit 3ff832
        string text = m_lookup_table.get_candidate(index).text;
Packit 3ff832
        unowned GLib.SList<string>? emojis =
Packit 3ff832
                m_emoji_to_emoji_variants_dict.lookup(text);
Packit 3ff832
        if (m_show_emoji_variant && emojis != null &&
Packit 3ff832
            m_backward_index < 0) {
Packit 3ff832
            show_emoji_variants(emojis);
Packit 3ff832
            show_all();
Packit 3ff832
        } else {
Packit 3ff832
            m_result = text;
Packit 3ff832
            hide();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void candidate_panel_cursor_down() {
Packit 3ff832
        enter_notify_disable_with_timer();
Packit 3ff832
        uint ncandidates = m_lookup_table.get_number_of_candidates();
Packit 3ff832
        uint cursor = m_lookup_table.get_cursor_pos();
Packit 3ff832
        if ((cursor + EMOJI_GRID_PAGE) < ncandidates) {
Packit 3ff832
            m_lookup_table.set_cursor_pos(cursor + EMOJI_GRID_PAGE);
Packit 3ff832
        } else if (cursor % EMOJI_GRID_PAGE < ncandidates) {
Packit 3ff832
                m_lookup_table.set_cursor_pos(cursor % EMOJI_GRID_PAGE);
Packit 3ff832
        } else {
Packit 3ff832
            m_lookup_table.set_cursor_pos(0);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void candidate_panel_cursor_up() {
Packit 3ff832
        enter_notify_disable_with_timer();
Packit 3ff832
        int ncandidates = (int)m_lookup_table.get_number_of_candidates();
Packit 3ff832
        int cursor = (int)m_lookup_table.get_cursor_pos();
Packit 3ff832
        int highest_pos =
Packit 3ff832
            ((ncandidates - 1)/ (int)EMOJI_GRID_PAGE * (int)EMOJI_GRID_PAGE)
Packit 3ff832
            + (cursor % (int)EMOJI_GRID_PAGE);
Packit 3ff832
        if ((cursor - (int)EMOJI_GRID_PAGE) >= 0) {
Packit 3ff832
            m_lookup_table.set_cursor_pos(cursor - (int)EMOJI_GRID_PAGE);
Packit 3ff832
        } else if (highest_pos < ncandidates) {
Packit 3ff832
            m_lookup_table.set_cursor_pos(highest_pos);
Packit 3ff832
        } else {
Packit 3ff832
            m_lookup_table.set_cursor_pos(0);
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private int get_page_num() {
Packit 3ff832
        if (m_category_active_index < 0)
Packit 3ff832
            m_category_active_index = 0;
Packit 3ff832
        var row = m_list_box.get_row_at_index(m_category_active_index);
Packit 3ff832
        Gtk.Allocation alloc = { 0, 0, 0, 0 };
Packit 3ff832
        row.get_allocation(out alloc);
Packit 3ff832
        var adjustment = m_scrolled_window.get_vadjustment();
Packit 3ff832
        var page_size = (int)adjustment.get_page_size();
Packit 3ff832
        int page_num = page_size / alloc.height;
Packit 3ff832
        page_num += ((page_size % alloc.height) > 0) ? 1 : 0;
Packit 3ff832
        return page_num;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private bool category_list_cursor_move(uint keyval) {
Packit 3ff832
        return_val_if_fail (m_list_box != null, false);
Packit 3ff832
        GLib.List<weak Gtk.Widget> list = m_list_box.get_children();
Packit 3ff832
        int length = (int)list.length();
Packit 3ff832
        if (length == 0)
Packit 3ff832
            return false;
Packit 3ff832
        switch(keyval) {
Packit 3ff832
        case Gdk.Key.Down:
Packit 3ff832
            if (++m_category_active_index == length)
Packit 3ff832
                m_category_active_index = 0;
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.Up:
Packit 3ff832
            if (--m_category_active_index < 0)
Packit 3ff832
                    m_category_active_index = length - 1;
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.Home:
Packit 3ff832
            m_category_active_index = 0;
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.End:
Packit 3ff832
            m_category_active_index = length - 1;
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.Page_Down:
Packit 3ff832
            var page_num = get_page_num();
Packit 3ff832
            if (m_category_active_index + 1 == length)
Packit 3ff832
                m_category_active_index = 0;
Packit 3ff832
            else if (m_category_active_index + page_num >= length)
Packit 3ff832
                m_category_active_index = length - 1;
Packit 3ff832
            else
Packit 3ff832
                m_category_active_index += page_num;
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.Page_Up:
Packit 3ff832
            var page_num = get_page_num();
Packit 3ff832
            if (m_category_active_index  == 0)
Packit 3ff832
                m_category_active_index = length - 1;
Packit 3ff832
            else if (m_category_active_index - page_num < 0)
Packit 3ff832
                m_category_active_index = 0;
Packit 3ff832
            else
Packit 3ff832
                m_category_active_index -= page_num;
Packit 3ff832
            break;
Packit 3ff832
        }
Packit 3ff832
        var row = m_list_box.get_selected_row();
Packit 3ff832
        if (row != null)
Packit 3ff832
            m_list_box.unselect_row(row);
Packit 3ff832
        clamp_page ();
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool has_variants(uint index) {
Packit 3ff832
        if (index >= m_lookup_table.get_number_of_candidates())
Packit 3ff832
            return false;
Packit 3ff832
        string text = m_lookup_table.get_candidate(index).text;
Packit 3ff832
        unowned GLib.SList<string>? emojis =
Packit 3ff832
                m_emoji_to_emoji_variants_dict.lookup(text);
Packit 3ff832
        if (m_show_emoji_variant && emojis != null &&
Packit 3ff832
            m_backward_index < 0) {
Packit 3ff832
            show_emoji_variants(emojis);
Packit 3ff832
            return true;
Packit 3ff832
        }
Packit 3ff832
        return false;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool key_press_cursor_horizontal(uint keyval,
Packit 3ff832
                                            uint modifiers) {
Packit 3ff832
        assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right);
Packit 3ff832
Packit 3ff832
        if (m_candidate_panel_mode &&
Packit 3ff832
            m_lookup_table.get_number_of_candidates() > 0) {
Packit 3ff832
            enter_notify_disable_with_timer();
Packit 3ff832
            if (keyval == Gdk.Key.Left)
Packit 3ff832
                m_lookup_table.cursor_up();
Packit 3ff832
            else if (keyval == Gdk.Key.Right)
Packit 3ff832
                m_lookup_table.cursor_down();
Packit 3ff832
        } else if (m_entry.get_text().length > 0) {
Packit 3ff832
            int step = 0;
Packit 3ff832
            if (keyval == Gdk.Key.Left)
Packit 3ff832
                step = -1;
Packit 3ff832
            else if (keyval == Gdk.Key.Right)
Packit 3ff832
                step = 1;
Packit 3ff832
            GLib.Signal.emit_by_name(
Packit 3ff832
                    m_entry, "move-cursor",
Packit 3ff832
                    Gtk.MovementStep.VISUAL_POSITIONS,
Packit 3ff832
                    step,
Packit 3ff832
                    (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0
Packit 3ff832
                            ? true : false);
Packit 3ff832
        } else {
Packit 3ff832
            // For Gdk.Key.f and Gdk.Key.b
Packit 3ff832
            if (keyval == Gdk.Key.Left)
Packit 3ff832
                keyval = Gdk.Key.Up;
Packit 3ff832
            else if (keyval == Gdk.Key.Right)
Packit 3ff832
                keyval = Gdk.Key.Down;
Packit 3ff832
            return category_list_cursor_move(keyval);
Packit 3ff832
        }
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool key_press_cursor_vertical(uint keyval,
Packit 3ff832
                                          uint modifiers) {
Packit 3ff832
        assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up ||
Packit 3ff832
                keyval == Gdk.Key.Page_Down || keyval == Gdk.Key.Page_Up);
Packit 3ff832
Packit 3ff832
        if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
Packit 3ff832
            if (keyval == Gdk.Key.Down)
Packit 3ff832
                keyval = Gdk.Key.Page_Down;
Packit 3ff832
            else if (keyval == Gdk.Key.Up)
Packit 3ff832
                keyval = Gdk.Key.Page_Up;
Packit 3ff832
        }
Packit 3ff832
        if ((m_candidate_panel_is_visible || m_annotation.length > 0)
Packit 3ff832
            && m_lookup_table.get_number_of_candidates() > 0) {
Packit 3ff832
            switch (keyval) {
Packit 3ff832
            case Gdk.Key.Down:
Packit 3ff832
                candidate_panel_cursor_down();
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.Up:
Packit 3ff832
                candidate_panel_cursor_up();
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.Page_Down:
Packit 3ff832
                enter_notify_disable_with_timer();
Packit 3ff832
                m_lookup_table.page_down();
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.Page_Up:
Packit 3ff832
                enter_notify_disable_with_timer();
Packit 3ff832
                m_lookup_table.page_up();
Packit 3ff832
                break;
Packit 3ff832
            }
Packit 3ff832
        } else {
Packit 3ff832
            return category_list_cursor_move(keyval);
Packit 3ff832
        }
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool key_press_cursor_home_end(uint keyval,
Packit 3ff832
                                          uint modifiers) {
Packit 3ff832
        assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End);
Packit 3ff832
Packit 3ff832
        uint ncandidates = m_lookup_table.get_number_of_candidates();
Packit 3ff832
        if (m_candidate_panel_mode && ncandidates > 0) {
Packit 3ff832
            enter_notify_disable_with_timer();
Packit 3ff832
            if (keyval == Gdk.Key.Home) {
Packit 3ff832
                m_lookup_table.set_cursor_pos(0);
Packit 3ff832
            } else if (keyval == Gdk.Key.End) {
Packit 3ff832
                m_lookup_table.set_cursor_pos(ncandidates - 1);
Packit 3ff832
            }
Packit 3ff832
            return true;
Packit 3ff832
        }
Packit 3ff832
        if (m_entry.get_text().length > 0) {
Packit 3ff832
            int step = 0;
Packit 3ff832
            if (keyval == Gdk.Key.Home)
Packit 3ff832
                step = -1;
Packit 3ff832
            else if (keyval == Gdk.Key.End)
Packit 3ff832
                step = 1;
Packit 3ff832
            GLib.Signal.emit_by_name(
Packit 3ff832
                    m_entry, "move-cursor",
Packit 3ff832
                    Gtk.MovementStep.DISPLAY_LINE_ENDS,
Packit 3ff832
                    step,
Packit 3ff832
                    (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0
Packit 3ff832
                            ? true : false);
Packit 3ff832
            return true;
Packit 3ff832
        }
Packit 3ff832
        return category_list_cursor_move(keyval);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool key_press_escape() {
Packit 3ff832
        if (m_show_unicode) {
Packit 3ff832
            if (!m_candidate_panel_is_visible) {
Packit 3ff832
                m_show_unicode = false;
Packit 3ff832
                m_category_active_index = -1;
Packit 3ff832
            }
Packit 3ff832
            hide_candidate_panel();
Packit 3ff832
            return true;
Packit 3ff832
        } else if (m_backward_index >= 0 && m_backward != null) {
Packit 3ff832
            show_emoji_for_category(m_backward);
Packit 3ff832
            return true;
Packit 3ff832
        } else if (m_candidate_panel_is_visible && m_backward != null) {
Packit 3ff832
            hide_candidate_panel();
Packit 3ff832
            return true;
Packit 3ff832
        }
Packit 3ff832
        hide();
Packit 3ff832
        if (m_candidate_panel_mode &&
Packit 3ff832
            m_lookup_table.get_number_of_candidates() > 0) {
Packit 3ff832
            // Call remove_all_children() instead of show_category_list()
Packit 3ff832
            // so that show_category_list do not remove children with
Packit 3ff832
            // PageUp/PageDown.
Packit 3ff832
            remove_all_children();
Packit 3ff832
        }
Packit 3ff832
        return false;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool key_press_enter() {
Packit 3ff832
        if (m_candidate_panel_is_visible) {
Packit 3ff832
            uint index = m_lookup_table.get_cursor_pos();
Packit 3ff832
            return has_variants(index);
Packit 3ff832
        } else if (m_category_active_index >= 0) {
Packit 3ff832
            Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row();
Packit 3ff832
            EBoxRow row = gtkrow as EBoxRow;
Packit 3ff832
            if (m_show_unicode)
Packit 3ff832
                show_unicode_for_block(row.text);
Packit 3ff832
            else
Packit 3ff832
                show_emoji_for_category(row.text);
Packit 3ff832
        }
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void entry_enter_keyval(uint keyval) {
Packit 3ff832
        unichar ch = IBus.keyval_to_unicode(keyval);
Packit 3ff832
        if (ch.iscntrl())
Packit 3ff832
            return;
Packit 3ff832
        string str = ch.to_string();
Packit 3ff832
Packit 3ff832
        // what gtk_entry_commit_cb() do
Packit 3ff832
        if (m_entry.get_selection_bounds(null, null)) {
Packit 3ff832
            m_entry.delete_selection();
Packit 3ff832
        } else {
Packit 3ff832
            if (m_entry.get_overwrite_mode()) {
Packit 3ff832
               uint text_length = m_entry.get_buffer().get_length();
Packit 3ff832
               if (m_entry.cursor_position < text_length)
Packit 3ff832
                   m_entry.delete_from_cursor(Gtk.DeleteType.CHARS, 1);
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        int pos = m_entry.get_position();
Packit 3ff832
        m_entry.insert_text(str, -1, ref pos);
Packit 3ff832
        m_entry.set_position(pos);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private Gdk.Rectangle get_monitor_geometry() {
Packit 3ff832
        Gdk.Rectangle monitor_area = { 0, };
Packit 3ff832
Packit 3ff832
        // Use get_monitor_geometry() instead of get_monitor_area().
Packit 3ff832
        // get_monitor_area() excludes docks, but the lookup window should be
Packit 3ff832
        // shown over them.
Packit 3ff832
#if VALA_0_34
Packit 3ff832
        Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor_at_point(
Packit 3ff832
                m_cursor_location.x,
Packit 3ff832
                m_cursor_location.y);
Packit 3ff832
        monitor_area = monitor.get_geometry();
Packit 3ff832
#else
Packit 3ff832
        Gdk.Screen screen = Gdk.Screen.get_default();
Packit 3ff832
        int monitor_num = screen.get_monitor_at_point(m_cursor_location.x,
Packit 3ff832
                                                      m_cursor_location.y);
Packit 3ff832
        screen.get_monitor_geometry(monitor_num, out monitor_area);
Packit 3ff832
#endif
Packit 3ff832
        return monitor_area;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void adjust_window_position() {
Packit 3ff832
        Gdk.Point cursor_right_bottom = {
Packit 3ff832
                m_cursor_location.x + m_cursor_location.width,
Packit 3ff832
                m_cursor_location.y + m_cursor_location.height
Packit 3ff832
        };
Packit 3ff832
Packit 3ff832
        Gtk.Allocation allocation;
Packit 3ff832
        get_allocation(out allocation);
Packit 3ff832
        Gdk.Point window_right_bottom = {
Packit 3ff832
            cursor_right_bottom.x + allocation.width,
Packit 3ff832
            cursor_right_bottom.y + allocation.height
Packit 3ff832
        };
Packit 3ff832
Packit 3ff832
        Gdk.Rectangle monitor_area = get_monitor_geometry();
Packit 3ff832
        int monitor_right = monitor_area.x + monitor_area.width;
Packit 3ff832
        int monitor_bottom = monitor_area.y + monitor_area.height;
Packit 3ff832
Packit 3ff832
        int x, y;
Packit 3ff832
        if (window_right_bottom.x > monitor_right)
Packit 3ff832
            x = monitor_right - allocation.width;
Packit 3ff832
        else
Packit 3ff832
            x = cursor_right_bottom.x;
Packit 3ff832
        if (x < 0)
Packit 3ff832
            x = 0;
Packit 3ff832
Packit 3ff832
        bool changed = false;
Packit 3ff832
        if (window_right_bottom.y > monitor_bottom) {
Packit 3ff832
            y = m_cursor_location.y - allocation.height;
Packit 3ff832
            // Do not up side down in Wayland
Packit 3ff832
            if (m_input_context_path == "") {
Packit 3ff832
                changed = (m_is_up_side_down == false);
Packit 3ff832
                m_is_up_side_down = true;
Packit 3ff832
            } else {
Packit 3ff832
                changed = (m_is_up_side_down == true);
Packit 3ff832
                m_is_up_side_down = false;
Packit 3ff832
            }
Packit 3ff832
        } else {
Packit 3ff832
            y = cursor_right_bottom.y;
Packit 3ff832
            changed = (m_is_up_side_down == true);
Packit 3ff832
            m_is_up_side_down = false;
Packit 3ff832
        }
Packit 3ff832
        if (y < 0)
Packit 3ff832
            y = 0;
Packit 3ff832
Packit 3ff832
        move(x, y);
Packit 3ff832
        if (changed) {
Packit 3ff832
            if (m_redraw_window_id > 0)
Packit 3ff832
                GLib.Source.remove(m_redraw_window_id);
Packit 3ff832
            m_redraw_window_id = GLib.Timeout.add(100, () => {
Packit 3ff832
                m_redraw_window_id = 0;
Packit 3ff832
                this.show_all();
Packit 3ff832
                return false;
Packit 3ff832
            });
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
#if 0
Packit 3ff832
    private void check_action_variant_cb(Gtk.MenuItem item) {
Packit 3ff832
        Gtk.CheckMenuItem check = item as Gtk.CheckMenuItem;
Packit 3ff832
        m_show_emoji_variant = check.get_active();
Packit 3ff832
        // Redraw emoji candidate panel for m_show_emoji_variant
Packit 3ff832
        if (m_candidate_panel_is_visible) {
Packit 3ff832
            // DOTO: queue_draw() does not effect at the real time.
Packit 3ff832
            this.queue_draw();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
#else
Packit 3ff832
    private void check_action_variant_cb(GLib.SimpleAction action,
Packit 3ff832
                                         GLib.Variant?     parameter) {
Packit 3ff832
        m_show_emoji_variant = !action.get_state().get_boolean();
Packit 3ff832
        action.set_state(new GLib.Variant.boolean(m_show_emoji_variant));
Packit 3ff832
        // Redraw emoji candidate panel for m_show_emoji_variant
Packit 3ff832
        if (m_candidate_panel_is_visible) {
Packit 3ff832
            // DOTO: queue_draw() does not effect at the real time.
Packit 3ff832
            this.queue_draw();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
#endif
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void action_close_cb(GLib.SimpleAction action,
Packit 3ff832
                                 GLib.Variant?     parameter) {
Packit 3ff832
        candidate_clicked(0, BUTTON_CLOSE_BUTTON, 0);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void update_favorite_emoji_dict() {
Packit 3ff832
        if (m_emoji_to_data_dict == null ||
Packit 3ff832
            m_annotation_to_emojis_dict == null)
Packit 3ff832
            return;
Packit 3ff832
Packit 3ff832
        for(int i = 0; i < m_favorites.length; i++) {
Packit 3ff832
            var favorite = m_favorites[i];
Packit 3ff832
Packit 3ff832
            string? annotation = "";
Packit 3ff832
            if (i < m_favorite_annotations.length) {
Packit 3ff832
                annotation = m_favorite_annotations[i];
Packit 3ff832
            }
Packit 3ff832
            if (annotation == "")
Packit 3ff832
                continue;
Packit 3ff832
            unowned IBus.EmojiData? data =
Packit 3ff832
                    m_emoji_to_data_dict.lookup(favorite);
Packit 3ff832
            if (data == null) {
Packit 3ff832
                GLib.SList<string> new_annotations = new GLib.SList<string>();
Packit 3ff832
                new_annotations.append(annotation);
Packit 3ff832
                IBus.EmojiData new_data = GLib.Object.new(
Packit 3ff832
                            typeof(IBus.EmojiData),
Packit 3ff832
                            "emoji", favorite.dup(),
Packit 3ff832
                            "annotations", new_annotations,
Packit 3ff832
                            "description", annotation.dup()
Packit 3ff832
                    ) as IBus.EmojiData;
Packit 3ff832
                m_emoji_to_data_dict.insert(favorite, new_data);
Packit 3ff832
            } else {
Packit 3ff832
                unowned GLib.SList<string> annotations = data.get_annotations();
Packit 3ff832
                if (annotations.find_custom(annotation, GLib.strcmp) == null) {
Packit 3ff832
                    annotations.append(annotation);
Packit 3ff832
                    data.set_annotations(annotations.copy());
Packit 3ff832
                }
Packit 3ff832
            }
Packit 3ff832
            unowned GLib.SList<string> emojis =
Packit 3ff832
                    m_annotation_to_emojis_dict.lookup(annotation);
Packit 3ff832
            if (emojis.find_custom(favorite, GLib.strcmp) == null) {
Packit 3ff832
                emojis.append(favorite);
Packit 3ff832
                m_annotation_to_emojis_dict.replace(annotation, emojis.copy());
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void set_annotation(string annotation) {
Packit 3ff832
        m_annotation = annotation;
Packit 3ff832
        remove_all_children();
Packit 3ff832
        if (annotation.length > 0) {
Packit 3ff832
            update_candidate_window();
Packit 3ff832
        } else {
Packit 3ff832
            if (m_show_unicode)
Packit 3ff832
                update_unicode_blocks();
Packit 3ff832
            else
Packit 3ff832
                update_category_list();
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public IBus.LookupTable get_one_dimension_lookup_table() {
Packit 3ff832
        var lookup_table = new IBus.LookupTable(EMOJI_GRID_PAGE, 0, true, true);
Packit 3ff832
        uint i = 0;
Packit 3ff832
        for (; i < m_lookup_table.get_number_of_candidates(); i++) {
Packit 3ff832
            IBus.Text text = new IBus.Text.from_string("");
Packit 3ff832
            text.copy(m_lookup_table.get_candidate(i));
Packit 3ff832
            lookup_table.append_candidate(text);
Packit 3ff832
        }
Packit 3ff832
        if (i > 0)
Packit 3ff832
            lookup_table.set_cursor_pos(m_lookup_table.get_cursor_pos());
Packit 3ff832
        return lookup_table;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public uint get_number_of_candidates() {
Packit 3ff832
        return m_lookup_table.get_number_of_candidates();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public uint get_cursor_pos() {
Packit 3ff832
        return m_lookup_table.get_cursor_pos();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void set_cursor_pos(uint cursor_pos) {
Packit 3ff832
        m_lookup_table.set_cursor_pos(cursor_pos);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public string get_current_candidate() {
Packit 3ff832
        // If category_list mode, do not show the category name on preedit.
Packit 3ff832
        // If candidate_panel mode, the first space key does not show the
Packit 3ff832
        // lookup table but the first candidate is avaiable on preedit.
Packit 3ff832
        if (!m_candidate_panel_mode)
Packit 3ff832
            return "";
Packit 3ff832
        uint cursor = m_lookup_table.get_cursor_pos();
Packit 3ff832
        return m_lookup_table.get_candidate(cursor).text;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public IBus.Text get_title_text() {
Packit 3ff832
        var language = _(IBus.get_language_name(m_current_lang_id));
Packit 3ff832
        uint ncandidates = this.get_number_of_candidates();
Packit 3ff832
        string main_title = _("Emoji Choice");
Packit 3ff832
        if (m_show_unicode)
Packit 3ff832
            main_title = _("Unicode Choice");
Packit 3ff832
        var text = new IBus.Text.from_string(
Packit 3ff832
                "%s (%s) (%u / %u)".printf(
Packit 3ff832
                        main_title,
Packit 3ff832
                        language,
Packit 3ff832
                        this.get_cursor_pos() + 1,
Packit 3ff832
                        ncandidates));
Packit 3ff832
        int char_count = text.text.char_count();
Packit 3ff832
        int start_index = -1;
Packit 3ff832
        for (int i = 0; i < char_count; i++) {
Packit 3ff832
            if (text.text.utf8_offset(i).has_prefix(language)) {
Packit 3ff832
                start_index = i;
Packit 3ff832
                break;
Packit 3ff832
            }
Packit 3ff832
        }
Packit 3ff832
        if (start_index >= 0) {
Packit 3ff832
            var attr = new IBus.Attribute(
Packit 3ff832
                    IBus.AttrType.FOREGROUND,
Packit 3ff832
                    0x808080,
Packit 3ff832
                    start_index,
Packit 3ff832
                    start_index + language.char_count());
Packit 3ff832
            var attrs = new IBus.AttrList();
Packit 3ff832
            attrs.append(attr);
Packit 3ff832
            text.set_attributes(attrs);
Packit 3ff832
        }
Packit 3ff832
        return text;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
#if 0
Packit 3ff832
    public GLib.SList<string>? get_candidates() {
Packit 3ff832
        if (m_annotation.length == 0) {
Packit 3ff832
            return null;
Packit 3ff832
        }
Packit 3ff832
        if (m_annotation.length > m_emoji_max_seq_len) {
Packit 3ff832
            return null;
Packit 3ff832
        }
Packit 3ff832
        string? unicode_point = check_unicode_point(m_annotation);
Packit 3ff832
        GLib.SList<string>? total_emojis =
Packit 3ff832
            lookup_emojis_from_annotation(m_annotation);
Packit 3ff832
        if (total_emojis == null) {
Packit 3ff832
            /* Users can type title strings against lower case.
Packit 3ff832
             * E.g. "Smile" against "smile"
Packit 3ff832
             * But the dictionary has the case sensitive annotations.
Packit 3ff832
             * E.g. ":D" and ":q"
Packit 3ff832
             * So need to call lookup_emojis_from_annotation() twice.
Packit 3ff832
             */
Packit 3ff832
            string lower_annotation = m_annotation.down();
Packit 3ff832
            total_emojis = lookup_emojis_from_annotation(lower_annotation);
Packit 3ff832
        }
Packit 3ff832
        if (unicode_point != null)
Packit 3ff832
            total_emojis.prepend(unicode_point);
Packit 3ff832
        return total_emojis;
Packit 3ff832
    }
Packit 3ff832
#endif
Packit 3ff832
Packit 3ff832
Packit 3ff832
#if 0
Packit 3ff832
    public string run(string    input_context_path,
Packit 3ff832
                      Gdk.Event event) {
Packit 3ff832
        assert (m_loop == null);
Packit 3ff832
Packit 3ff832
        m_is_running = true;
Packit 3ff832
        m_input_context_path = input_context_path;
Packit 3ff832
        m_candidate_panel_is_visible = false;
Packit 3ff832
        m_result = null;
Packit 3ff832
        m_enter_notify_enable = true;
Packit 3ff832
        m_show_unicode = false;
Packit 3ff832
Packit 3ff832
        /* Let gtk recalculate the window size. */
Packit 3ff832
        resize(1, 1);
Packit 3ff832
Packit 3ff832
        m_entry.set_text("");
Packit 3ff832
Packit 3ff832
        show_category_list();
Packit 3ff832
        m_entry.set_activates_default(true);
Packit 3ff832
        show_all();
Packit 3ff832
Packit 3ff832
        /* Some window managers, e.g. MATE, GNOME, Plasma desktops,
Packit 3ff832
         * does not give the keyboard focus when Emojier is lauched
Packit 3ff832
         * twice with Ctrl-Shift-e via XIEvent, if present_with_time()
Packit 3ff832
         * is not applied.
Packit 3ff832
         * But XFCE4 desktop does not effect this bug.
Packit 3ff832
         * Seems this is caused by the window manager's focus stealing
Packit 3ff832
         * prevention logic:
Packit 3ff832
         * https://mail.gnome.org/archives/gtk-devel-list/2017-May/msg00026.html
Packit 3ff832
         */
Packit 3ff832
        present_centralize(event);
Packit 3ff832
Packit 3ff832
        Gdk.Device pointer;
Packit 3ff832
#if VALA_0_34
Packit 3ff832
        Gdk.Seat seat = event.get_seat();
Packit 3ff832
        if (seat == null) {
Packit 3ff832
            var display = get_display();
Packit 3ff832
            seat = display.get_default_seat();
Packit 3ff832
        }
Packit 3ff832
        pointer = seat.get_pointer();
Packit 3ff832
#else
Packit 3ff832
        Gdk.Device device = event.get_device();
Packit 3ff832
        if (device == null) {
Packit 3ff832
            var display = get_display();
Packit 3ff832
            device = display.list_devices().data;
Packit 3ff832
        }
Packit 3ff832
        if (device.get_source() == Gdk.InputSource.KEYBOARD)
Packit 3ff832
            pointer = device.get_associated_device();
Packit 3ff832
        else
Packit 3ff832
            pointer = device;
Packit 3ff832
#endif
Packit 3ff832
        pointer.get_position_double(null,
Packit 3ff832
                                    out m_mouse_x,
Packit 3ff832
                                    out m_mouse_y);
Packit 3ff832
Packit 3ff832
        m_loop = new GLib.MainLoop();
Packit 3ff832
        m_loop.run();
Packit 3ff832
        m_loop = null;
Packit 3ff832
Packit 3ff832
        // Need focus-out on Gtk.Entry to send the emoji to applications.
Packit 3ff832
        Gdk.Event fevent = new Gdk.Event(Gdk.EventType.FOCUS_CHANGE);
Packit 3ff832
        fevent.focus_change.in = 0;
Packit 3ff832
        fevent.focus_change.window  = get_window();
Packit 3ff832
        m_entry.send_focus_change(fevent);
Packit 3ff832
        fevent.focus_change.window  = null;
Packit 3ff832
Packit 3ff832
        hide();
Packit 3ff832
        // Make sure the switcher is hidden before returning from this function.
Packit 3ff832
        while (Gtk.events_pending())
Packit 3ff832
            Gtk.main_iteration();
Packit 3ff832
        m_is_running = false;
Packit 3ff832
Packit 3ff832
        return m_result;
Packit 3ff832
    }
Packit 3ff832
#endif
Packit 3ff832
Packit 3ff832
Packit 3ff832
    /* override virtual functions */
Packit 3ff832
    public override void show_all() {
Packit 3ff832
        base.show_all();
Packit 3ff832
        if (m_candidate_panel_mode)
Packit 3ff832
            show_candidate_panel();
Packit 3ff832
        else if (m_show_unicode)
Packit 3ff832
            show_unicode_blocks();
Packit 3ff832
        else
Packit 3ff832
            show_category_list();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public override void hide() {
Packit 3ff832
        base.hide();
Packit 3ff832
        m_candidate_panel_is_visible = false;
Packit 3ff832
        // m_candidate_panel_mode is not false in when you type something
Packit 3ff832
        // during enabling the candidate panel.
Packit 3ff832
        if (m_redraw_window_id > 0) {
Packit 3ff832
            GLib.Source.remove(m_redraw_window_id);
Packit 3ff832
            m_redraw_window_id = 0;
Packit 3ff832
        }
Packit 3ff832
        if (m_unicode_progress_id > 0) {
Packit 3ff832
            GLib.Source.remove(m_unicode_progress_id);
Packit 3ff832
            m_unicode_progress_id = 0;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public override bool key_press_event(Gdk.EventKey event) {
Packit 3ff832
        uint keyval = event.keyval;
Packit 3ff832
        uint modifiers = event.state;
Packit 3ff832
Packit 3ff832
        /* Need to handle events in key_press_event() instead of
Packit 3ff832
         * key_release_event() so that this can know if the event
Packit 3ff832
         * was handled by IME.
Packit 3ff832
         */
Packit 3ff832
        switch (keyval) {
Packit 3ff832
        case Gdk.Key.Escape:
Packit 3ff832
            if (key_press_escape())
Packit 3ff832
                show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Return:
Packit 3ff832
        case Gdk.Key.KP_Enter:
Packit 3ff832
            if (key_press_enter()) {
Packit 3ff832
                show_all();
Packit 3ff832
            } else {
Packit 3ff832
                m_result = get_current_candidate();
Packit 3ff832
                hide();
Packit 3ff832
            }
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.BackSpace:
Packit 3ff832
            if (m_entry.get_text().length > 0) {
Packit 3ff832
                if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
Packit 3ff832
                                             Gtk.DeleteType.WORD_ENDS, -1);
Packit 3ff832
                } else {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "backspace");
Packit 3ff832
                }
Packit 3ff832
                return true;
Packit 3ff832
            }
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.Delete:
Packit 3ff832
        case Gdk.Key.KP_Delete:
Packit 3ff832
            if (m_entry.get_text().length > 0) {
Packit 3ff832
                if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
Packit 3ff832
                                             Gtk.DeleteType.WORD_ENDS, 1);
Packit 3ff832
                } else {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "delete-from-cursor",
Packit 3ff832
                                             Gtk.DeleteType.CHARS, 1);
Packit 3ff832
                }
Packit 3ff832
                return true;
Packit 3ff832
            }
Packit 3ff832
            break;
Packit 3ff832
        case Gdk.Key.space:
Packit 3ff832
        case Gdk.Key.KP_Space:
Packit 3ff832
            if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
Packit 3ff832
                if (m_entry.get_text().length > 0)
Packit 3ff832
                    entry_enter_keyval(keyval);
Packit 3ff832
            } else if (m_candidate_panel_is_visible) {
Packit 3ff832
                enter_notify_disable_with_timer();
Packit 3ff832
                m_lookup_table.cursor_down();
Packit 3ff832
                show_candidate_panel();
Packit 3ff832
            }
Packit 3ff832
            else {
Packit 3ff832
                category_list_cursor_move(Gdk.Key.Down);
Packit 3ff832
                show_all();
Packit 3ff832
            }
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Right:
Packit 3ff832
        case Gdk.Key.KP_Right:
Packit 3ff832
            key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Left:
Packit 3ff832
        case Gdk.Key.KP_Left:
Packit 3ff832
            key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Down:
Packit 3ff832
        case Gdk.Key.KP_Down:
Packit 3ff832
            key_press_cursor_vertical(Gdk.Key.Down, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Up:
Packit 3ff832
        case Gdk.Key.KP_Up:
Packit 3ff832
            key_press_cursor_vertical(Gdk.Key.Up, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Page_Down:
Packit 3ff832
        case Gdk.Key.KP_Page_Down:
Packit 3ff832
            key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Page_Up:
Packit 3ff832
        case Gdk.Key.KP_Page_Up:
Packit 3ff832
            key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Home:
Packit 3ff832
        case Gdk.Key.KP_Home:
Packit 3ff832
            key_press_cursor_home_end(Gdk.Key.Home, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.End:
Packit 3ff832
        case Gdk.Key.KP_End:
Packit 3ff832
            key_press_cursor_home_end(Gdk.Key.End, modifiers);
Packit 3ff832
            show_all();
Packit 3ff832
            return true;
Packit 3ff832
        case Gdk.Key.Insert:
Packit 3ff832
        case Gdk.Key.KP_Insert:
Packit 3ff832
            GLib.Signal.emit_by_name(m_entry, "toggle-overwrite");
Packit 3ff832
            return true;
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) {
Packit 3ff832
            switch (keyval) {
Packit 3ff832
            case Gdk.Key.f:
Packit 3ff832
                key_press_cursor_horizontal(Gdk.Key.Right, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.b:
Packit 3ff832
                key_press_cursor_horizontal(Gdk.Key.Left, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.n:
Packit 3ff832
            case Gdk.Key.N:
Packit 3ff832
                key_press_cursor_vertical(Gdk.Key.Down, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.p:
Packit 3ff832
            case Gdk.Key.P:
Packit 3ff832
                key_press_cursor_vertical(Gdk.Key.Up, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.h:
Packit 3ff832
                key_press_cursor_home_end(Gdk.Key.Home, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.e:
Packit 3ff832
                key_press_cursor_home_end(Gdk.Key.End, modifiers);
Packit 3ff832
                show_all();
Packit 3ff832
                return true;
Packit 3ff832
            case Gdk.Key.u:
Packit 3ff832
                if (m_entry.get_text().length > 0) {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry,
Packit 3ff832
                                             "delete-from-cursor",
Packit 3ff832
                                             Gtk.DeleteType.PARAGRAPH_ENDS,
Packit 3ff832
                                             -1);
Packit 3ff832
                    return true;
Packit 3ff832
                }
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.a:
Packit 3ff832
                if (m_entry.get_text().length > 0) {
Packit 3ff832
                    m_entry.select_region(0, -1);
Packit 3ff832
                    return true;
Packit 3ff832
                }
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.x:
Packit 3ff832
                if (m_entry.get_text().length > 0) {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "cut-clipboard");
Packit 3ff832
                    return true;
Packit 3ff832
                }
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.C:
Packit 3ff832
            case Gdk.Key.c:
Packit 3ff832
                if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) {
Packit 3ff832
                    if (m_candidate_panel_is_visible) {
Packit 3ff832
                        uint index = m_lookup_table.get_cursor_pos();
Packit 3ff832
                        var text = m_lookup_table.get_candidate(index).text;
Packit 3ff832
                        Gtk.Clipboard clipboard =
Packit 3ff832
                                Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
Packit 3ff832
                        clipboard.set_text(text, -1);
Packit 3ff832
                        clipboard.store();
Packit 3ff832
                        return true;
Packit 3ff832
                    }
Packit 3ff832
                } else if (m_entry.get_text().length > 0) {
Packit 3ff832
                    GLib.Signal.emit_by_name(m_entry, "copy-clipboard");
Packit 3ff832
                    return true;
Packit 3ff832
                }
Packit 3ff832
                break;
Packit 3ff832
            case Gdk.Key.v:
Packit 3ff832
                GLib.Signal.emit_by_name(m_entry, "paste-clipboard");
Packit 3ff832
                return true;
Packit 3ff832
            }
Packit 3ff832
            return false;
Packit 3ff832
        }
Packit 3ff832
Packit 3ff832
        entry_enter_keyval(keyval);
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool is_running() {
Packit 3ff832
        return m_is_running;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public string get_input_context_path() {
Packit 3ff832
        return m_input_context_path;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void set_input_context_path(string input_context_path) {
Packit 3ff832
        m_input_context_path = input_context_path;
Packit 3ff832
        if (input_context_path == "") {
Packit 3ff832
            m_warning_message = _("" +
Packit 3ff832
                "Failed to get the current text application. " +
Packit 3ff832
                "Please re-focus your application. E.g. Press Esc key " +
Packit 3ff832
                "several times to release the emoji typing mode, " +
Packit 3ff832
                "click your desktop and click your text application again."
Packit 3ff832
            );
Packit 3ff832
        } else {
Packit 3ff832
            m_warning_message = "";
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public string get_selected_string() {
Packit 3ff832
        return m_result;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private void reset_window_mode() {
Packit 3ff832
        m_backward_index = -1;
Packit 3ff832
        m_backward = null;
Packit 3ff832
        m_candidate_panel_is_visible = false;
Packit 3ff832
        m_candidate_panel_mode = false;
Packit 3ff832
        // Do not clear m_lookup_table to work with space key later.
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void reset() {
Packit 3ff832
        reset_window_mode();
Packit 3ff832
        m_input_context_path = "";
Packit 3ff832
        m_result = null;
Packit 3ff832
        m_category_active_index = -1;
Packit 3ff832
        m_show_unicode = false;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void present_centralize(Gdk.Event event) {
Packit 3ff832
        Gtk.Allocation allocation;
Packit 3ff832
        get_allocation(out allocation);
Packit 3ff832
        Gdk.Rectangle monitor_area;
Packit 3ff832
        Gdk.Rectangle work_area;
Packit 3ff832
#if VALA_0_34
Packit 3ff832
        Gdk.Display display = Gdk.Display.get_default();
Packit 3ff832
        Gdk.Monitor monitor = display.get_monitor_at_window(this.get_window());
Packit 3ff832
        monitor_area = monitor.get_geometry();
Packit 3ff832
        work_area = monitor.get_workarea();
Packit 3ff832
#else
Packit 3ff832
        Gdk.Screen screen = Gdk.Screen.get_default();
Packit 3ff832
        int monitor_num = screen.get_monitor_at_window(this.get_window());
Packit 3ff832
        screen.get_monitor_geometry(monitor_num, out monitor_area);
Packit 3ff832
        work_area = screen.get_monitor_workarea(monitor_num);
Packit 3ff832
#endif
Packit 3ff832
        int x = (monitor_area.x + monitor_area.width - allocation.width)/2;
Packit 3ff832
        int y = (monitor_area.y + monitor_area.height
Packit 3ff832
                 - allocation.height)/2;
Packit 3ff832
        // Do not hide a bottom panel in XFCE4
Packit 3ff832
        if (work_area.y < y)
Packit 3ff832
            y = work_area.y;
Packit 3ff832
        // FIXME: move() does not work in Wayland
Packit 3ff832
        move(x, y);
Packit 3ff832
Packit 3ff832
        uint32 timestamp = event.get_time();
Packit 3ff832
        present_with_time(timestamp);
Packit 3ff832
        m_entry.set_activates_default(true);
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public void set_cursor_location(int x,
Packit 3ff832
                                    int y,
Packit 3ff832
                                    int width,
Packit 3ff832
                                    int height) {
Packit 3ff832
        Gdk.Rectangle location = Gdk.Rectangle(){
Packit 3ff832
            x = x, y = y, width = width, height = height };
Packit 3ff832
        if (m_cursor_location == location)
Packit 3ff832
            return;
Packit 3ff832
        m_cursor_location = location;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public bool is_candidate_panel_mode() {
Packit 3ff832
        return m_candidate_panel_mode;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static bool has_loaded_emoji_dict() {
Packit 3ff832
        if (m_emoji_to_data_dict == null)
Packit 3ff832
            return false;
Packit 3ff832
        GLib.List keys = m_emoji_to_data_dict.get_keys();
Packit 3ff832
        if (keys.length() == 0)
Packit 3ff832
            return false;
Packit 3ff832
        return true;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void set_annotation_lang(string? lang) {
Packit 3ff832
        if (lang == null || lang == "")
Packit 3ff832
            lang = "en";
Packit 3ff832
        if (m_current_lang_id == lang)
Packit 3ff832
            return;
Packit 3ff832
        m_current_lang_id = lang;
Packit 3ff832
        reload_emoji_dict();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static string get_annotation_lang() {
Packit 3ff832
        return m_current_lang_id;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
    public static void set_emoji_font(string? emoji_font) {
Packit 3ff832
        return_if_fail(emoji_font != null && emoji_font != "");
Packit 3ff832
        Pango.FontDescription font_desc =
Packit 3ff832
                Pango.FontDescription.from_string(emoji_font);
Packit 3ff832
        string font_family = font_desc.get_family();
Packit 3ff832
        if (font_family != null) {
Packit 3ff832
            m_emoji_font_family = font_family;
Packit 3ff832
            m_emoji_font_changed = true;
Packit 3ff832
        }
Packit 3ff832
        int font_size = font_desc.get_size() / Pango.SCALE;
Packit 3ff832
        if (font_size != 0) {
Packit 3ff832
            m_emoji_font_size = font_size;
Packit 3ff832
            m_emoji_font_changed = true;
Packit 3ff832
        }
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void set_partial_match(bool has_partial_match) {
Packit 3ff832
        m_has_partial_match = has_partial_match;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void set_partial_match_length(int length) {
Packit 3ff832
        if (length < 1)
Packit 3ff832
            return;
Packit 3ff832
        m_partial_match_length = length;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void set_partial_match_condition(int condition) {
Packit 3ff832
        if (condition < 0)
Packit 3ff832
            return;
Packit 3ff832
        m_partial_match_condition = condition;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void set_favorites(string[]? unowned_favorites,
Packit 3ff832
                                     string[]? unowned_favorite_annotations) {
Packit 3ff832
        m_favorites = {};
Packit 3ff832
        m_favorite_annotations = {};
Packit 3ff832
        for(int i = 0; i < unowned_favorites.length; i++) {
Packit 3ff832
            string? favorite = unowned_favorites[i];
Packit 3ff832
            // Avoid gsetting value error by manual setting
Packit 3ff832
            GLib.return_if_fail(favorite != null);
Packit 3ff832
            m_favorites += favorite;
Packit 3ff832
        }
Packit 3ff832
        for(int i = 0; i < unowned_favorite_annotations.length; i++) {
Packit 3ff832
            string? favorite_annotation = unowned_favorite_annotations[i];
Packit 3ff832
            GLib.return_if_fail(favorite_annotation != null);
Packit 3ff832
            m_favorite_annotations += favorite_annotation;
Packit 3ff832
        }
Packit 3ff832
        update_favorite_emoji_dict();
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    private static GLib.Object get_load_progress_object() {
Packit 3ff832
            if (m_unicode_progress_object == null)
Packit 3ff832
                m_unicode_progress_object = new LoadProgressObject();
Packit 3ff832
            return m_unicode_progress_object as GLib.Object;
Packit 3ff832
    }
Packit 3ff832
Packit 3ff832
Packit 3ff832
    public static void load_unicode_dict() {
Packit 3ff832
        if (m_unicode_block_list.length() == 0)
Packit 3ff832
            make_unicode_block_dict();
Packit 3ff832
        if (m_name_to_unicodes_dict.size() == 0)
Packit 3ff832
            make_unicode_name_dict(IBusEmojier.get_load_progress_object());
Packit 3ff832
    }
Packit 3ff832
}