/* vim:set et sts=4 sw=4 fileencoding=utf-8: */
/* ibus-hangul - The Hangul Engine For IBus
 * Copyright (C) 2008-2009 Peng Huang <shawn.p.huang@gmail.com>
 * Copyright (C) 2009-2011 Choe Hwanjin <choe.hwanjin@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>           //GVariant

#include <ibus.h>
#include <hangul.h>

#include "i18n.h"
#include "engine.h"
#include "ustring.h"

#include "utils.h"

// On Gnome Shell with IBus 1.5, new property named "symbol" is used to
// represent the mode indicator on the system panel. Note that "symbol" does
// not exist in IBus 1.4.x.
#if IBUS_CHECK_VERSION(1, 5, 0)
    #define IBUS_HANGUL_HAS_SYMBOL
#endif  // IBus >= 1.5

/***
#define get_english_icon() \
                        (g_build_filename(IBUSHANGUL_DATADIR, "/icons/Jini/english.svg", NULL))
#define get_hangul_icon() \
                        (g_build_filename(IBUSHANGUL_DATADIR, "/icons/Jini/hangul.svg", NULL))
#define get_hanja_icon() \
                        (g_build_filename(IBUSHANGUL_DATADIR, "/icons/hanja.svg", NULL))
#define get_symbol_dict() \
                        (g_build_filename(IBUSHANGUL_DATADIR, "/data/symbol.txt", NULL))
#define get_hanja_dict() \
                        (g_build_filename(LIBHANGUL_DATADIR, "/hanja/hanja.txt", NULL))
#define get_emoticon_dict() \
                        (g_build_filename(IBUSHANGUL_DATADIR, "/data/emoticon.txt", NULL))

#define get_autotext_file() \
                        (g_build_filename(g_get_home_dir(), "/.config/ibus-hangul/autotext.txt", NULL))
***/
#define get_english_icon() \
                        (IBUSHANGUL_DATADIR"/icons/Jini/english.svg")
#define get_hangul_icon() \
                        (IBUSHANGUL_DATADIR"/icons/Jini/hangul.svg")
#define get_hanja_icon() \
                        (IBUSHANGUL_DATADIR"/icons/hanja.svg")
#define get_symbol_dict() \
                        (IBUSHANGUL_DATADIR"/data/symbol.txt")
#define get_hanja_dict() \
                        (LIBHANGUL_DATADIR"/hanja/hanja.txt")
#define get_emoticon_dict() \
                        (IBUSHANGUL_DATADIR"/data/emoticon.txt")

#define get_autotext_file() \
                        (g_build_filename(g_get_home_dir(), "/.config/ibus-hangul/autotext.txt", NULL))

typedef struct _IBusHangulEngine IBusHangulEngine;
typedef struct _IBusHangulEngineClass IBusHangulEngineClass;

typedef struct _HotkeyList HotkeyList;

typedef struct _InputModeProperty InputModeProperty;

enum {
    INPUT_MODE_LATIN,
    INPUT_MODE_HANGUL,
    INPUT_MODE_COUNT,
};

struct _IBusHangulEngine {
    IBusEngine parent;

    /* members */
    HangulInputContext *context;
    UString* preedit;
    int input_mode;
    gboolean hanja_mode;
    HanjaList* list_symbol;
    HanjaList* list_emoticon;
    HanjaList* list_hanja;
    int last_lookup_method;

    IBusLookupTable *table;

    IBusProperty    *prop_input_mode;
    IBusProperty    *prop_hanja_mode;
    IBusPropList    *prop_list;

    IBusText        *input_mode_symbols[INPUT_MODE_COUNT];
    IBusText        *input_mode_label[INPUT_MODE_COUNT];
};

struct _IBusHangulEngineClass {
    IBusEngineClass parent;
};

struct KeyEvent {
    guint keyval;
    guint modifiers;
};

struct _HotkeyList {
    guint   all_modifiers;
    GArray *keys;
};

enum {
    LOOKUP_METHOD_EXACT,
    LOOKUP_METHOD_PREFIX,
    LOOKUP_METHOD_SUFFIX,
};

struct _InputModeProperty {
    int input_mode;
    char *key;    //IBus property key for the mode.
    char *label;    //text for the radio menu.
    char *label_for_panel;    //text for the language panel.
    char *icon;
};

InputModeProperty InputModePropertyArray[INPUT_MODE_COUNT] = {
    {INPUT_MODE_LATIN,             //input_mode
        "InputMode.English",                    //key
        N_("English"),                              //label
        "영",                                               //label_for_panel
        get_english_icon(),                     //icon
    },
    {INPUT_MODE_HANGUL, 
        "InputMode.Hangul", 
        N_("Hangul"), 
        "한", 
        get_hangul_icon(), 
    },
};

#define GSettingsName_HiddenFlag 					"hidden-flag"
#define GSettingsName_KeyboardHangul 			"keyboard-hangul"
#define GSettingsName_KeyboardEnglish			"keyboard-english"
#define GSettingsName_KeyboardLanguage		"keyboard-language"
#define GSettingsName_KeyboardXkb				"keyboard-xkb"
#define GSettingsName_KeysHangul					"keys-hangul"
#define GSettingsName_KeysHanja					"keys-hanja"
#define GSettingsName_KeysOn						"keys-on"
#define GSettingsName_KeysOff						"keys-off"
#define GSettingsName_EnableKeysVieditor	"enable-keys-vieditor"
#define GSettingsName_DictSymbol					"dictionary-symbol"
#define GSettingsName_DictHanja					"dictionary-hanja"
#define GSettingsName_DictEmoticon				"dictionary-emoticon"
#define GSettingsName_PreeditWord				"preedit-word-commit"
#define GSettingsName_PreeditReorder			"preedit-auto-reorder"
#define GSettingsName_DisableLatinMode		"disable-latin-mode"
#define GSettingsName_Enable104Keyboard		"enable-104-keyboard"
#define GSettingsName_EnablePreeditFlush	"enable-preedit-flush"
#define GSettingsName_PreeditFlushTime		"preedit-flush-time"
#define GSettingsName_EnableAutoTextMode	"enable-mode-autotext"
#define GSettingsName_KeysAutoTextMode		"keys-mode-autotext"
#define GSettingsName_EnableExtendedLayout	"enable-layout-extended"
#define GSettingsName_EnableGalmadeuliMethod	"enable-method-galmadeuli"
#define GSettingsName_LookupTable				"lookup-table-orientation"

#define GSettingsDefault_HiddenFlag				0x00
#define GSettingsDefault_KeyboardHangul		"2"
#define GSettingsDefault_KeyboardEnglish		"qwerty"
#define GSettingsDefault_KeyboardLanguage	"hangul"
#define GSettingsDefault_KeyboardXkb			"hangul:us"
#define GSettingsDefault_KeysHangul				"Shift+space,Hangul"
#define GSettingsDefault_KeysHanja				"Hangul_Hanja,F9"
#define GSettingsDefault_KeysOn					NULL
#define GSettingsDefault_KeysOff					"Escape"
#define GSettingsDefault_EnableKeysVieditor	FALSE
#define GSettingsDefault_DictSymbol				get_symbol_dict()
#define GSettingsDefault_DictHanja				get_hanja_dict()
#define GSettingsDefault_DictEmoticon			get_emoticon_dict()
#define GSettingsDefault_PreeditWord					FALSE
#define GSettingsDefault_PreeditReorder				TRUE
#define GSettingsDefault_DisableLatinMode		FALSE
#define GSettingsDefault_Enable104Keyboard			FALSE
#define GSettingsDefault_EnablePreeditFlush		TRUE
#define GSettingsDefault_PreeditFlushTime			1000
#define GSettingsDefault_EnableAutoTextMode		TRUE
#define GSettingsDefault_KeysAutoTextMode			IBUS_KEY_comma
#define GSettingsDefault_EnableExtendedLayout	TRUE
#define GSettingsDefault_EnableGalmadeuliMethod	TRUE
#define GSettingsDefault_LookupTable					0

char *xkb_layout_list[] = {
    "hangul:us",
    "hangul:dvorak",
    "hangul:colemak",
    "hangul:default", 
    NULL
};

char *english_layout_list[] = {
    "qwerty",
    "dvorak",
    "colemak",
    NULL
};

char *initial_language_list[] = {
    "english",
    "hangul",
    NULL
};

/* functions prototype */
static void     ibus_hangul_engine_class_init
                                            (IBusHangulEngineClass  *klass);
static void     ibus_hangul_engine_init     (IBusHangulEngine       *hangul);
static GObject*
                ibus_hangul_engine_constructor
                                            (GType                   type,
                                             guint                   n_construct_params,
                                             GObjectConstructParam  *construct_params);
static void     ibus_hangul_engine_destroy  (IBusHangulEngine       *hangul);
static gboolean
                ibus_hangul_engine_process_key_event
                                            (IBusEngine             *engine,
                                             guint                   keyval,
                                             guint                   keycode,
                                             guint                   modifiers);
static void ibus_hangul_engine_focus_in     (IBusEngine             *engine);
static void ibus_hangul_engine_focus_out    (IBusEngine             *engine);
static void ibus_hangul_engine_reset        (IBusEngine             *engine);
static void ibus_hangul_engine_enable       (IBusEngine             *engine);
static void ibus_hangul_engine_disable      (IBusEngine             *engine);
#if 0
static void ibus_engine_set_cursor_location (IBusEngine             *engine,
                                             gint                    x,
                                             gint                    y,
                                             gint                    w,
                                             gint                    h);
static void ibus_hangul_engine_set_capabilities
                                            (IBusEngine             *engine,
                                             guint                   caps);
#endif
static void ibus_hangul_engine_page_up      (IBusEngine             *engine);
static void ibus_hangul_engine_page_down    (IBusEngine             *engine);
static void ibus_hangul_engine_cursor_up    (IBusEngine             *engine);
static void ibus_hangul_engine_cursor_down  (IBusEngine             *engine);
static void ibus_hangul_engine_property_activate
                                            (IBusEngine             *engine,
                                             const gchar            *prop_name,
                                             guint                   prop_state);
#if 0
static void ibus_hangul_engine_property_show
                                                                                        (IBusEngine             *engine,
                                             const gchar            *prop_name);
static void ibus_hangul_engine_property_hide
                                                                                        (IBusEngine             *engine,
                                             const gchar            *prop_name);
#endif

static void ibus_hangul_engine_candidate_clicked
                                            (IBusEngine             *engine,
                                             guint                   index,
                                             guint                   button,
                                             guint                   state);

static void ibus_hangul_engine_flush        (IBusHangulEngine       *hangul);
static void ibus_hangul_engine_clear_preedit_text
                                            (IBusHangulEngine       *hangul);
static void ibus_hangul_engine_update_preedit_text
                                            (IBusHangulEngine       *hangul);

static void ibus_hangul_engine_update_lookup_table
                                            (IBusHangulEngine       *hangul);
///*************************************************///
static void ibus_hangul_engine_update_hanja_list
                                            (IBusHangulEngine *hangul);
static void ibus_hangul_engine_apply_hanja_list
                                            (IBusHangulEngine *hangul);
static void ibus_hangul_engine_hide_lookup_table
                                            (IBusHangulEngine *hangul);
static gboolean ibus_hangul_engine_process_candidate_key_event
                                            (IBusHangulEngine    *hangul,
                                                guint                keyval,
                                                guint                modifiers);
///*************************************************///
static gboolean ibus_hangul_engine_has_preedit
                                            (IBusHangulEngine       *hangul);
///*************************************************///
static void ibus_hangul_engine_update_input_mode_panel 
                                                (IBusEngine *engine);
static IBusText* ibus_hangul_engine_get_input_mode_symbol
                                                (IBusHangulEngine *hangul);
static IBusText* ibus_hangul_engine_get_input_mode_label
                                                (IBusHangulEngine *hangul);
static void ibus_hangul_engine_flush_timeout_on 
                                                (IBusHangulEngine *hangul);
static void ibus_hangul_engine_flush_timeout_off (void);
static gboolean ibus_hangul_engine_flush_timeout 
                                                (gpointer user_data);
static HanjaList* ibus_hangul_engine_lookup_autotext_table 
                                                (const gchar *key);
static char * ibus_hangul_engine_get_autotext 
                                                (const gchar *key);
static gboolean ibus_hangul_engine_process_autotext 
                                                (IBusEngine     *engine,
                                                guint           keyval);
///*************************************************///
static bool ibus_hangul_engine_on_transition
                                            (HangulInputContext     *hic,
                                             ucschar                 c,
                                             const ucschar          *preedit,
                                             void                   *data);

static void ibus_config_value_changed       (IBusConfig             *config,
                                             const gchar            *section,
                                             const gchar            *name,
                                             GVariant               *value,
                                             gpointer                user_data);

static void        lookup_table_set_visible (IBusLookupTable        *table,
                                             gboolean                flag);
static gboolean        lookup_table_is_visible
                                            (IBusLookupTable        *table);

static gboolean key_event_list_match        (GArray                 *list,
                                             guint                   keyval,
                                             guint                   modifiers);

static void     hotkey_list_init            (HotkeyList           *list);
static void     hotkey_list_fini            (HotkeyList           *list);
static void     hotkey_list_set_from_string (HotkeyList         *list,
                                             const char             *str);
///*************************************************///
static void     hotkey_list_set_from_string_vector
                                            (HotkeyList         *list,
                                                const char             **strv);
///*************************************************///
static void     hotkey_list_append          (HotkeyList           *list,
                                             guint                   keyval,
                                             guint                   modifiers);
static gboolean hotkey_list_match           (HotkeyList           *list,
                                             guint                   keyval,
                                             guint                   modifiers);
static gboolean hotkey_list_has_modifier    (HotkeyList           *list,
                                             guint                   keyval);

static glong ucschar_strlen (const ucschar* str);
///*************************************************///
static guint keyval_qwerty_to_dvorak_colemak (guint keyval, guint layout);
///*************************************************///

static IBusEngineClass *parent_class = NULL;
static HanjaTable *table_hanja = NULL;
static HanjaTable *table_symbol = NULL;
static HanjaTable *table_emoticon = NULL;
static IBusConfig *config = NULL;
static GString    *keyboard_hangul = NULL;
static GString    *keyboard_english = NULL;
static GString    *keyboard_language = NULL;// initial_input_mode
static GString    *keyboard_xkb = NULL;
static GString    *dict_symbol = NULL;
static GString    *dict_emoticon = NULL;
static GString    *dict_hanja = NULL;
static HotkeyList keys_hangul;
static HotkeyList keys_hanja;
static HotkeyList keys_on;
static HotkeyList keys_off;
static IBusKeymap *keymap = NULL;
static gboolean preedit_word = GSettingsDefault_PreeditWord;
static gboolean preedit_reorder = GSettingsDefault_PreeditReorder;
static gboolean disable_latin_mode = GSettingsDefault_DisableLatinMode;
static gboolean enable_104_keyboard = GSettingsDefault_Enable104Keyboard;
static gboolean enable_vieditor_key = GSettingsDefault_EnableKeysVieditor;
static guint lookup_table_orientation = GSettingsDefault_LookupTable;
static guint key_event_timeout_id = 0;
static gboolean enable_preedit_flush = GSettingsDefault_EnablePreeditFlush;
static guint preedit_flush_time = GSettingsDefault_PreeditFlushTime;
static gboolean enable_autotext_mode = GSettingsDefault_EnableAutoTextMode;
static guint autotext_mode_key = GSettingsDefault_KeysAutoTextMode;
static HanjaTable *table_autotext = NULL;
static guint previous_keyval = 0;
static gboolean enable_extended_layout = GSettingsDefault_EnableExtendedLayout;
static guint previous_extended_index = 0;
static gboolean enable_galmadeuli_method = GSettingsDefault_EnableGalmadeuliMethod;
static unsigned char hidden_flag = GSettingsDefault_HiddenFlag;

static glong
ucschar_strlen (const ucschar* str)
{
    const ucschar* p = str;
    while (*p != 0)
        p++;
    return p - str;
}

static guint
initial_language_str_to_int (const GString *string)
{
    if (string == NULL) {
        return 1;   //hangul
    }
    
    int i;
    for (i = 0; initial_language_list[i] != NULL; i++) {
        if (g_strcmp0 (string->str, initial_language_list[i]) == 0) {
            return i;
        }
    }
    
    return 1;   //hangul
}

GType
ibus_hangul_engine_get_type (void)
{
    static GType type = 0;

    static const GTypeInfo type_info = {
        sizeof (IBusHangulEngineClass),
        (GBaseInitFunc)     NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc)    ibus_hangul_engine_class_init,
        NULL,
        NULL,
        sizeof (IBusHangulEngine),
        0,
        (GInstanceInitFunc) ibus_hangul_engine_init,
    };

    if (type == 0) {
            type = g_type_register_static (IBUS_TYPE_ENGINE,
                                           "IBusHangulEngine",
                                           &type_info,
                                           (GTypeFlags) 0);
    }

    return type;
}

void
ibus_hangul_init (IBusBus *bus)
{
    GVariant* value;

    config = ibus_bus_get_config (bus);
    if (config)
        g_object_ref_sink (config);

    hidden_flag = GSettingsDefault_HiddenFlag; // ibus_hangul_engine_init ()

    keyboard_hangul = g_string_new (GSettingsDefault_KeyboardHangul);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeyboardHangul);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            g_string_assign (keyboard_hangul, str);
        }
        g_variant_unref(value);
    }

    keyboard_english = g_string_new (GSettingsDefault_KeyboardEnglish);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeyboardEnglish);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            g_string_assign (keyboard_english, str);
        }
        g_variant_unref(value);
    }

    keyboard_language = g_string_new (GSettingsDefault_KeyboardLanguage);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeyboardLanguage);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            g_string_assign (keyboard_language, str);
        }
        g_variant_unref(value);
    }

    keyboard_xkb = g_string_new (GSettingsDefault_KeyboardXkb);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeyboardXkb);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            g_string_assign (keyboard_xkb, str);
        }
        g_variant_unref(value);
    }

    hotkey_list_init(&keys_hangul);
    hotkey_list_set_from_string (&keys_hangul, GSettingsDefault_KeysHangul);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeysHangul);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar **strv = g_variant_get_strv (value, NULL);
            hotkey_list_set_from_string_vector(&keys_hangul, strv);
        }
        g_variant_unref(value);
    }

    hotkey_list_init(&keys_hanja);
    hotkey_list_set_from_string (&keys_hanja, GSettingsDefault_KeysHanja);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeysHanja);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar **strv = g_variant_get_strv (value, NULL);
            hotkey_list_set_from_string_vector(&keys_hanja, strv);
        }
        g_variant_unref(value);
    }

    enable_vieditor_key = GSettingsDefault_EnableKeysVieditor;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_EnableKeysVieditor);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_vieditor_key = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    hotkey_list_init (&keys_on);
    hotkey_list_set_from_string (&keys_on, GSettingsDefault_KeysOn);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeysOn);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar **strv = g_variant_get_strv (value, NULL);
            hotkey_list_set_from_string_vector(&keys_on, strv);
        }
        g_variant_unref (value);
    }

    hotkey_list_init (&keys_off);
    hotkey_list_set_from_string (&keys_off, GSettingsDefault_KeysOff);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeysOff);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar **strv = g_variant_get_strv (value, NULL);
            hotkey_list_set_from_string_vector(&keys_off, strv);
        }
        g_variant_unref(value);
    }

    preedit_word = GSettingsDefault_PreeditWord;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_PreeditWord);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            preedit_word = g_variant_get_boolean (value);
        }
        g_variant_unref(value);
    }

    preedit_reorder = GSettingsDefault_PreeditReorder;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_PreeditReorder);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            preedit_reorder = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    disable_latin_mode = GSettingsDefault_DisableLatinMode;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_DisableLatinMode);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            disable_latin_mode = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    enable_104_keyboard = GSettingsDefault_Enable104Keyboard;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_Enable104Keyboard);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_104_keyboard = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    enable_preedit_flush = GSettingsDefault_EnablePreeditFlush;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_EnablePreeditFlush);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_preedit_flush = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    preedit_flush_time = GSettingsDefault_PreeditFlushTime;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_PreeditFlushTime);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            preedit_flush_time = g_variant_get_uint32 (value);
        }
        g_variant_unref (value);
    }

    lookup_table_orientation = GSettingsDefault_LookupTable;
    value = ibus_config_get_value (config, "panel", 
                                                    GSettingsName_LookupTable);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            lookup_table_orientation = g_variant_get_int32 (value);
        }
        g_variant_unref(value);
    }

    dict_symbol = g_string_new (GSettingsDefault_DictSymbol);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_DictSymbol);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            if ( g_file_test(str, 
                            (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                g_string_assign (dict_symbol, str);
            }
        }
        g_variant_unref(value);
    }
    table_symbol = hanja_table_load (dict_symbol->str);

    dict_emoticon = g_string_new (GSettingsDefault_DictEmoticon);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_DictEmoticon);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            if ( g_file_test(str, 
                            (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                g_string_assign (dict_emoticon, str);
            }
        }
        g_variant_unref(value);
    }
    table_emoticon = hanja_table_load (dict_emoticon->str);

    dict_hanja = g_string_new (GSettingsDefault_DictHanja);
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_DictHanja);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            const gchar *str = g_variant_get_string (value, NULL);
            if ( g_file_test(str, 
                            (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                g_string_assign (dict_hanja, str);
            }
        }
        g_variant_unref(value);
    }
    table_hanja = hanja_table_load (dict_hanja->str);

    enable_autotext_mode = GSettingsDefault_EnableAutoTextMode;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_EnableAutoTextMode);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_autotext_mode = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    if (enable_autotext_mode) {
        if ( g_file_test(get_autotext_file(), 
                    (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
            table_autotext = hanja_table_load (get_autotext_file());
        }
    }

    autotext_mode_key = GSettingsDefault_KeysAutoTextMode;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_KeysAutoTextMode);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            autotext_mode_key = g_variant_get_int32 (value);
        }
        g_variant_unref(value);
    }

    enable_extended_layout = GSettingsDefault_EnableExtendedLayout;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_EnableExtendedLayout);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_extended_layout = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    enable_galmadeuli_method = GSettingsDefault_EnableGalmadeuliMethod;
    value = ibus_config_get_value (config, "engine/hangul", 
                                                    GSettingsName_EnableGalmadeuliMethod);
    if (value != NULL) {
        if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
            enable_galmadeuli_method = g_variant_get_boolean (value);
        }
        g_variant_unref (value);
    }

    keymap = ibus_keymap_get("kr");
}

void
ibus_hangul_exit (void)
{
    if (keymap != NULL) {
	g_object_unref(keymap);
	keymap = NULL;
    }

    hotkey_list_fini (&keys_hangul);
    hotkey_list_fini (&keys_hanja);
    hotkey_list_fini (&keys_on);
    hotkey_list_fini (&keys_off);

    hanja_table_delete (table_hanja);
    table_hanja = NULL;

    hanja_table_delete (table_symbol);
    table_symbol = NULL;

    hanja_table_delete (table_emoticon);
    table_emoticon = NULL;

    g_object_unref (config);
    config = NULL;

    g_string_free (keyboard_hangul, TRUE);
    keyboard_hangul = NULL;

    g_string_free (keyboard_english, TRUE);
    keyboard_english = NULL;

    g_string_free (keyboard_language, TRUE);
    keyboard_language = NULL;

    g_string_free (keyboard_xkb, TRUE);
    keyboard_xkb = NULL;

    g_string_free (dict_symbol, TRUE);
    dict_symbol = NULL;

    g_string_free (dict_emoticon, TRUE);
    dict_emoticon = NULL;

    g_string_free (dict_hanja, TRUE);
    dict_hanja = NULL;

    hanja_table_delete (table_autotext);
    table_autotext = NULL;
}

static void
ibus_hangul_engine_class_init (IBusHangulEngineClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (klass);
    IBusEngineClass *engine_class = IBUS_ENGINE_CLASS (klass);

    parent_class = (IBusEngineClass *) g_type_class_peek_parent (klass);

    object_class->constructor = ibus_hangul_engine_constructor;
    ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_hangul_engine_destroy;

    engine_class->process_key_event = ibus_hangul_engine_process_key_event;

    engine_class->reset = ibus_hangul_engine_reset;
    engine_class->enable = ibus_hangul_engine_enable;
    engine_class->disable = ibus_hangul_engine_disable;

    engine_class->focus_in = ibus_hangul_engine_focus_in;
    engine_class->focus_out = ibus_hangul_engine_focus_out;

    engine_class->page_up = ibus_hangul_engine_page_up;
    engine_class->page_down = ibus_hangul_engine_page_down;

    engine_class->cursor_up = ibus_hangul_engine_cursor_up;
    engine_class->cursor_down = ibus_hangul_engine_cursor_down;

    engine_class->property_activate = ibus_hangul_engine_property_activate;

    engine_class->candidate_clicked = ibus_hangul_engine_candidate_clicked;
}

static void
ibus_hangul_engine_init (IBusHangulEngine *hangul)
{
    IBusProperty* prop;
    IBusText* label;
    IBusText* tooltip;
    IBusText* symbol;

    hangul->context = hangul_ic_new (keyboard_hangul->str);
    hangul_ic_connect_callback (hangul->context, "transition",
                                ibus_hangul_engine_on_transition, hangul);
#ifdef libhangul_3beol
    hangul_ic_set_extended_layout_mode (hangul->context, enable_extended_layout);
    hangul_ic_set_galmadeuli_method_mode (hangul->context, enable_galmadeuli_method);

    hidden_flag = hangul_ic_get_layout_flags (hangul->context);
#endif
    ibus_config_set_value (config, "engine/hangul", 
                                                    GSettingsName_HiddenFlag,
                                                    g_variant_new_byte (hidden_flag) );

    hangul->preedit = ustring_new();
    hangul->list_symbol = NULL;
    hangul->list_emoticon = NULL;
    hangul->list_hanja = NULL;
    hangul->input_mode = initial_language_str_to_int (keyboard_language);
    hangul->hanja_mode = FALSE;
    hangul->last_lookup_method = LOOKUP_METHOD_PREFIX;

    if (disable_latin_mode) {
        hangul->input_mode = INPUT_MODE_HANGUL;
    }


    hangul->prop_list = ibus_prop_list_new ();
    g_object_ref_sink (hangul->prop_list);

    //|sub_prop_list| is a radio menu which is shown when a button in the
    //language panel (i.e. |prop| below) is clicked
    IBusPropList *sub_prop_list = ibus_prop_list_new();
    //Create items for the radio menu
    int i;
    const InputModeProperty *entry = NULL;
    for (i = 0; i < INPUT_MODE_COUNT; i++) {
        const InputModeProperty *inner_entry = &(InputModePropertyArray[i]);
        label = ibus_text_new_from_string(_(inner_entry->label));

        IBusPropState state = PROP_STATE_UNCHECKED;
        if (inner_entry->input_mode == hangul->input_mode) {
            entry = inner_entry;
            state = PROP_STATE_CHECKED;
        }
        prop = ibus_property_new (inner_entry->key,
                              PROP_TYPE_RADIO,
                              label,
                              inner_entry->icon /*icon*/,
                              NULL /*tooltip*/,
                              TRUE /*sensitive*/, 
                              TRUE /*visible*/, 
                              state, 
                              NULL /*sub props*/);
        ibus_prop_list_append (sub_prop_list, prop);
        //|sub_prop_list| owns |prop| by calling g_objcet_ref_sink for the |prop|.
    }

    label = ibus_hangul_engine_get_input_mode_label (hangul);
    tooltip = ibus_text_new_from_string (_("Input Mode"));
    // The label of |prop_composition_mode_| is shown in the language panel.
    // Note that the property name "InputMode" is hard-coded in the Gnome shell.
    // Do not change the name. Othewise the Gnome shell fails to recognize that
    // this property indicates Mozc's input mode.
    // See /usr/share/gnome-shell/js/ui/status/keyboard.js for details.
    prop = ibus_property_new ("InputMode",
                        PROP_TYPE_MENU,
                        label,
                        entry->icon,
                        tooltip,
                        TRUE,
                        TRUE,
                        PROP_STATE_UNCHECKED,
                        sub_prop_list);
    //Gnome shell uses symbol property for the mode indicator text icon if
    //the property name is "InputMode".
#ifdef IBUS_HANGUL_HAS_SYMBOL
    label = ibus_hangul_engine_get_input_mode_symbol (hangul);
    ibus_property_set_symbol (prop, label);
#endif    //IBUS_HANGUL_HAS_SYMBOL
    // Likewise, |prop| owns |sub_prop_list|. We have to sink
    // |prop| here so ibus_engine_update_property() call in
    // PropertyActivate() does not destruct the object.
    g_object_ref_sink (prop);
    hangul->prop_input_mode = prop;
    ibus_prop_list_append (hangul->prop_list, prop);

    label = ibus_text_new_from_string (_("Hanja lock"));
    tooltip = ibus_text_new_from_string (_("Enable/Disable Hanja mode"));
    prop = ibus_property_new ("hanja_mode",
                              PROP_TYPE_TOGGLE,
                              label,
                              get_hanja_icon(),
                              tooltip,
                              TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);

#ifdef IBUS_HANGUL_HAS_SYMBOL
    label = ibus_text_new_from_string ("漢");
    ibus_property_set_symbol(prop, label);
#endif    //IBUS_HANGUL_HAS_SYMBOL

    g_object_ref_sink (prop);
    ibus_prop_list_append (hangul->prop_list, prop);
    hangul->prop_hanja_mode = prop;

    label = ibus_text_new_from_string (_("Hangul Preferences"));
    tooltip = ibus_text_new_from_string (_("Configure hangul engine"));
    prop = ibus_property_new ("hangul_setup",
                              PROP_TYPE_NORMAL,
                              label,
                              "gtk-preferences",
                              tooltip,
                              TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);
    ibus_prop_list_append (hangul->prop_list, prop);

    label = ibus_text_new_from_string (_("IBus Preferences"));
    tooltip = ibus_text_new_from_string (_("Configure ibus"));
    prop = ibus_property_new ("ibus_setup",
                              PROP_TYPE_NORMAL,
                              label,
                              "gtk-properties",
                              tooltip,
                              TRUE, TRUE, PROP_STATE_UNCHECKED, NULL);
    ibus_prop_list_append (hangul->prop_list, prop);

    hangul->table = ibus_lookup_table_new (9, 0, TRUE, FALSE);
    g_object_ref_sink (hangul->table);

    g_signal_connect (config, "value-changed",
                      G_CALLBACK(ibus_config_value_changed), hangul);
}

static GObject*
ibus_hangul_engine_constructor (GType                   type,
                                guint                   n_construct_params,
                                GObjectConstructParam  *construct_params)
{
    IBusHangulEngine *hangul;

    hangul = (IBusHangulEngine *) G_OBJECT_CLASS (parent_class)->constructor (type,
                                                       n_construct_params,
                                                       construct_params);

    return (GObject *)hangul;
}


static void
ibus_hangul_engine_destroy (IBusHangulEngine *hangul)
{
    int i;
    IBusText **input_modes;

    if (hangul->prop_input_mode) {
        g_object_unref (hangul->prop_input_mode);
        hangul->prop_input_mode = NULL;
    }

    if (hangul->prop_hanja_mode) {
        g_object_unref (hangul->prop_hanja_mode);
        hangul->prop_hanja_mode = NULL;
    }

    if (hangul->prop_list) {
        g_object_unref (hangul->prop_list);
        hangul->prop_list = NULL;
    }

    if (hangul->table) {
        g_object_unref (hangul->table);
        hangul->table = NULL;
    }

    if (hangul->context) {
        hangul_ic_delete (hangul->context);
        hangul->context = NULL;
    }

    input_modes = hangul->input_mode_symbols;
    for (i = 0; i < INPUT_MODE_COUNT; ++i) {
        if (input_modes[i] != NULL) {
            g_object_unref(input_modes[i]);
            input_modes[i] = NULL;
        }
    }

    input_modes = hangul->input_mode_label;
    for(i = 0; i < INPUT_MODE_COUNT; ++i) {
        if (input_modes[0] != NULL) {
            g_object_unref(input_modes[i]);
            input_modes[i] = NULL;
        }
    }

    IBUS_OBJECT_CLASS (parent_class)->destroy ((IBusObject *)hangul);
}

static void
ibus_hangul_engine_clear_preedit_text (IBusHangulEngine *hangul)
{
    IBusText *text;

    text = ibus_text_new_from_static_string ("");
    ibus_engine_update_preedit_text ((IBusEngine *)hangul, text, 0, FALSE);
}

static void
ibus_hangul_engine_update_preedit_text (IBusHangulEngine *hangul)
{
    const ucschar *hic_preedit;
    IBusText *text;
    UString *preedit;
    gint preedit_len;

    // ibus-hangul's preedit string is made up of ibus context's
    // internal preedit string and libhangul's preedit string.
    // libhangul only supports one syllable preedit string.
    // In order to make longer preedit string, ibus-hangul maintains
    // internal preedit string.
    hic_preedit = hangul_ic_get_preedit_string (hangul->context);

    preedit = ustring_dup (hangul->preedit);
    preedit_len = ustring_length(preedit);
    ustring_append_ucs4 (preedit, hic_preedit, -1);

    if (ustring_length(preedit) > 0) {
	IBusPreeditFocusMode preedit_option = IBUS_ENGINE_PREEDIT_COMMIT;

        if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
            preedit_option = IBUS_ENGINE_PREEDIT_CLEAR;
        }

        text = ibus_text_new_from_ucs4 ((gunichar*)preedit->data);
        // ibus-hangul's internal preedit string
        ibus_text_append_attribute (text, IBUS_ATTR_TYPE_UNDERLINE,
                IBUS_ATTR_UNDERLINE_SINGLE, 0, preedit_len);
        // Preedit string from libhangul context.
        // This is currently composing syllable.
        ibus_text_append_attribute (text, IBUS_ATTR_TYPE_FOREGROUND,
                0x00ffffff, preedit_len, -1);
        ibus_text_append_attribute (text, IBUS_ATTR_TYPE_BACKGROUND,
                0x00000000, preedit_len, -1);
        ibus_engine_update_preedit_text_with_mode ((IBusEngine *)hangul,
                                                   text,
                                                   ibus_text_get_length (text),
                                                   TRUE,
                                                   preedit_option);
    } else {
        text = ibus_text_new_from_static_string ("");
        ibus_engine_update_preedit_text ((IBusEngine *)hangul, text, 0, FALSE);
    }

    ustring_delete(preedit);
}

static void
ibus_hangul_engine_update_lookup_table_ui (IBusHangulEngine *hangul)
{
    guint cursor_pos;
    const char* comment;
    IBusText* text;

    guint list_symbol_count = 0;
    guint list_emoticon_count = 0;
    //guint list_hanja_count = 0;

    // update aux text
    cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
    list_symbol_count = hanja_list_get_size (hangul->list_symbol);
    if (cursor_pos < list_symbol_count) {   // 기호
        comment = hanja_list_get_nth_comment (hangul->list_symbol, cursor_pos);
    } else {
        cursor_pos -= list_symbol_count;
        list_emoticon_count = hanja_list_get_size (hangul->list_emoticon);
        if (cursor_pos < list_emoticon_count) {   // 이모티콘
            comment = hanja_list_get_nth_comment (hangul->list_emoticon, cursor_pos);
        } else {                                                  // 한자
            cursor_pos -= list_emoticon_count;
            //list_hanja_count = hanja_list_get_size (hangul->list_hanja);
            comment = hanja_list_get_nth_comment (hangul->list_hanja, cursor_pos);
        }
    }
 
    if (comment == NULL) {
        comment = "NULL";
    }

    text = ibus_text_new_from_string (comment);
    ibus_engine_update_auxiliary_text ((IBusEngine *)hangul, text, TRUE);

    // update lookup table
    ibus_engine_update_lookup_table ((IBusEngine *)hangul, hangul->table, TRUE);
}

static void
ibus_hangul_engine_commit_current_candidate (IBusHangulEngine *hangul)
{
    guint cursor_pos;
    const char* key;
    const char* value;
    const ucschar* hic_preedit;
    glong key_len;
    glong hic_preedit_len;
    glong preedit_len;

    IBusText* text;

    guint list_symbol_count = 0;
    guint list_emoticon_count = 0;
    //guint list_hanja_count = 0;

    // update aux text
    cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
    list_symbol_count = hanja_list_get_size (hangul->list_symbol);
    if (cursor_pos < list_symbol_count) {   // 기호
        key = hanja_list_get_nth_key (hangul->list_symbol, cursor_pos);
        value = hanja_list_get_nth_value (hangul->list_symbol, cursor_pos);
    } else {
        cursor_pos -= list_symbol_count;
        list_emoticon_count = hanja_list_get_size (hangul->list_emoticon);
        if (cursor_pos < list_emoticon_count) {   // 이모티콘
            key = hanja_list_get_nth_key (hangul->list_emoticon, cursor_pos);
            value = hanja_list_get_nth_value (hangul->list_emoticon, cursor_pos);
        } else {                                                  // 한자
            cursor_pos -= list_emoticon_count;
            //list_hanja_count = hanja_list_get_size (hangul->list_hanja);
            key = hanja_list_get_nth_key (hangul->list_hanja, cursor_pos);
            value = hanja_list_get_nth_value (hangul->list_hanja, cursor_pos);
        }
    }
    hic_preedit = hangul_ic_get_preedit_string (hangul->context);

    key_len = g_utf8_strlen(key, -1);
    preedit_len = ustring_length(hangul->preedit);
    hic_preedit_len = ucschar_strlen (hic_preedit);

    if (hangul->last_lookup_method == LOOKUP_METHOD_PREFIX) {
        if (preedit_len == 0 && hic_preedit_len == 0) {
            /* remove surrounding_text */
            if (key_len > 0) {
                ibus_engine_delete_surrounding_text ((IBusEngine *)hangul,
                        -key_len , key_len);
            }
        } else {
            /* remove ibus preedit text */
            if (key_len > 0) {
                glong n = MIN(key_len, preedit_len);
                ustring_erase (hangul->preedit, 0, n);
                key_len -= preedit_len;
            }

            /* remove hic preedit text */
            if (key_len > 0) {
                hangul_ic_reset (hangul->context);
                key_len -= hic_preedit_len;
            }
        }
    } else {
        /* remove hic preedit text */
        if (hic_preedit_len > 0) {
            hangul_ic_reset (hangul->context);
            key_len -= hic_preedit_len;
        }

        /* remove ibus preedit text */
        if (key_len > preedit_len) {
            ustring_erase (hangul->preedit, 0, preedit_len);
            key_len -= preedit_len;
        } else if (key_len > 0) {
            ustring_erase (hangul->preedit, 0, key_len);
            key_len = 0;
        }

        /* remove surrounding_text */
        if (key_len > 0) {
            ibus_engine_delete_surrounding_text ((IBusEngine *)hangul,
                    -key_len , key_len);
        }
    }

    /* clear preedit text before commit */
    ibus_hangul_engine_clear_preedit_text (hangul);

    if (value == NULL) {
        value = "NULL";
    }

    text = ibus_text_new_from_string (value);
    ibus_engine_commit_text ((IBusEngine *)hangul, text);

    ibus_hangul_engine_update_preedit_text (hangul);
}

static gchar*
h_ibus_text_get_substring (IBusText* ibus_text, glong p1, glong p2)
{
    const gchar* text;
    const gchar* begin;
    const gchar* end;
    gchar* substring;
    glong limit;
    glong pos;
    glong n;

    text = ibus_text_get_text (ibus_text);
    limit = ibus_text_get_length (ibus_text) + 1;
    if (text == NULL || limit == 0)
        return NULL;

    p1 = MAX(0, p1);
    p2 = MAX(0, p2);

    pos = MIN(p1, p2);
    n = ABS(p2 - p1);

    if (pos + n > limit)
        n = limit - pos;

    begin = g_utf8_offset_to_pointer (text, pos);
    end = g_utf8_offset_to_pointer (begin, n);

    substring = g_strndup (begin, end - begin);
    return substring;
}

static HanjaList*
ibus_hangul_engine_lookup_hanja_table (const char* key, int method)
{
    HanjaList* list = NULL;

    if (key == NULL)
        return NULL;

    switch (method) {
    case LOOKUP_METHOD_EXACT:
        if (table_hanja != NULL) {
            list = hanja_table_match_exact (table_hanja, key);
        }
        break;
    case LOOKUP_METHOD_PREFIX:
        if (table_hanja != NULL) {
            list = hanja_table_match_prefix (table_hanja, key);
        }
        break;
    case LOOKUP_METHOD_SUFFIX:
        if (list == NULL && table_hanja != NULL) {
            list = hanja_table_match_suffix (table_hanja, key);
        }
        break;
    }

    return list;
}
 
static HanjaList*
ibus_hangul_engine_lookup_symbol_table (const char* key, int method)
{
    HanjaList* list = NULL;

    if (key == NULL)
        return NULL;

    switch (method) {
    case LOOKUP_METHOD_EXACT:
        if (table_symbol != NULL) {
            list = hanja_table_match_exact (table_symbol, key);
        }
        break;
    case LOOKUP_METHOD_PREFIX:
        if (table_symbol != NULL) {
            list = hanja_table_match_prefix (table_symbol, key);
        }
        break;
    case LOOKUP_METHOD_SUFFIX:
        if (table_symbol != NULL) {
            list = hanja_table_match_suffix (table_symbol, key);
        }
        break;
    }

    return list;
}
 
static HanjaList*
ibus_hangul_engine_lookup_emoticon_table (const char* key, int method)
{
    HanjaList* list = NULL;

    if (key == NULL)
        return NULL;

    switch (method) {
    case LOOKUP_METHOD_EXACT:
        if (table_emoticon != NULL) {
            list = hanja_table_match_exact (table_emoticon, key);
        }
        break;
    case LOOKUP_METHOD_PREFIX:
        if (table_emoticon != NULL) {
            list = hanja_table_match_prefix (table_emoticon, key);
        }
        break;
    case LOOKUP_METHOD_SUFFIX:
        if (table_emoticon != NULL) {
            list = hanja_table_match_suffix (table_emoticon, key);
        }
        break;
    }

    return list;
}

static void
ibus_hangul_engine_update_hanja_list (IBusHangulEngine *hangul)
{
    gchar* dictionayr_key;
    gchar* preedit_utf8;
    const ucschar* hic_preedit;
    UString* preedit;
    int lookup_method;
    IBusText* ibus_text = NULL;
    guint cursor_pos = 0;
    guint anchor_pos = 0;

    if (hangul->list_symbol != NULL) {
        hanja_list_delete (hangul->list_symbol);
        hangul->list_symbol = NULL;
    }

    if (hangul->list_emoticon != NULL) {
        hanja_list_delete (hangul->list_emoticon);
        hangul->list_emoticon = NULL;
    }

    if (hangul->list_hanja != NULL) {
        hanja_list_delete (hangul->list_hanja);
        hangul->list_hanja = NULL;
    }

    hic_preedit = hangul_ic_get_preedit_string (hangul->context);

    dictionayr_key = NULL;
    lookup_method = LOOKUP_METHOD_PREFIX;

    preedit = ustring_dup (hangul->preedit);
    ustring_append_ucs4 (preedit, hic_preedit, -1);

    if (ustring_length(preedit) > 0) {
        preedit_utf8 = ustring_to_utf8 (preedit, -1);
        if (preedit_word || hangul->hanja_mode) {
            dictionayr_key = preedit_utf8;
            lookup_method = LOOKUP_METHOD_PREFIX;
        } else {
            gchar* substr;
            ibus_engine_get_surrounding_text ((IBusEngine *)hangul, &ibus_text,
                    &cursor_pos, &anchor_pos);

            substr = h_ibus_text_get_substring (ibus_text,
                    cursor_pos - 64, cursor_pos);

            if (substr != NULL) {
                dictionayr_key = g_strconcat (substr, preedit_utf8, NULL);
                g_free (preedit_utf8);
            } else {
                dictionayr_key = preedit_utf8;
            }
            lookup_method = LOOKUP_METHOD_SUFFIX;
        }
    } else {
        ibus_engine_get_surrounding_text ((IBusEngine *)hangul, &ibus_text,
                &cursor_pos, &anchor_pos);
        if (cursor_pos != anchor_pos) {
            // If we have selection in surrounding text, we use that.
            dictionayr_key = h_ibus_text_get_substring (ibus_text,
                    cursor_pos, anchor_pos);
            lookup_method = LOOKUP_METHOD_EXACT;
        } else {
            dictionayr_key = h_ibus_text_get_substring (ibus_text,
                    cursor_pos - 64, cursor_pos);
            lookup_method = LOOKUP_METHOD_SUFFIX;
        }
    }

    if (dictionayr_key != NULL) {    // #3
        hangul->list_symbol = ibus_hangul_engine_lookup_symbol_table (dictionayr_key,
                lookup_method);
        hangul->list_emoticon = ibus_hangul_engine_lookup_emoticon_table (dictionayr_key,
                lookup_method);
        hangul->list_hanja = ibus_hangul_engine_lookup_hanja_table (dictionayr_key,
                lookup_method);
        hangul->last_lookup_method = lookup_method;
        g_free (dictionayr_key);
    }

    ustring_delete (preedit);

    if (ibus_text != NULL)
        g_object_unref (ibus_text);
}

static void
ibus_hangul_engine_apply_hanja_list (IBusHangulEngine *hangul)
{// #2-2
    HanjaList *list_symbol = hangul->list_symbol;
    HanjaList *list_emoticon = hangul->list_emoticon;
    HanjaList *list_hanja = hangul->list_hanja;
    if (list_symbol != NULL || list_emoticon != NULL || list_hanja != NULL) {
        int i, n;

        ibus_lookup_table_clear (hangul->table);

        if (list_symbol != NULL) { //기호
            n = hanja_list_get_size (list_symbol);
            for (i = 0; i < n; i++) {
                const char* value = hanja_list_get_nth_value (list_symbol, i);
                IBusText* text = ibus_text_new_from_string (value);
                ibus_lookup_table_append_candidate (hangul->table, text);
            }
        }
        if (list_emoticon != NULL) { //이모티콘
            n = hanja_list_get_size (list_emoticon);
            for (i = 0; i < n; i++) {
                const char* value = hanja_list_get_nth_value (list_emoticon, i);
                IBusText* text = ibus_text_new_from_string (value);
                ibus_lookup_table_append_candidate (hangul->table, text);
            }
        }
        if (list_hanja != NULL) {   //한자
            n = hanja_list_get_size (list_hanja);
            for (i = 0; i < n; i++) {
                const char* value = hanja_list_get_nth_value (list_hanja, i);
                IBusText* text = ibus_text_new_from_string (value);
                ibus_lookup_table_append_candidate (hangul->table, text);
            }
        }

        ibus_lookup_table_set_cursor_pos (hangul->table, 0);
        ibus_hangul_engine_update_lookup_table_ui (hangul);
        lookup_table_set_visible (hangul->table, TRUE);
    }
}

static void
ibus_hangul_engine_hide_lookup_table (IBusHangulEngine *hangul)
{
    gboolean is_visible;
    is_visible = lookup_table_is_visible (hangul->table);

    // Sending hide lookup table message when the lookup table
    // is not visible results wrong behavior. So I have to check
    // whether the table is visible or not before to hide.
    if (is_visible) {
        ibus_engine_hide_lookup_table ((IBusEngine *)hangul);
        ibus_engine_hide_auxiliary_text ((IBusEngine *)hangul);
        lookup_table_set_visible (hangul->table, FALSE);
    }

    if (hangul->list_symbol != NULL) {
        hanja_list_delete (hangul->list_symbol);
        hangul->list_symbol = NULL;
    }
    if (hangul->list_emoticon != NULL) {
        hanja_list_delete (hangul->list_emoticon);
        hangul->list_emoticon = NULL;
    }
    if (hangul->list_hanja != NULL) {
        hanja_list_delete (hangul->list_hanja);
        hangul->list_hanja = NULL;
    }
}

static void
ibus_hangul_engine_update_lookup_table (IBusHangulEngine *hangul)
{
    ibus_hangul_engine_flush_timeout_off();

    ibus_hangul_engine_update_hanja_list (hangul);

    if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
		ibus_lookup_table_set_orientation (hangul->table, lookup_table_orientation);
	// We should redraw preedit text with IBUS_ENGINE_PREEDIT_CLEAR option
	// here to prevent committing it on focus out event incidentally.
	ibus_hangul_engine_update_preedit_text (hangul);
        ibus_hangul_engine_apply_hanja_list (hangul);
    } else {
        ibus_hangul_engine_hide_lookup_table (hangul);
    }
}

static gboolean
ibus_hangul_engine_process_candidate_key_event (IBusHangulEngine    *hangul,
                                                guint                keyval,
                                                guint                modifiers)
{
    if (keyval == IBUS_Escape) {
        ibus_hangul_engine_hide_lookup_table (hangul);
	// When the lookup table is poped up, preedit string is 
	// updated with IBUS_ENGINE_PREEDIT_CLEAR option.
	// So, when focus is out, the preedit text will not be committed.
	// To prevent this problem, we have to update preedit text here
	// with IBUS_ENGINE_PREEDIT_COMMIT option.
	ibus_hangul_engine_update_preedit_text (hangul);
        return TRUE;
    } else if (keyval == IBUS_Return) {
        ibus_hangul_engine_commit_current_candidate (hangul);

        if (hangul->hanja_mode && ibus_hangul_engine_has_preedit (hangul)) {
            ibus_hangul_engine_update_lookup_table (hangul);
        } else {
            ibus_hangul_engine_hide_lookup_table (hangul);
        }
        return TRUE;
    } else if (keyval >= IBUS_1 && keyval <= IBUS_9) {
        guint page_no;
        guint page_size;
        guint cursor_pos;

        page_size = ibus_lookup_table_get_page_size (hangul->table);
        cursor_pos = ibus_lookup_table_get_cursor_pos (hangul->table);
        page_no = cursor_pos / page_size;

        cursor_pos = page_no * page_size + (keyval - IBUS_1);
        ibus_lookup_table_set_cursor_pos (hangul->table, cursor_pos);

        ibus_hangul_engine_commit_current_candidate (hangul);

        if (hangul->hanja_mode && ibus_hangul_engine_has_preedit (hangul)) {
            ibus_hangul_engine_update_lookup_table (hangul);
        } else {
            ibus_hangul_engine_hide_lookup_table (hangul);
        }
        return TRUE;
    } else if (keyval == IBUS_Page_Up) {
        ibus_lookup_table_page_up (hangul->table);
        ibus_hangul_engine_update_lookup_table_ui (hangul);
        return TRUE;
    } else if (keyval == IBUS_Page_Down) {
        ibus_lookup_table_page_down (hangul->table);
        ibus_hangul_engine_update_lookup_table_ui (hangul);
        return TRUE;
    } else {
        if (lookup_table_orientation == 0) {
            // horizontal
            if (keyval == IBUS_Left) {
                ibus_lookup_table_cursor_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Right) {
                ibus_lookup_table_cursor_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Up) {
                ibus_lookup_table_page_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Down) {
                ibus_lookup_table_page_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            }
        } else {
            // vertical
            if (keyval == IBUS_Left) {
                ibus_lookup_table_page_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Right) {
                ibus_lookup_table_page_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Up) {
                ibus_lookup_table_cursor_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_Down) {
                ibus_lookup_table_cursor_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            }
        }
    }

    if (!hangul->hanja_mode) {
        if (lookup_table_orientation == 0) {
            // horizontal
            if (keyval == IBUS_h) {
                ibus_lookup_table_cursor_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_l) {
                ibus_lookup_table_cursor_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_k) {
                ibus_lookup_table_page_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_j) {
                ibus_lookup_table_page_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            }
        } else {
            // vertical
            if (keyval == IBUS_h) {
                ibus_lookup_table_page_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_l) {
                ibus_lookup_table_page_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_k) {
                ibus_lookup_table_cursor_up (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            } else if (keyval == IBUS_j) {
                ibus_lookup_table_cursor_down (hangul->table);
                ibus_hangul_engine_update_lookup_table_ui (hangul);
                return TRUE;
            }
        }
    }

    return FALSE;
}

static gboolean
ibus_hangul_engine_process_key_event (IBusEngine     *engine,
                                      guint           keyval,
                                      guint           keycode,
                                      guint           modifiers)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    guint mask;
    gboolean retval;
    const ucschar *str;

    gunichar keysym;

    mask = IBUS_BUTTON1_MASK | IBUS_BUTTON2_MASK | IBUS_BUTTON3_MASK;
    if (modifiers & mask ) {
        ibus_hangul_engine_flush (hangul);
        return TRUE;
    }

    if (enable_104_keyboard) {
        if ( (keyval == 0xffea) && (keycode == 100) ) {//IBUS_KEY_Alt_R && Alt_R
            keyval = 0xff31;    //IBUS_Hangul
            keycode = 122;      //Hangul
        } else if ( (keyval == 0xffe4) && (keycode == 97) ) {//IBUS_KEY_Control_R && Control_R
            keyval = 0xff34;    //IBUS_Hangul_Hanja
            keycode = 123;      //Hangul_Hanja
        } else if (modifiers & IBUS_RELEASE_MASK) {
            return FALSE;
        }
    } else if (modifiers & IBUS_RELEASE_MASK) {
        return FALSE;
    }

    if (enable_vieditor_key) {
        /* This feature is for vi* users.
        * On Esc, the input mode is changed to latin */
        if (hotkey_list_match (&keys_off, keyval, modifiers)) {
            hangul->input_mode = INPUT_MODE_LATIN;

            ibus_hangul_engine_update_input_mode_panel (engine);

            ibus_hangul_engine_flush (hangul);

            /* If we return TRUE, then vi will not receive "ESC" key event. */
            return FALSE;
        }

        /* This feature is for vi* users.
        * On Key, the input mode is changed to hangul */
        if (hotkey_list_match (&keys_on, keyval, modifiers)) {
            hangul->input_mode = INPUT_MODE_HANGUL;

            ibus_hangul_engine_update_input_mode_panel (engine);

            ibus_hangul_engine_flush (hangul);

            /* If we return TRUE, then vi will not receive On key event. */
            return FALSE;
        }
    }

    if (hotkey_list_has_modifier(&keys_hangul, keyval))
        return FALSE;

    if (hotkey_list_match(&keys_hangul, keyval, modifiers)) {

        if (disable_latin_mode) {
            hangul->input_mode = INPUT_MODE_HANGUL;
        } else if (hangul->input_mode == INPUT_MODE_LATIN) {
            hangul->input_mode = INPUT_MODE_HANGUL;
        } else if (hangul->input_mode == INPUT_MODE_HANGUL) {
            hangul->input_mode = INPUT_MODE_LATIN;
        } else {
            hangul->input_mode = INPUT_MODE_HANGUL;
        }

        ibus_hangul_engine_update_input_mode_panel (engine);

        ibus_hangul_engine_flush (hangul);

        return TRUE;
    }

    if (hangul->input_mode == INPUT_MODE_LATIN) {
        if (g_strcmp0(keyboard_xkb->str, xkb_layout_list[0]) != 0) {//hangul:us
            return FALSE;
        }

        if (g_strcmp0(keyboard_english->str, english_layout_list[1]) == 0) {//dvorak
            keyval = keyval_qwerty_to_dvorak_colemak(keyval, 0);
        } else if (g_strcmp0(keyboard_english->str, english_layout_list[2]) == 0) {//colemak
            keyval = keyval_qwerty_to_dvorak_colemak(keyval, 1);
        } else {
            return FALSE;
        }

        //Ignore key event with control, alt, super or mod5
        mask = IBUS_CONTROL_MASK |
            IBUS_MOD1_MASK | IBUS_MOD3_MASK | IBUS_MOD4_MASK | IBUS_MOD5_MASK;
        if(keyval && !(modifiers & mask)) {
            keysym = (gchar) keyval;
            if (keysym) {
                IBusText *text;
                text = ibus_text_new_from_unichar (keysym);
                ibus_engine_commit_text (engine, text);
                return TRUE;
            }
        }
        return FALSE;
    }


    // if we don't ignore shift keys, shift key will make flush the preedit 
    // string. So you cannot input shift+key.
    // Let's think about these examples:
    //   dlTek (2 set)
    //   qhRdmaqkq (2 set)
    if (keyval == IBUS_Shift_L || keyval == IBUS_Shift_R)
        return FALSE;

    // If a hotkey has any modifiers, we ignore that modifier
    // keyval, or we cannot make the hanja key work.
    // Because when we get the modifier key alone, we commit the
    // current preedit string. So after that, even if we get the
    // right hanja key event, we don't have preedit string to be changed
    // to hanja word.
    // See this bug: http://code.google.com/p/ibus/issues/detail?id=1036
    if (hotkey_list_has_modifier(&keys_hanja, keyval))
        return FALSE; 

    ibus_hangul_engine_flush_timeout_off();
    ibus_hangul_engine_flush_timeout_on(hangul);

    if (hotkey_list_match(&keys_hanja, keyval, modifiers)) {
        if (hangul->list_symbol == NULL && hangul->list_emoticon == NULL && hangul->list_hanja == NULL) {
            ibus_hangul_engine_update_lookup_table (hangul); // #1
        } else {
            ibus_hangul_engine_hide_lookup_table (hangul);
        }
        return TRUE;
    }

    if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
        retval = ibus_hangul_engine_process_candidate_key_event (hangul,
                     keyval, modifiers);
        if (hangul->hanja_mode) {
            if (retval) {
                ibus_hangul_engine_flush_timeout_off();
                return TRUE;
            }
        } else {
            ibus_hangul_engine_flush_timeout_off();
            return TRUE;
        }
    }

    // If we've got a key event with some modifiers, commit current
    // preedit string and ignore this key event.
    // So, if you want to add some key event handler, put it 
    // before this code.
    // Ignore key event with control, alt, super or mod5
    mask = IBUS_CONTROL_MASK |
	    IBUS_MOD1_MASK | IBUS_MOD3_MASK | IBUS_MOD4_MASK | IBUS_MOD5_MASK;
    if (modifiers & mask) {
        ibus_hangul_engine_flush_timeout_off();
        ibus_hangul_engine_flush (hangul);
        return FALSE;
    }

    if (keyval == IBUS_BackSpace) {
        retval = hangul_ic_backspace (hangul->context);
        if (!retval) {
            guint preedit_len = ustring_length (hangul->preedit);
            if (preedit_len > 0) {
                ustring_erase (hangul->preedit, preedit_len - 1, 1);
                retval = TRUE;
            }
        }

        ibus_hangul_engine_update_preedit_text (hangul);

        if (hangul->hanja_mode) {
            if (ibus_hangul_engine_has_preedit (hangul)) {
                ibus_hangul_engine_update_lookup_table (hangul);
            } else {
                ibus_hangul_engine_hide_lookup_table (hangul);
            }
        }
    } else {
	// We need to normalize the keyval to US qwerty keylayout,
	// because the korean input method is depend on the position of
	// each key, not the character. We make the keyval from keycode
	// as if the keyboard is US qwerty layout. Then we can assume the
	// keyval represent the position of the each key.
	// But if the hic is in transliteration mode, then we should not
	// normalize the keyval.
        bool is_transliteration_mode =
             hangul_ic_is_transliteration(hangul->context);
        if (!is_transliteration_mode) {
            if (keymap != NULL) {
                keyval = ibus_keymap_lookup_keysym(keymap, keycode, modifiers);
            }
        }

        // ignore capslock
        if (modifiers & IBUS_LOCK_MASK) {
            if (keyval >= 'A' && keyval <= 'z') {
                if (keyval < 0x5B)
                    keyval = keyval + 0x20;
                else
                    keyval = keyval - 0x20;
            }
        }

#ifdef libhangul_3beol
        retval = hangul_ic_process_with_capslock (hangul->context, keyval, 
																	(modifiers & IBUS_LOCK_MASK) );
#else
        retval = hangul_ic_process (hangul->context, keyval);
#endif

        str = hangul_ic_get_commit_string (hangul->context);
        if (preedit_word || hangul->hanja_mode) {
            const ucschar* hic_preedit;

            hic_preedit = hangul_ic_get_preedit_string (hangul->context);
            if (hic_preedit != NULL && hic_preedit[0] != 0) {
                ustring_append_ucs4 (hangul->preedit, str, -1);
            } else {
                IBusText *text;
                const ucschar* preedit;

                ustring_append_ucs4 (hangul->preedit, str, -1);
                if (ustring_length (hangul->preedit) > 0) {
                    /* clear preedit text before commit */
                    ibus_hangul_engine_clear_preedit_text (hangul);

                    preedit = ustring_begin (hangul->preedit);
                    text = ibus_text_new_from_ucs4 ((gunichar*)preedit);
                    ibus_engine_commit_text (engine, text);
                }
                ustring_clear (hangul->preedit);
            }
        } else {
            guint layout_index = 0;
#ifdef libhangul_3beol
            if (hangul_ic_get_extended_layout_prevkey (hangul->context)) {
                ibus_hangul_engine_flush_timeout_off();
            }
            layout_index = hangul_ic_get_extended_layout_index (hangul->context);
#endif
            if (layout_index) {// 확장 배열로 들어갈 때는 flush 를 꺼준다
                ibus_hangul_engine_flush_timeout_off();
                previous_extended_index = layout_index;
            } else if (previous_extended_index) {// 확장 글쇠값을 넣은 뒤에 값을 지운다
                previous_extended_index = 0;
            } else if (enable_autotext_mode) {// 확장 글쇠를 처리하지 않을  상용구를 쓴다
                if ( g_file_test(get_autotext_file(), 
                            (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                    if ( ibus_hangul_engine_process_autotext (engine, keyval) ) {
                        return TRUE;
                    }
                }
            }

            if (str != NULL && str[0] != 0) {
                IBusText *text;

                /* clear preedit text before commit */
                ibus_hangul_engine_clear_preedit_text (hangul);

                text = ibus_text_new_from_ucs4 (str);
                ibus_engine_commit_text (engine, text);
            }
        }

        ibus_hangul_engine_update_preedit_text (hangul);

        if (hangul->hanja_mode) {
            ibus_hangul_engine_update_lookup_table (hangul);
        }

        if (!retval)
            ibus_hangul_engine_flush (hangul);
    }

    /* We always return TRUE here even if we didn't use this event.
     * Instead, we forward the event to clients.
     *
     * Because IBus has a problem with sync mode.
     * I think it's limitations of IBus implementation.
     * We call several engine functions(updating preedit text and committing
     * text) inside this function.
     * But clients cannot receive the results of other calls until this
     * function ends. Clients only get one result from a remote call at a time
     * because clients may run on event loop.
     * Clients may process this event first and then get the results which
     * may change the preedit text or commit text.
     * So the event order is broken.
     * Call order:
     *      engine                          client
     *                                      call process_key_event
     *      begin process_key_event
     *        call commit_text
     *        call update_preedit_text
     *      return the event as unused
     *                                      receive the result of process_key_event
     *                                      receive the result of commit_text
     *                                      receive the result of update_preedit_text
     *
     * To solve this problem, we return TRUE as if we consumed this event.
     * After that, we forward this event to clients.
     * Then clients may get the events in correct order.
     * This approach is a kind of async processing.
     * Call order:
     *      engine                          client
     *                                      call process_key_event
     *      begin process_key_event
     *        call commit_text
     *        call update_preedit_text
     *        call forward_key_event
     *      return the event as used
     *                                      receive the result of process_key_event
     *                                      receive the result of commit_text
     *                                      receive the result of update_preedit_text
     *                                      receive the forwarded key event
     *
     * See: https://github.com/choehwanjin/ibus-hangul/issues/40
     */
    if (!retval) {
        ibus_engine_forward_key_event (engine, keyval, keycode, modifiers);
    }

    return TRUE;
}

static void
ibus_hangul_engine_flush (IBusHangulEngine *hangul)
{
    const gunichar *str;
    IBusText *text;

    ibus_hangul_engine_hide_lookup_table (hangul);

    str = hangul_ic_flush (hangul->context);

    ustring_append_ucs4 (hangul->preedit, str, -1);

    if (ustring_length (hangul->preedit) != 0) {
        /* clear preedit text before commit */
        ibus_hangul_engine_clear_preedit_text (hangul);

	str = ustring_begin (hangul->preedit);
	text = ibus_text_new_from_ucs4 (str);

	ibus_engine_commit_text ((IBusEngine *) hangul, text);

	ustring_clear(hangul->preedit);
    }

    ibus_hangul_engine_update_preedit_text (hangul);
}

static void
ibus_hangul_engine_focus_in (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    ibus_hangul_engine_update_input_mode_panel (engine);

    if (hangul->hanja_mode) {
        ibus_property_set_state (hangul->prop_hanja_mode, PROP_STATE_CHECKED);
    } else {
        ibus_property_set_state (hangul->prop_hanja_mode, PROP_STATE_UNCHECKED);
    }

    ibus_engine_register_properties (engine, hangul->prop_list);

    ibus_hangul_engine_update_preedit_text (hangul);

    if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
        ibus_hangul_engine_update_lookup_table_ui (hangul);
    }

    parent_class->focus_in (engine);
}

static void
ibus_hangul_engine_focus_out (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    if (hangul->list_symbol == NULL && hangul->list_emoticon == NULL && hangul->list_hanja == NULL) {
	// ibus-hangul uses
	// ibus_engine_update_preedit_text_with_mode() function which makes
	// the preedit string committed automatically when the focus is out.
	// So we don't need to commit the preedit here.
	// hangul_ic_reset (hangul->context);
    } else {
        ibus_hangul_engine_hide_lookup_table (hangul);
	// When the lookup table is poped up, preedit string is 
	// updated with IBUS_ENGINE_PREEDIT_CLEAR option.
	// So, when focus is out, the preedit text will not be committed.
	// To prevent this problem, we have to update preedit text here
	// with IBUS_ENGINE_PREEDIT_COMMIT option.
        ibus_hangul_engine_update_preedit_text (hangul);
    }

	hangul_ic_reset (hangul->context);

    parent_class->focus_out ((IBusEngine *) hangul);
}

static void
ibus_hangul_engine_reset (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    ibus_hangul_engine_flush (hangul);
    parent_class->reset (engine);
}

static void
ibus_hangul_engine_enable (IBusEngine *engine)
{
    parent_class->enable (engine);

    ibus_engine_get_surrounding_text (engine, NULL, NULL, NULL);
}

static void
ibus_hangul_engine_disable (IBusEngine *engine)
{
    ibus_hangul_engine_focus_out (engine);
    parent_class->disable (engine);
}

static void
ibus_hangul_engine_page_up (IBusEngine *engine)
{
    parent_class->page_up (engine);
}

static void
ibus_hangul_engine_page_down (IBusEngine *engine)
{
    parent_class->page_down (engine);
}

static void
ibus_hangul_engine_cursor_up (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
        ibus_lookup_table_cursor_up (hangul->table);
        ibus_hangul_engine_update_lookup_table_ui (hangul);
    }

    parent_class->cursor_up (engine);
}

static void
ibus_hangul_engine_cursor_down (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    if (hangul->list_symbol != NULL || hangul->list_emoticon != NULL || hangul->list_hanja != NULL) {
        ibus_lookup_table_cursor_down (hangul->table);
        ibus_hangul_engine_update_lookup_table_ui (hangul);
    }

    parent_class->cursor_down (engine);
}

static void
ibus_hangul_engine_property_activate (IBusEngine    *engine,
                                      const gchar   *prop_name,
                                      guint          prop_state)
{
    if (g_str_has_suffix(prop_name, "setup")) {
        if (g_strcmp0(prop_name, "hangul_setup") == 0) {
            GError *error = NULL;
            gchar *argv[2] = { NULL, };

            argv[0] = "ibus-setup-hangul";
            argv[1] = NULL;
            g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
        } else if (g_strcmp0(prop_name, "ibus_setup") == 0) {
            GError *error = NULL;
            gchar *argv[2] = { NULL, };

            argv[0] = "ibus-setup";
            argv[1] = NULL;
            g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
        }
    } else if (g_str_has_prefix(prop_name, "InputMode")) {
        if (prop_state != PROP_STATE_CHECKED) {
            return;
        }

        IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

        if (disable_latin_mode){
            hangul->input_mode = INPUT_MODE_HANGUL;
        } else {
            guint i;
            for (i = 0; i < INPUT_MODE_COUNT; i++) {
                if (g_strcmp0 (prop_name, InputModePropertyArray[i].key) == 0) {
                    hangul->input_mode = i;
                    break;
                }
            }
        }

        ibus_hangul_engine_update_input_mode_panel (engine);

        ibus_hangul_engine_flush (hangul);
    } else if (g_strcmp0(prop_name, "hanja_mode") == 0) {
        IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

        hangul->hanja_mode = !hangul->hanja_mode;
        if (hangul->hanja_mode) {
            ibus_property_set_state (hangul->prop_hanja_mode,
                    PROP_STATE_CHECKED);
        } else {
            ibus_property_set_state (hangul->prop_hanja_mode,
                    PROP_STATE_UNCHECKED);
        }

        ibus_engine_update_property (engine, hangul->prop_hanja_mode);
        ibus_hangul_engine_flush (hangul);
    }
}

static gboolean
ibus_hangul_engine_has_preedit (IBusHangulEngine *hangul)
{
    guint preedit_len;
    const ucschar *hic_preedit;

    hic_preedit = hangul_ic_get_preedit_string (hangul->context);
    if (hic_preedit[0] != 0)
        return TRUE;

    preedit_len = ustring_length (hangul->preedit);
    if (preedit_len > 0)
        return TRUE;

    return FALSE;
}

static void
ibus_hangul_engine_update_input_mode_panel (IBusEngine *engine)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    //IBusPropList * ibus_property_get_sub_props (IBusProperty *prop);
    IBusPropList *sub_prop_list = ibus_property_get_sub_props (hangul->prop_input_mode);
    //IBusProperty * ibus_prop_list_get (IBusPropList *prop_list, guint index); 
    IBusProperty *sub_prop;
    int i;
    // update property state
    for (i = 0; i < sub_prop_list->properties->len; i++) {
        sub_prop = ibus_prop_list_get(sub_prop_list, i);
        if (g_strcmp0(ibus_property_get_key(sub_prop), 
                    InputModePropertyArray[hangul->input_mode].key) == 0) {
            ibus_property_set_state(sub_prop, PROP_STATE_CHECKED);
        } else {
            ibus_property_set_state(sub_prop, PROP_STATE_UNCHECKED);
        }
        ibus_engine_update_property (engine, sub_prop);
    } // end update property state

    // Update the text icon for Gnome shell.
#ifdef IBUS_HANGUL_HAS_SYMBOL
    IBusText *symbol = ibus_hangul_engine_get_input_mode_symbol (hangul);
    ibus_property_set_symbol (hangul->prop_input_mode, symbol);
#endif  // IBUS_HANGUL_HAS_SYMBOL
    ibus_property_set_icon (hangul->prop_input_mode, 
                InputModePropertyArray[hangul->input_mode].icon);

    IBusText *label = ibus_hangul_engine_get_input_mode_label (hangul);
    ibus_property_set_label (hangul->prop_input_mode, label);
    
    ibus_engine_update_property (engine, hangul->prop_input_mode);
}

static IBusText *
ibus_hangul_engine_get_input_mode_symbol (IBusHangulEngine *hangul)
{
    IBusText **symbols = hangul->input_mode_symbols;

    if (symbols[0] == NULL) {
        int i;
        for (i = 0; i < INPUT_MODE_COUNT; i++) {
            symbols[i] = ibus_text_new_from_string (
                                            InputModePropertyArray[i].label_for_panel);
            g_object_ref_sink(symbols[i]);
        }
    }

    return symbols[hangul->input_mode];
}

static IBusText *
ibus_hangul_engine_get_input_mode_label (IBusHangulEngine *hangul)
{
    IBusText **label = hangul->input_mode_label;

    if (label[0] == NULL) {
        int i;
        for (i = 0; i < INPUT_MODE_COUNT; i++) {
            label[i] = ibus_text_new_from_string (
                                            g_strdup_printf ("%s (%s)", 
                                            _("Input Mode"), 
                                            _(InputModePropertyArray[i].label)));
            g_object_ref_sink(label[i]);
        }
    }

    return label[hangul->input_mode];
}

static bool
ibus_hangul_engine_on_transition (HangulInputContext     *hic,
                                  ucschar                 c,
                                  const ucschar          *preedit,
                                  void                   *data)
{
    if (!preedit_reorder) {
        if (hangul_is_choseong (c)) {
            if (hangul_ic_has_jungseong (hic) || hangul_ic_has_jongseong (hic))
                return false;
        }

        if (hangul_is_jungseong (c)) {
            if (hangul_ic_has_jongseong (hic))
                return false;
        }
    }

    return true;
}

static void
ibus_config_value_changed (IBusConfig   *config,
                           const gchar  *section,
                           const gchar  *name,
                           GVariant     *value,
                           gpointer      user_data)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) user_data;

    // name 의 값이 keyboard-hangul 은 keyboard_hangul 형태로 넘어오나...
    if (g_strcmp0(section, "engine/hangul") == 0) {
        if (g_str_has_prefix(name, "keyboard")) {//g_str_has_suffix
            if ( g_str_has_suffix (name, "hangul") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        g_string_assign (keyboard_hangul, str);
                    } else {
                        g_string_assign (keyboard_hangul, GSettingsDefault_KeyboardHangul);
                    }
                    hangul_ic_select_keyboard (hangul->context, keyboard_hangul->str);

#ifdef libhangul_3beol
                    hidden_flag = hangul_ic_get_layout_flags (hangul->context);
#endif
                    ibus_config_set_value (config, "engine/hangul", 
                                                                    GSettingsName_HiddenFlag,
                                                                    g_variant_new_byte (hidden_flag) );
                }
            } else if ( g_str_has_suffix (name, "english") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        g_string_assign (keyboard_english, str);
                    } else {
                        g_string_assign (keyboard_english, GSettingsDefault_KeyboardEnglish);
                    }
                }
            } else if ( g_str_has_suffix (name, "language") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        g_string_assign (keyboard_language, str);
                    } else {
                        g_string_assign (keyboard_language, GSettingsDefault_KeyboardLanguage);
                    }
                }
            } else if ( g_str_has_suffix (name, "xkb") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        g_string_assign (keyboard_xkb, str);
                    } else {
                        g_string_assign (keyboard_xkb, GSettingsDefault_KeyboardXkb);
                    }
                }
            }
        } else if (g_str_has_prefix(name, "keys")) {
            if ( g_str_has_suffix (name, "hangul") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar **strv = g_variant_get_strv(value, NULL);
                        hotkey_list_set_from_string_vector(&keys_hangul, strv);
                    } else {
                        hotkey_list_set_from_string(&keys_hangul, GSettingsDefault_KeysHangul);
                    }
                }
            } else if ( g_str_has_suffix (name, "hanja") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar **strv = g_variant_get_strv(value, NULL);
                        hotkey_list_set_from_string_vector(&keys_hanja, strv);
                    } else {
                        hotkey_list_set_from_string(&keys_hanja, GSettingsDefault_KeysHanja);
                    }
                }
            } else if ( g_str_has_suffix (name, "on") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar **strv = g_variant_get_strv(value, NULL);
                        hotkey_list_set_from_string_vector(&keys_on, strv);
                    } else {
                        hotkey_list_set_from_string(&keys_on, GSettingsDefault_KeysOn);
                    }
                }
            } else if ( g_str_has_suffix (name, "off") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar **strv = g_variant_get_strv(value, NULL);
                        hotkey_list_set_from_string_vector(&keys_off, strv);
                    } else {
                        hotkey_list_set_from_string(&keys_on, GSettingsDefault_KeysOff);
                    }
                }
            } else if ( g_str_has_suffix (name, "autotext") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        autotext_mode_key = g_variant_get_int32(value);
                    } else {
                        autotext_mode_key = GSettingsDefault_KeysAutoTextMode;
                    }
                }
            } 
        } else if (g_str_has_prefix(name, "preedit")) {
            if ( g_str_has_suffix (name, "commit") ) {// "word_commit"
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        preedit_word = g_variant_get_boolean (value);
                    } else {
                        preedit_word = GSettingsDefault_PreeditWord;
                    }
                }
            } else if ( g_str_has_suffix (name, "reorder") ) {// "auto_reorder"
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        preedit_reorder = g_variant_get_boolean (value);
                    } else {
                        preedit_reorder = GSettingsDefault_PreeditReorder;
                    }
                }
            } else if ( g_str_has_suffix (name, "time") ) {// "flush_time"
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        preedit_flush_time = g_variant_get_uint32 (value);
                    } else {
                        preedit_flush_time = GSettingsDefault_PreeditFlushTime;
                    }
                }
            } 
        } else if (g_str_has_prefix(name, "disable")) {
            if ( g_str_has_suffix (name, "mode") ) {// "disable-latin-mode"
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        disable_latin_mode = g_variant_get_boolean (value);
                    } else {
                        disable_latin_mode = GSettingsDefault_DisableLatinMode;
                    }
                    if (disable_latin_mode) {
                        hangul->input_mode = INPUT_MODE_HANGUL;
                    }

                    ibus_hangul_engine_update_input_mode_panel ((IBusEngine *)hangul);

                    ibus_hangul_engine_flush (hangul);
                }
                
            } 
        } else if (g_str_has_prefix(name, "dictionary")) {
            if ( g_str_has_suffix (name, "symbol") ) {
                if (value != NULL) {
                    g_string_assign (dict_symbol, GSettingsDefault_DictSymbol);
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        if ( g_file_test(str, 
                                (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                            g_string_assign (dict_symbol, str);
                        }
                    }
                    hanja_table_delete (table_symbol);
                    table_symbol = hanja_table_load (dict_symbol->str);
                }
            } else if ( g_str_has_suffix (name, "emoticon") ) {
                if (value != NULL) {
                    g_string_assign (dict_emoticon, GSettingsDefault_DictEmoticon);
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        if ( g_file_test(str, 
                                (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                            g_string_assign (dict_emoticon, str);
                        }
                    }
                    hanja_table_delete (table_emoticon);
                    table_emoticon = hanja_table_load (dict_emoticon->str);
                }
            } else if ( g_str_has_suffix (name, "hanja") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        const gchar *str = g_variant_get_string(value, NULL);
                        if ( g_file_test(str, 
                                (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                            g_string_assign (dict_hanja, str);
                        }
                    }
                    hanja_table_delete (table_hanja);
                    table_hanja = hanja_table_load (dict_hanja->str);
                }
            } 
        } else if (g_str_has_prefix(name, "enable")) {
            if ( g_str_has_suffix (name, "keyboard") ) {// "104_keyboard"
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        enable_104_keyboard = g_variant_get_boolean (value);
                    } else {
                        enable_104_keyboard = GSettingsDefault_Enable104Keyboard;
                    }
                }
            } else if ( g_str_has_suffix (name, "autotext") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        previous_keyval = 0;
                        enable_autotext_mode = g_variant_get_boolean (value);
                        if (enable_autotext_mode) {
                            if ( g_file_test(get_autotext_file(), 
                                        (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) ) {
                                if (table_autotext != NULL) {
                                    hanja_table_delete (table_autotext);
                                }
                                table_autotext = hanja_table_load (get_autotext_file());
                            }
                        }
                    }
                }
            } else if ( g_str_has_suffix (name, "vieditor") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        enable_vieditor_key = g_variant_get_boolean (value);
                    } else {
                        enable_vieditor_key = GSettingsDefault_EnableKeysVieditor;
                    }
                }
            } else if ( g_str_has_suffix (name, "flush") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        enable_preedit_flush = g_variant_get_boolean (value);
                    } else {
                        enable_preedit_flush = GSettingsDefault_EnablePreeditFlush;
                    }
                }
            } else if ( g_str_has_suffix (name, "extended") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        enable_extended_layout = g_variant_get_boolean (value);
                    } else {
                        enable_extended_layout = GSettingsDefault_EnableExtendedLayout;
                    }
#ifdef libhangul_3beol
                    hangul_ic_set_extended_layout_mode (hangul->context, enable_extended_layout);
#endif
                }
            } else if ( g_str_has_suffix (name, "galmadeuli") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        enable_galmadeuli_method = g_variant_get_boolean (value);
                    } else {
                        enable_galmadeuli_method = GSettingsDefault_EnableGalmadeuliMethod;
                    }
#ifdef libhangul_3beol
                    hangul_ic_set_galmadeuli_method_mode (hangul->context, enable_galmadeuli_method);
#endif
                }
            }
        }
    } else if (g_strcmp0 (section, "panel") == 0) {
        if ( g_str_has_prefix(name, "lookup") ) {// "lookup_table"
            if ( g_str_has_suffix(name, "orientation") ) {
                if (value != NULL) {
                    if (g_strcmp0 (g_variant_print (value, FALSE), "()") != 0) {
                        lookup_table_orientation = g_variant_get_int32(value);
                    } else {
                        lookup_table_orientation = GSettingsDefault_LookupTable;
                    }
                }
            }
        }
    }
}

static void
lookup_table_set_visible (IBusLookupTable *table, gboolean flag)
{
    g_object_set_data (G_OBJECT(table), "visible", GUINT_TO_POINTER(flag));
}

static gboolean
lookup_table_is_visible (IBusLookupTable *table)
{
    gpointer res = g_object_get_data (G_OBJECT(table), "visible");
    return GPOINTER_TO_UINT(res);
}

static void
key_event_list_append(GArray* list, guint keyval, guint modifiers)
{
    struct KeyEvent ev = { keyval, modifiers};
    g_array_append_val(list, ev);
}

static gboolean
key_event_list_match(GArray* list, guint keyval, guint modifiers)
{
    guint i;
    guint mask;

    /* ignore capslock and numlock */
    mask = IBUS_SHIFT_MASK |
           IBUS_CONTROL_MASK |
           IBUS_MOD1_MASK |
           IBUS_MOD3_MASK |
           IBUS_MOD4_MASK |
           IBUS_MOD5_MASK;

    modifiers &= mask;
    for (i = 0; i < list->len; ++i) {
        struct KeyEvent* ev = &g_array_index(list, struct KeyEvent, i);
        if (ev->keyval == keyval && ev->modifiers == modifiers) {
            return TRUE;
        }
    }

    return FALSE;
}

static void
ibus_hangul_engine_candidate_clicked (IBusEngine     *engine,
                                      guint           index,
                                      guint           button,
                                      guint           state)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;
    if (hangul == NULL)
	return;

    if (hangul->table == NULL)
	return;

    ibus_lookup_table_set_cursor_pos (hangul->table, index);
    ibus_hangul_engine_commit_current_candidate (hangul);

    if (hangul->hanja_mode) {
	ibus_hangul_engine_update_lookup_table (hangul);
    } else {
	ibus_hangul_engine_hide_lookup_table (hangul);
    }
}

static void
hotkey_list_init(HotkeyList* list)
{
    list->all_modifiers = 0;
    list->keys = g_array_sized_new(FALSE, TRUE, sizeof(struct KeyEvent), 4);
}

static void
hotkey_list_fini(HotkeyList* list)
{
    g_array_free(list->keys, TRUE);
    list->keys = NULL;
}

static void
hotkey_list_append_from_string(HotkeyList *list, const char* str)
{
    guint keyval = 0;
    guint modifiers = 0;
    gboolean res;

    res = ibus_key_event_from_string(str, &keyval, &modifiers);
    if (res) {
	hotkey_list_append(list, keyval, modifiers);
    }
}

static void
hotkey_list_append(HotkeyList *list, guint keyval, guint modifiers)
{
    list->all_modifiers |= modifiers;
    key_event_list_append(list->keys, keyval, modifiers);
}

static void
hotkey_list_set_from_string(HotkeyList *list, const char* str)
{
    gchar** items = g_strsplit(str, ",", 0);

    list->all_modifiers = 0;
    g_array_set_size(list->keys, 0);

    if (items != NULL) {
        int i;
        for (i = 0; items[i] != NULL; ++i) {
	    hotkey_list_append_from_string(list, items[i]);
        }
        g_strfreev(items);
    }
}
 
static void
hotkey_list_set_from_string_vector(HotkeyList *list, const char **strv)
{
    list->all_modifiers = 0;
    g_array_set_size(list->keys, 0);

    if (strv != NULL) {
        int i;
        for (i = 0; strv[i] != NULL; ++i) {
            hotkey_list_append_from_string(list, strv[i]);
        }
    }
}

static gboolean
hotkey_list_match(HotkeyList* list, guint keyval, guint modifiers)
{
    return key_event_list_match(list->keys, keyval, modifiers);
}

static gboolean
hotkey_list_has_modifier(HotkeyList* list, guint keyval)
{
    if (list->all_modifiers & IBUS_CONTROL_MASK) {
	if (keyval == IBUS_Control_L || keyval == IBUS_Control_R)
	    return TRUE;
    }

    if (list->all_modifiers & IBUS_MOD1_MASK) {
	if (keyval == IBUS_Alt_L || keyval == IBUS_Alt_R)
	    return TRUE;
    }

    if (list->all_modifiers & IBUS_SUPER_MASK) {
	if (keyval == IBUS_Super_L || keyval == IBUS_Super_R)
	    return TRUE;
    }

    if (list->all_modifiers & IBUS_HYPER_MASK) {
	if (keyval == IBUS_Hyper_L || keyval == IBUS_Hyper_R)
	    return TRUE;
    }

    if (list->all_modifiers & IBUS_META_MASK) {
	if (keyval == IBUS_Meta_L || keyval == IBUS_Meta_R)
	    return TRUE;
    }

    return FALSE;
}

static guint
keyval_qwerty_to_dvorak_colemak (guint keyval, guint layout)
{//{dvorak, colemak}
    static unsigned short table[][2] = {
    { '!',    '!' },    /* ! */
    { '_',    '"' },    /* " */
    { '#',    '#' },    /* # */
    { '$',    '$' },    /* $ */
    { '%',    '%' },    /* % */
    { '&',    '&' },    /* & */
    { '-',    '\'' },    /* ' */
    { '(',    '(' },    /* ( */
    { ')',    ')' },    /* ) */
    { '*',    '*' },    /* * */
    { '}',    '+' },    /* + */
    { 'w',    ',' },    /* , */
    { '[',    '-' },    /* - */
    { 'v',    '.' },    /* . */
    { 'z',    '/' },    /* / */
    { '0',    '0' },    /* 0 */
    { '1',    '1' },    /* 1 */
    { '2',    '2' },    /* 2 */
    { '3',    '3' },    /* 3 */
    { '4',    '4' },    /* 4 */
    { '5',    '5' },    /* 5 */
    { '6',    '6' },    /* 6 */
    { '7',    '7' },    /* 7 */
    { '8',    '8' },    /* 8 */
    { '9',    '9' },    /* 9 */
    { 'S',    'O' },    /* : */
    { 's',    'o' },    /* ; */
    { 'W',    '<' },    /* < */
    { ']',    '=' },    /* = */
    { 'V',    '>' },    /* > */
    { 'Z',    '?' },    /* ? */
    { '@',    '@' },    /* @ */
    { 'A',    'A' },    /* A */
    { 'X',    'B' },    /* B */
    { 'J',    'C' },    /* C */
    { 'E',    'S' },    /* D */
    { '>',    'F' },    /* E */
    { 'U',    'T' },    /* F */
    { 'I',    'D' },    /* G */
    { 'D',    'H' },    /* H */
    { 'C',    'U' },    /* I */
    { 'H',    'N' },    /* J */
    { 'T',    'E' },    /* K */
    { 'N',    'I' },    /* L */
    { 'M',    'M' },    /* M */
    { 'B',    'K' },    /* N */
    { 'R',    'Y' },    /* O */
    { 'L',    ':' },    /* P */
    { '"',    'Q' },    /* Q */
    { 'P',    'P' },    /* R */
    { 'O',    'R' },    /* S */
    { 'Y',    'G' },    /* T */
    { 'G',    'L' },    /* U */
    { 'K',    'V' },    /* V */
    { '<',    'W' },    /* W */
    { 'Q',    'X' },    /* X */
    { 'F',    'J' },    /* Y */
    { ':',    'Z' },    /* Z */
    { '/',    '[' },    /* [ */
    { '\\',    '\\' },    /* \ */
    { '=',    ']' },    /* ] */
    { '^',    '^' },    /* ^ */
    { '{',    '_' },    /* _ */
    { '`',    '`' },    /* ` */
    { 'a',    'a' },    /* a */
    { 'x',    'b' },    /* b */
    { 'j',    'c' },    /* c */
    { 'e',    's' },    /* d */
    { '.',    'f' },    /* e */
    { 'u',    't' },    /* f */
    { 'i',    'd' },    /* g */
    { 'd',    'h' },    /* h */
    { 'c',    'u' },    /* i */
    { 'h',    'n' },    /* j */
    { 't',    'e' },    /* k */
    { 'n',    'i' },    /* l */
    { 'm',    'm' },    /* m */
    { 'b',    'k' },    /* n */
    { 'r',    'y' },    /* o */
    { 'l',    ';' },    /* p */
    { '\'',    'q' },    /* q */
    { 'p',    'p' },    /* r */
    { 'o',    'r' },    /* s */
    { 'y',    'g' },    /* t */
    { 'g',    'l' },    /* u */
    { 'k',    'v' },    /* v */
    { ',',    'w' },    /* w */
    { 'q',    'x' },    /* x */
    { 'f',    'j' },    /* y */
    { ';',    'z' },    /* z */
    { '?',    '{' },    /* { */
    { '|',    '|' },    /* | */
    { '+',    '}' },    /* } */
    { '~',    '~' },    /* ~ */
    };

    if (layout < 0 || layout > 1) {
        return 0;
    } else if (keyval < '!' || keyval > '~') {
        return 0;
    } else {
        return table[keyval - '!'][layout];
    }
}

static gboolean
ibus_hangul_engine_flush_timeout (gpointer user_data)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) user_data;

    // 상용구 모드로 들어간 상태에서 시간이 지나 자동 flush 될 때, 
    // 상용구 모드 글쇠값을 뿌려주고 상용구 모드에서 나간다.
    if (previous_keyval == autotext_mode_key) {
        previous_keyval = 0;
        IBusText *text;
        text = ibus_text_new_from_unichar ((gunichar)autotext_mode_key);
        ibus_engine_commit_text ((IBusEngine *)hangul, text);
    }

    ibus_hangul_engine_flush(hangul);
    
    key_event_timeout_id = 0;

    return FALSE;
}

static void
ibus_hangul_engine_flush_timeout_on (IBusHangulEngine *hangul)
{
    if (enable_preedit_flush) {
        if (key_event_timeout_id == 0) {
            key_event_timeout_id = g_timeout_add (preedit_flush_time, 
                                                        (GSourceFunc)ibus_hangul_engine_flush_timeout, 
                                                        hangul);
        }
    }
}

static void
ibus_hangul_engine_flush_timeout_off (void)
{
    if (key_event_timeout_id != 0) {
        g_source_remove(key_event_timeout_id);
        key_event_timeout_id = 0;
    }
}

static HanjaList*
ibus_hangul_engine_lookup_autotext_table (const gchar *key)
{
    HanjaList* list = NULL;

    if (table_autotext != NULL) {
        list = hanja_table_match_exact (table_autotext, key);
    }

    return list;
}

static char * 
ibus_hangul_engine_get_autotext (const gchar *key)
{
    HanjaList *list_autotext =
                                ibus_hangul_engine_lookup_autotext_table (key);
    if (list_autotext != NULL) {
        gchar *value = g_strdup(hanja_list_get_nth_value (list_autotext, 0));

        hanja_list_delete (list_autotext);
        list_autotext = NULL;
        
        return value;
    }

    return NULL;
}

static gboolean
ibus_hangul_engine_process_autotext (IBusEngine     *engine,
                                                                guint           keyval)
{
    IBusHangulEngine *hangul = (IBusHangulEngine *) engine;

    IBusText *text;
    gchar key[2];
    key[0] = (gchar)keyval;
    key[1] = '\0';

    if (previous_keyval == autotext_mode_key) {// 상용구를 처리한다
        previous_keyval = 0;

        /* clear preedit text before commit */
        ibus_hangul_engine_clear_preedit_text (hangul);

        gchar *autotext_string =
                                        ibus_hangul_engine_get_autotext (key);

        if (autotext_string != NULL) {
            // 상용구가 있으면 넣어두었던 상용구 모드 글쇠값을 지운다
            gboolean retval;
            retval = hangul_ic_backspace (hangul->context);
            if (!retval) {
                guint preedit_len = ustring_length (hangul->preedit);
                if (preedit_len > 0) {
                    ustring_erase (hangul->preedit, preedit_len - 1, 1);
                }
            }
            // 상용구를 넣는다
            text = ibus_text_new_from_string (autotext_string);
            ibus_engine_commit_text (engine, text);

            hangul_ic_reset (hangul->context);
            ibus_hangul_engine_update_preedit_text (hangul);

            g_free(autotext_string);
            autotext_string = NULL;

            return TRUE; // 상용구 처리
        } else {// 상용구가 없으면 상용구 모드 글쇠값을 뿌려준다
            text = ibus_text_new_from_unichar ((gunichar)autotext_mode_key);
            ibus_engine_commit_text (engine, text);

            ibus_hangul_engine_update_preedit_text (hangul);
        }
    } else if (keyval == autotext_mode_key) {// 상용구 모드로 들어간다
        previous_keyval = autotext_mode_key;

        // 조합하던 글자를 뿌린다
        const ucschar *commit_string;
        commit_string = hangul_ic_get_commit_string (hangul->context);

        if (commit_string != NULL && commit_string[1] != 0) {
            /* clear preedit text before commit */
            ibus_hangul_engine_clear_preedit_text (hangul);

            text = ibus_text_new_from_unichar (commit_string[0]);
            ibus_engine_commit_text (engine, text);

            ibus_hangul_engine_update_preedit_text (hangul);
        }
        // 상용구 모드 글쇠값을 넣어둔다
        text = ibus_text_new_from_unichar ((gunichar)autotext_mode_key);
        ibus_engine_update_preedit_text_with_mode ((IBusEngine *)hangul,
                                                   text,
                                                   ibus_text_get_length (text),
                                                   TRUE,
                                                   IBUS_ENGINE_PREEDIT_COMMIT);

        return TRUE; // 상용구 모드 글쇠 처리
    }

    return FALSE;
}
