/***************************************************************************
* Copyright (C) 2010~2012 by CSSlayer *
* *
* 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "config.h"
#include <gtk/gtk.h>
#include <libintl.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcitx-config/fcitx-config.h>
#include <fcitx-utils/uthash.h>
#include <fcitx-config/hotkey.h>
#include <fcitx-config/xdg.h>
#include <fcitx/addon.h>
#include "config_widget.h"
#include "keygrab.h"
#include "sub_config_widget.h"
#include "configdesc.h"
#define _(s) gettext(s)
#define D_(d, x) dgettext (d, x)
#define RoundColor(c) ((c)>=0?((c)<=255?c:255):0)
G_DEFINE_TYPE(FcitxConfigWidget, fcitx_config_widget, GTK_TYPE_VBOX)
typedef struct {
int i;
FcitxConfigWidget* widget;
GtkWidget* table;
} HashForeachContext;
enum {
PROP_0,
PROP_CONFIG_DESC,
PROP_PREFIX,
PROP_NAME,
PROP_SUBCONFIG
};
static void
fcitx_config_widget_set_property(GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void sync_filter(FcitxGenericConfig* gconfig, FcitxConfigGroup *group, FcitxConfigOption *option, void *value, FcitxConfigSync sync, void *arg);
static void set_none_font_clicked(GtkWidget *button, gpointer arg);
static void hash_foreach_cb(gpointer key,
gpointer value,
gpointer user_data);
static void
fcitx_config_widget_setup_ui(FcitxConfigWidget *self);
static void
fcitx_config_widget_finalize(GObject *object);
static void
fcitx_config_widget_class_init(FcitxConfigWidgetClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gobject_class->set_property = fcitx_config_widget_set_property;
gobject_class->finalize = fcitx_config_widget_finalize;
g_object_class_install_property(gobject_class,
PROP_CONFIG_DESC,
g_param_spec_pointer("cfdesc",
"Configuration Description",
"Configuration Description for this widget",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property(gobject_class,
PROP_PREFIX,
g_param_spec_string("prefix",
"Prefix of path",
"Prefix of configuration path",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property(gobject_class,
PROP_NAME,
g_param_spec_string("name",
"File name",
"File name of configuration file",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property(gobject_class,
PROP_SUBCONFIG,
g_param_spec_string("subconfig",
"subconfig",
"subconfig",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}
static void
fcitx_config_widget_init(FcitxConfigWidget *self)
{
}
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;
FcitxConfigOptionDesc2 *codesc2 =
(FcitxConfigOptionDesc2*)codesc;
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);
if (codesc2->longDesc && *codesc2->longDesc) {
const char *tooltip = D_(cfdesc->domain,
codesc2->longDesc);
gtk_widget_set_tooltip_text(GTK_WIDGET(label),
tooltip);
gtk_widget_set_tooltip_text(GTK_WIDGET(inputWidget),
tooltip);
}
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);
}
FcitxConfigWidget*
fcitx_config_widget_new(FcitxConfigFileDesc* cfdesc, const gchar* prefix, const gchar* name, const char* subconfig)
{
FcitxConfigWidget* widget =
g_object_new(FCITX_TYPE_CONFIG_WIDGET,
"cfdesc", cfdesc,
"prefix", prefix,
"name", name,
"subconfig", subconfig,
NULL);
fcitx_config_widget_setup_ui(widget);
return widget;
}
static void
fcitx_config_widget_set_property(GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FcitxConfigWidget* config_widget = FCITX_CONFIG_WIDGET(gobject);
switch (prop_id) {
case PROP_CONFIG_DESC:
config_widget->cfdesc = g_value_get_pointer(value);
break;
case PROP_PREFIX:
if (config_widget->prefix)
g_free(config_widget->prefix);
config_widget->prefix = g_strdup(g_value_get_string(value));
break;
case PROP_NAME:
if (config_widget->name)
g_free(config_widget->name);
config_widget->name = g_strdup(g_value_get_string(value));
break;
case PROP_SUBCONFIG:
if (config_widget->parser)
sub_config_parser_free(config_widget->parser);
config_widget->parser = sub_config_parser_new(g_value_get_string(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
break;
}
}
static void set_none_font_clicked(GtkWidget *button, gpointer arg)
{
gtk_font_button_set_font_name(GTK_FONT_BUTTON(arg), "");
}
void sync_filter(FcitxGenericConfig* gconfig, FcitxConfigGroup *group, FcitxConfigOption *option, void *value, FcitxConfigSync sync, void *arg)
{
FcitxConfigOptionDesc *codesc = option->optionDesc;
if (!codesc)
return;
if (sync == Raw2Value) {
switch (codesc->type) {
case T_I18NString:
break;
case T_Integer: {
int value = atoi(option->rawValue);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(arg), value);
}
break;
case T_Color: {
int r = 0, g = 0, b = 0;
char scolor[9];
sscanf(option->rawValue, "%d %d %d", &r, &g, &b);
r = RoundColor(r);
g = RoundColor(g);
b = RoundColor(b);
snprintf(scolor, 8 , "#%02X%02X%02X", r, g, b);
GdkColor color;
gdk_color_parse(scolor, &color);
gtk_color_button_set_color(GTK_COLOR_BUTTON(arg), &color);
}
break;
case T_Boolean: {
gboolean bl;
if (strcmp(option->rawValue, "True") == 0)
bl = TRUE;
else
bl = FALSE;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(arg), bl);
}
break;
case T_Font: {
gtk_font_button_set_font_name(GTK_FONT_BUTTON(arg), option->rawValue);
}
break;
case T_Enum: {
FcitxConfigEnum* cenum = &codesc->configEnum;
int index = 0, i;
for (i = 0; i < cenum->enumCount; i++) {
if (strcmp(cenum->enumDesc[i], option->rawValue) == 0) {
index = i;
}
}
gtk_combo_box_set_active(GTK_COMBO_BOX(arg), index);
}
break;
case T_Hotkey: {
FcitxHotkey hotkey[2];
int j;
FcitxHotkeySetKey(option->rawValue, hotkey);
GArray *array = (GArray*) arg;
for (j = 0; j < 2; j ++) {
GtkWidget *button = g_array_index(array, GtkWidget*, j);
keygrab_button_set_key(KEYGRAB_BUTTON(button), hotkey[j].sym, hotkey[j].state);
if (hotkey[j].desc)
free(hotkey[j].desc);
}
}
break;
case T_File:
case T_Char:
case T_String: {
gtk_entry_set_text(GTK_ENTRY(arg), option->rawValue);
}
break;
}
} else {
if (codesc->type != T_I18NString && option->rawValue) {
free(option->rawValue);
option->rawValue = NULL;
}
switch (codesc->type) {
case T_I18NString:
break;
case T_Integer: {
int value;
value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(arg));
option->rawValue = g_strdup_printf("%d", value);
}
break;
case T_Color: {
int r = 0, g = 0, b = 0;
GdkColor color;
gtk_color_button_get_color(GTK_COLOR_BUTTON(arg), &color);
r = color.red / 256;
g = color.green / 256;
b = color.blue / 256;
r = RoundColor(r);
g = RoundColor(g);
b = RoundColor(b);
option->rawValue = g_strdup_printf("%d %d %d", r, g, b);
}
break;
case T_Boolean: {
gboolean bl;
bl = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(arg));
if (bl)
option->rawValue = strdup("True");
else
option->rawValue = strdup("False");
}
break;
case T_Font: {
const char *font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(arg));
PangoFontDescription *fontdesc = pango_font_description_from_string(font);
if (fontdesc) {
const char *family = pango_font_description_get_family(fontdesc);
if (family)
option->rawValue = strdup(family);
else
option->rawValue = strdup("");
pango_font_description_free(fontdesc);
} else
option->rawValue = strdup("");
}
break;
case T_Enum: {
FcitxConfigEnum* cenum = &codesc->configEnum;
int index = 0;
index = gtk_combo_box_get_active(GTK_COMBO_BOX(arg));
option->rawValue = strdup(cenum->enumDesc[index]);
}
break;
case T_Hotkey: {
GArray *array = (GArray*) arg;
GtkWidget *button;
guint key;
GdkModifierType mods;
char *strkey[2] = { NULL, NULL };
int j = 0, k = 0;
for (j = 0; j < 2 ; j ++) {
button = g_array_index(array, GtkWidget*, j);
keygrab_button_get_key(KEYGRAB_BUTTON(button), &key, &mods);
strkey[k] = FcitxHotkeyGetKeyString(key, mods);
if (strkey[k])
k ++;
}
if (strkey[1])
option->rawValue = g_strdup_printf("%s %s", strkey[0], strkey[1]);
else if (strkey[0]) {
option->rawValue = strdup(strkey[0]);
} else
option->rawValue = strdup("");
for (j = 0 ; j < k ; j ++)
free(strkey[j]);
}
break;
case T_File:
case T_Char:
case T_String: {
option->rawValue = strdup(gtk_entry_get_text(GTK_ENTRY(arg)));
}
break;
}
}
}
void fcitx_config_widget_response(
FcitxConfigWidget* config_widget,
ConfigWidgetAction action
)
{
if (!config_widget->cfdesc)
return;
if (action == CONFIG_WIDGET_DEFAULT) {
FcitxConfigResetConfigToDefaultValue(&config_widget->gconfig);
FcitxConfigBindSync(&config_widget->gconfig);
} else if (action == CONFIG_WIDGET_SAVE) {
FILE* fp = FcitxXDGGetFileUserWithPrefix(config_widget->prefix, config_widget->name, "w", NULL);
if (fp) {
FcitxConfigSaveConfigFileFp(fp, &config_widget->gconfig, config_widget->cfdesc);
fclose(fp);
GError* error;
gchar* argv[3];
argv[0] = EXEC_PREFIX "/bin/fcitx-remote";
argv[1] = "-r";
argv[2] = 0;
g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
}
}
}
void fcitx_config_widget_finalize(GObject *object)
{
FcitxConfigWidget* config_widget = FCITX_CONFIG_WIDGET(object);
g_free(config_widget->name);
g_free(config_widget->prefix);
sub_config_parser_free(config_widget->parser);
G_OBJECT_CLASS(fcitx_config_widget_parent_class)->finalize(object);
}
void hash_foreach_cb(gpointer key,
gpointer value,
gpointer user_data)
{
HashForeachContext* context = user_data;
FcitxConfigWidget* widget = context->widget;
FcitxSubConfigPattern* pattern = value;
FcitxSubConfig* subconfig = sub_config_new(key, pattern);
if (subconfig == NULL)
return;
int i = context->i;
GtkWidget* label = gtk_label_new(dgettext(widget->parser->domain, subconfig->name));
g_object_set(label, "xalign", 0.0f, NULL);
GtkWidget *inputWidget = GTK_WIDGET(fcitx_sub_config_widget_new(subconfig));
gtk_table_attach(GTK_TABLE(context->table), label, 0, 1, i, i + 1, GTK_FILL, GTK_SHRINK, 5, 5);
gtk_table_attach(GTK_TABLE(context->table), inputWidget, 1, 2, i, i + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 4);
context->i ++;
}
gboolean fcitx_config_widget_response_cb(GtkDialog *dialog,
gint response,
gpointer user_data)
{
if (response == GTK_RESPONSE_OK) {
FcitxConfigWidget* config_widget = (FcitxConfigWidget*) user_data;
fcitx_config_widget_response(config_widget, CONFIG_WIDGET_SAVE);
}
gtk_widget_destroy(GTK_WIDGET(dialog));
return FALSE;
}
GtkWidget* fcitx_config_dialog_new(FcitxAddon* addon, GtkWindow* parent)
{
gchar* config_desc_name = g_strdup_printf("%s.desc", addon->name);
FcitxConfigFileDesc* cfdesc = get_config_desc(config_desc_name);
g_free(config_desc_name);
gboolean configurable = (gboolean)(cfdesc != NULL || strlen(addon->subconfig) != 0);
if (!configurable) {
return NULL;
}
GtkWidget* dialog = gtk_dialog_new_with_buttons(addon->generalname,
parent,
GTK_DIALOG_MODAL,
GTK_STOCK_OK,
GTK_RESPONSE_OK,
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL,
NULL
);
gchar* config_file_name = g_strdup_printf("%s.config", addon->name);
FcitxConfigWidget* config_widget = fcitx_config_widget_new(cfdesc, "conf", config_file_name, addon->subconfig);
GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
gtk_box_pack_start(GTK_BOX(content_area), GTK_WIDGET(config_widget), TRUE, TRUE, 0);
g_free(config_file_name);
gtk_widget_set_size_request(GTK_WIDGET(config_widget), -1, 400);
g_signal_connect(dialog, "response",
G_CALLBACK(fcitx_config_widget_response_cb),
config_widget);
return dialog;
}