static void
fcitx_config_widget_setup_ui(FcitxConfigWidget *self)
{
    FcitxConfigFileDesc* cfdesc = self->cfdesc;
    GtkWidget *cvbox = GTK_WIDGET(self);
    GtkWidget *configNotebook = gtk_notebook_new();
    gtk_box_pack_start(GTK_BOX(cvbox), configNotebook, TRUE, TRUE, 0);
    if (cfdesc) {
        bindtextdomain(cfdesc->domain, LOCALEDIR);
        bind_textdomain_codeset(cfdesc->domain, "UTF-8");

        FILE *fp;
        fp = FcitxXDGGetFileWithPrefix(self->prefix, self->name, "r", NULL);
        self->gconfig.configFile = FcitxConfigParseConfigFileFp(fp, cfdesc);

        FcitxConfigGroupDesc *cgdesc = NULL;
        FcitxConfigOptionDesc *codesc = NULL;
        for (cgdesc = cfdesc->groupsDesc;
                cgdesc != NULL;
                cgdesc = (FcitxConfigGroupDesc*)cgdesc->hh.next) {
            codesc = cgdesc->optionsDesc;
            if (codesc == NULL)
                continue;

            GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
            GtkWidget *table = gtk_table_new(2, HASH_COUNT(codesc), FALSE);
            GtkWidget *plabel = gtk_label_new(D_(cfdesc->domain, cgdesc->groupName));
            GtkWidget *scrollwnd = gtk_scrolled_window_new(NULL, NULL);

            gtk_container_set_border_width(GTK_CONTAINER(table), 0);
            gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwnd), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
            gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrollwnd), table);
            gtk_box_pack_start(GTK_BOX(hbox), scrollwnd, TRUE, TRUE, 0);
            gtk_notebook_append_page(GTK_NOTEBOOK(configNotebook),
                                     hbox,
                                     plabel);

            int i = 0;
            for (; codesc != NULL;
                    codesc = (FcitxConfigOptionDesc*)codesc->hh.next, i++) {
                const char *s;
                if (codesc->desc && strlen(codesc->desc) != 0)
                    s = D_(cfdesc->domain, codesc->desc);
                else
                    s = D_(cfdesc->domain, codesc->optionName);

                GtkWidget *inputWidget = NULL;
                void *argument = NULL;

                switch (codesc->type) {
                case T_Integer:
                    inputWidget = gtk_spin_button_new_with_range(-1.0, 10000.0, 1.0);
                    argument = inputWidget;
                    break;
                case T_Color:
                    inputWidget = gtk_color_button_new();
                    argument = inputWidget;
                    break;
                case T_Boolean:
                    inputWidget = gtk_check_button_new();
                    argument = inputWidget;
                    break;
                case T_Font: {
                    inputWidget = gtk_hbox_new(FALSE, 0);
                    argument = gtk_font_button_new();
                    GtkWidget *button = gtk_button_new_with_label(_("Clear font setting"));
                    gtk_box_pack_start(GTK_BOX(inputWidget), argument, TRUE, TRUE, 0);
                    gtk_box_pack_start(GTK_BOX(inputWidget), button, FALSE, FALSE, 0);
                    gtk_font_button_set_use_size(GTK_FONT_BUTTON(argument), FALSE);
                    gtk_font_button_set_show_size(GTK_FONT_BUTTON(argument), FALSE);
                    g_signal_connect(G_OBJECT(button), "clicked", (GCallback) set_none_font_clicked, argument);
                }
                break;
                case T_Enum: {
                    int i;
                    FcitxConfigEnum *e = &codesc->configEnum;
#if GTK_CHECK_VERSION(2, 24, 0)
                    inputWidget = gtk_combo_box_text_new();
                    for (i = 0; i < e->enumCount; i ++) {
                        gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(inputWidget), D_(cfdesc->domain, e->enumDesc[i]));
                    }
#else
                    inputWidget = gtk_combo_box_new_text();
                    for (i = 0; i < e->enumCount; i ++)
                    {
                        gtk_combo_box_append_text(GTK_COMBO_BOX(inputWidget), D_(cfdesc->domain, e->enumDesc[i]));
                    }
#endif
                    argument = inputWidget;
                }
                break;
                case T_Hotkey: {
                    GtkWidget *button[2];
                    button[0] = keygrab_button_new();
                    button[1] = keygrab_button_new();
                    inputWidget = gtk_hbox_new(FALSE, 0);
                    gtk_box_pack_start(GTK_BOX(inputWidget), button[0], FALSE, TRUE, 0);
                    gtk_box_pack_start(GTK_BOX(inputWidget), button[1], FALSE, TRUE, 0);
                    argument = g_array_new(FALSE, FALSE, sizeof(void*));
                    g_array_append_val(argument, button[0]);
                    g_array_append_val(argument, button[1]);
                }
                break;
                case T_File:
                case T_Char:
                case T_String:
                    inputWidget = gtk_entry_new();
                    argument = inputWidget;
                    break;
                default:
                    break;
                }

                if (inputWidget) {
                    GtkWidget* label = gtk_label_new(s);
                    g_object_set(label, "xalign", 0.0f, NULL);
                    gtk_table_attach(GTK_TABLE(table), label, 0, 1, i, i + 1, GTK_FILL, GTK_SHRINK, 5, 5);
                    gtk_table_attach(GTK_TABLE(table), inputWidget, 1, 2, i, i + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 4);
                    FcitxConfigBindValue(self->gconfig.configFile, cgdesc->groupName, codesc->optionName, NULL, sync_filter, argument);
                }
            }
        }

        FcitxConfigBindSync(&self->gconfig);
    }

    if (self->parser) {
        GHashTable* subconfigs = self->parser->subconfigs;
        if (g_hash_table_size(subconfigs) != 0) {
            GtkWidget *table = gtk_table_new(2, g_hash_table_size(subconfigs), FALSE);
            GtkWidget *plabel = gtk_label_new(_("Other"));
            GtkWidget *scrollwnd = gtk_scrolled_window_new(NULL, NULL);
            GtkWidget *viewport = gtk_viewport_new(NULL, NULL);

            gtk_container_set_border_width(GTK_CONTAINER(table), 4);
            gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwnd), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
            gtk_container_add(GTK_CONTAINER(scrollwnd), viewport);
            gtk_container_add(GTK_CONTAINER(viewport), table);
            gtk_notebook_append_page(GTK_NOTEBOOK(configNotebook),
                                     scrollwnd,
                                     plabel);

            HashForeachContext context;
            context.i = 0;
            context.table = table;
            context.widget = self;
            g_hash_table_foreach(subconfigs, hash_foreach_cb, &context);
        }
    }

    gtk_widget_set_size_request(configNotebook, 500, -1);
    gtk_notebook_set_scrollable(GTK_NOTEBOOK(configNotebook), TRUE);
}
static void
fcitx_config_widget_create_option_widget(
    FcitxConfigWidget *self,
    FcitxConfigGroupDesc* cgdesc,
    FcitxConfigOptionDesc* codesc,
    char** label,
    char** tooltip,
    GtkWidget** inputWidget,
    void** newarg)
{
    FcitxConfigFileDesc* cfdesc = self->cfdesc;
    FcitxConfigOptionDesc2* codesc2 = (FcitxConfigOptionDesc2*) codesc;
    void* oldarg = NULL;
    void* argument = NULL;
    char* name = g_strdup_printf("%s/%s", cgdesc->groupName, codesc->optionName);
    oldarg = g_hash_table_lookup(self->argmap, name);

    if (codesc->desc && strlen(codesc->desc) != 0) {
        *label = strdup(D_(cfdesc->domain, codesc->desc));
    } else {
        *label = strdup(D_(cfdesc->domain, codesc->optionName));
    }

    if (codesc2->longDesc && codesc2->longDesc[0]) {
        *tooltip = strdup(D_(cfdesc->domain, codesc2->longDesc));
    }

    switch (codesc->type) {
    case T_Integer:
        *inputWidget = gtk_spin_button_new_with_range(
            codesc2->constrain.integerConstrain.min,
            codesc2->constrain.integerConstrain.max,
            1.0);
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        if (oldarg) {
            g_object_bind_property(*inputWidget, "value", oldarg, "value", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(*inputWidget, "notify::value", (GCallback) _fcitx_config_widget_changed, self);
            argument = *inputWidget;
        }
        break;
    case T_Color:
        *inputWidget = gtk_color_button_new();
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        if (oldarg) {
            g_object_bind_property(*inputWidget, "color", oldarg, "color", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(*inputWidget, "notify::color", (GCallback) _fcitx_config_widget_changed, self);
            argument = *inputWidget;
        }
        break;
    case T_Boolean:
        *inputWidget = gtk_check_button_new();
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        if (oldarg) {
            g_object_bind_property(*inputWidget, "active", oldarg, "active", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(*inputWidget, "notify::active", (GCallback) _fcitx_config_widget_changed, self);
            argument = *inputWidget;
        }
        break;
    case T_Font: {
        *inputWidget = gtk_grid_new();
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        GtkWidget* arg = gtk_font_button_new();
        GtkWidget *button = gtk_button_new_with_label(_("Clear font setting"));
        g_object_set(arg, "hexpand", TRUE, NULL);
        gtk_grid_attach(GTK_GRID(*inputWidget), arg, 0, 0, 1, 1);
        gtk_grid_attach(GTK_GRID(*inputWidget), button, 1, 0, 2, 1);
        gtk_font_button_set_use_size(GTK_FONT_BUTTON(arg), FALSE);
        gtk_font_button_set_show_size(GTK_FONT_BUTTON(arg), FALSE);
        g_signal_connect(G_OBJECT(button), "clicked", (GCallback) set_none_font_clicked, arg);
        if (oldarg) {
            g_object_bind_property(arg, "font-name", oldarg, "font-name", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(arg, "notify::font-name", (GCallback) _fcitx_config_widget_changed, self);
            argument = arg;
        }
    }
    break;
    case T_Enum: {
        int i;
        FcitxConfigEnum *e = &codesc->configEnum;
        *inputWidget = gtk_combo_box_text_new();
        for (i = 0; i < e->enumCount; i ++) {
            gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(*inputWidget), D_(cfdesc->domain, e->enumDesc[i]));
        }
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        if (oldarg) {
            g_object_bind_property(*inputWidget, "active", oldarg, "active", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(*inputWidget, "notify::active", (GCallback) _fcitx_config_widget_changed, self);
            argument = *inputWidget;
        }
    }
    break;
    case T_Hotkey: {
        GtkWidget *button[2];
        button[0] = keygrab_button_new();
        button[1] = keygrab_button_new();
        *inputWidget = gtk_grid_new();
        gtk_grid_attach(GTK_GRID(*inputWidget), button[0], 0, 0, 1, 1);
        gtk_grid_attach(GTK_GRID(*inputWidget), button[1], 1, 0, 2, 1);
        g_object_set(G_OBJECT(button[0]), "hexpand", TRUE, NULL);
        g_object_set(G_OBJECT(button[1]), "hexpand", TRUE, NULL);
        if (oldarg) {
            GArray* array = oldarg;
            int j;
            for (j = 0; j < 2; j ++) {
                GtkWidget *oldbutton = g_array_index(array, GtkWidget*, j);
                g_signal_connect(oldbutton, "changed", (GCallback) sync_hotkey, button[j]);
                g_signal_connect(button[j], "changed", (GCallback) sync_hotkey, oldbutton);
            }
        }
        else {
            argument = g_array_new(FALSE, FALSE, sizeof(void*));
            int j;
            for (j = 0; j < 2; j ++) {
                g_signal_connect(button[j], "changed", (GCallback) _fcitx_config_widget_hotkey_changed, self);
                g_array_append_val(argument, button[j]);
            }
        }
    }
    break;
    case T_File:
    case T_Char:
    case T_String:
        *inputWidget = gtk_entry_new();
        g_object_set(*inputWidget, "hexpand", TRUE, NULL);
        if (oldarg) {
            g_object_bind_property(*inputWidget, "text", oldarg, "text", G_BINDING_BIDIRECTIONAL);
        } else {
            g_signal_connect(*inputWidget, "notify::text", (GCallback) _fcitx_config_widget_changed, self);
            argument = *inputWidget;
        }
        break;
    default:
        break;
    }