GTK+ plugin: implementation of edit window. No save available yet, just look.
authorAndriy Grytsenko <andrej@rep.kiev.ua>
Sun, 30 Oct 2016 16:00:24 +0000 (18:00 +0200)
committerAndriy Grytsenko <andrej@rep.kiev.ua>
Sun, 30 Oct 2016 16:02:30 +0000 (18:02 +0200)
.gitignore
configure.ac
plugins/Makefile.am
plugins/gtk/edit.c [new file with mode: 0644]
plugins/gtk/edit.h [new file with mode: 0644]
plugins/gtk/gtk.c
plugins/openbox/openbox.c
po/lxhotkey.pot
src/lxhotkey.c
src/lxhotkey.h

index 8a826da..f920765 100644 (file)
@@ -21,6 +21,7 @@ po/Makefile.in.in
 po/POTFILES
 .deps/
 .libs/
+.dirstamp
 *~
 *.o
 *.lo
index fde3435..bc011f7 100644 (file)
@@ -88,6 +88,18 @@ AC_SUBST(GTK_LIBS)
 dnl Test for libunistring for correct UTF-8 printf
 AC_CHECK_LIB(unistring, ulc_fprintf)
 
+dnl Supress extra linking
+AC_MSG_CHECKING([whether $LD accepts --as-needed])
+case `$LD --as-needed -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+    LDFLAGS="$LDFLAGS -Wl,--as-needed"
+    AC_MSG_RESULT([yes])
+    ;;
+*)
+    AC_MSG_RESULT([no])
+    ;;
+esac
+
 dnl Fix invalid sysconfdir when --prefix=/usr
 if test `eval "echo $sysconfdir"` = /usr/etc
 then
index 7d79856..f1203de 100644 (file)
@@ -22,7 +22,7 @@ ob_la_SOURCES = openbox/openbox.c
 ob_la_LIBADD = -lfm-extra
 
 # GTK module
-gtk_la_SOURCES = gtk/gtk.c
+gtk_la_SOURCES = gtk/gtk.c gtk/edit.c
 gtk_la_CFLAGS = $(GTK_CFLAGS)
 gtk_la_LIBADD = $(GTK_LIBS)
 
@@ -37,7 +37,8 @@ desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
 @INTLTOOL_DESKTOP_RULE@
 
 EXTRA_DIST= \
-       gtk/lxhotkey-gtk.desktop.in gtk/lxhotkey-gtk.desktop
+       gtk/lxhotkey-gtk.desktop.in gtk/lxhotkey-gtk.desktop \
+       gtk/edit.h
 
 install-exec-hook:
        rm -f $(DESTDIR)$(pkglibdir)/*.la
diff --git a/plugins/gtk/edit.c b/plugins/gtk/edit.c
new file mode 100644 (file)
index 0000000..706c277
--- /dev/null
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *
+ * This file is a part of LXHotkey project.
+ *
+ * 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
+
+#define WANT_OPTIONS_EQUAL
+
+#include "lxhotkey.h"
+#include "edit.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#if !GTK_CHECK_VERSION(2, 21, 0)
+# define GDK_KEY_Tab    GDK_Tab
+#endif
+
+enum {
+    EDIT_MODE_NONE,
+    EDIT_MODE_ADD, /* add action */
+    EDIT_MODE_EDIT, /* change selected */
+    EDIT_MODE_OPTION /* add suboption */
+};
+
+static const LXHotkeyAttr *find_template_for_option(GtkTreeModel *model,
+                                                    GtkTreeIter *iter,
+                                                    const GList *t_list)
+{
+    const LXHotkeyAttr *opt, *tmpl;
+
+    gtk_tree_model_get(model, iter, 2, &opt, -1);
+    while (t_list)
+    {
+        tmpl = t_list->data;
+        if (g_strcmp0(tmpl->name, opt->name) == 0)
+            return tmpl;
+        t_list = t_list->next;
+    }
+    return NULL;
+}
+
+static const GList *get_options_from_template(const LXHotkeyAttr *tmpl, PluginData *data)
+{
+    if (tmpl == NULL)
+        return NULL;
+    else if (tmpl->has_actions)
+        return data->edit_template;
+    else
+        return tmpl->subopts;
+}
+
+static const GList *get_parent_template_list(GtkTreeModel *model, GtkTreeIter *iter,
+                                             PluginData *data)
+{
+    const GList *tmpl_list;
+    GtkTreeIter parent_iter;
+    const LXHotkeyAttr *parent;
+
+    if (!gtk_tree_model_iter_parent(model, &parent_iter, iter))
+        return data->edit_template;
+    /* get list for parent of parent first - recursion is here */
+    tmpl_list = get_parent_template_list(model, &parent_iter, data);
+    /* now find parent in that list */
+    parent = find_template_for_option(model, &parent_iter, tmpl_list);
+    return get_options_from_template(parent, data);
+}
+
+static void fill_edit_frame(PluginData *data, const LXHotkeyAttr *opt,
+                            const GList *subopts, const GList *exempt)
+{
+    GtkListStore *names_store;
+    const LXHotkeyAttr *sub;
+    const GList *l;
+    int i = 0;
+
+    names_store = GTK_LIST_STORE(gtk_list_store_new(3, G_TYPE_STRING, /* description */
+                                                       G_TYPE_STRING, /* name */
+                                                       G_TYPE_POINTER)); /* template */
+    while (subopts)
+    {
+        sub = subopts->data;
+        /* ignore existing opts */
+        for (l = exempt; l; l = l->next)
+            if (strcmp(sub->name, ((LXHotkeyAttr *)l->data)->name) == 0)
+                break;
+        if (l == NULL)
+            gtk_list_store_insert_with_values(names_store, NULL, i++, 0, _(sub->name),
+                                                                      1, sub->name,
+                                                                      2, sub, -1);
+        subopts = subopts->next;
+    }
+    gtk_combo_box_set_model(data->edit_actions, GTK_TREE_MODEL(names_store));
+    g_object_unref(names_store);
+    gtk_combo_box_set_active(data->edit_actions, 0);
+    /* values box will be set by changing active callback */
+    gtk_widget_set_visible(GTK_WIDGET(data->edit_actions), opt == NULL); /* visible on add */
+    gtk_widget_set_visible(data->edit_option_name, opt != NULL); /* visible on edit */
+    if (opt)
+        gtk_label_set_text(GTK_LABEL(data->edit_option_name), _(opt->name));
+}
+
+static void update_edit_toolbar(PluginData *data)
+{
+    const LXHotkeyAttr *opt, *tmpl;
+    const GList *tmpl_list;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+
+    /* update AddOption button -- exec only */
+    if (gtk_action_get_visible(data->add_option_button))
+    {
+        /* make not clickable if no more options to add */
+        gtk_action_set_sensitive(data->add_option_button,
+                g_list_length((GList *)data->edit_template) != g_list_length(data->edit_options_copy));
+    }
+    /* update Remove button */
+    if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
+                                         &model, &iter))
+    {
+        gtk_action_set_sensitive(data->rm_option_button, FALSE);
+        gtk_action_set_sensitive(data->edit_option_button, FALSE);
+        gtk_action_set_sensitive(data->add_suboption_button, FALSE);
+        return;
+    }
+    gtk_action_set_sensitive(data->rm_option_button, TRUE);
+    gtk_tree_model_get(model, &iter, 2, &opt, -1);
+    tmpl_list = get_parent_template_list(model, &iter, data);
+    while (tmpl_list)
+    {
+        tmpl = tmpl_list->data;
+        if (g_strcmp0(tmpl->name, opt->name) == 0)
+            break;
+        tmpl_list = tmpl_list->next;
+    }
+    if (G_UNLIKELY(tmpl_list == NULL))
+    {
+        /* option isn't supported, probably deprecated one */
+        gtk_action_set_sensitive(data->edit_option_button, FALSE);
+        gtk_action_set_sensitive(data->add_suboption_button, FALSE);
+        return;
+    }
+    gtk_action_set_sensitive(data->edit_option_button,
+                             (tmpl->subopts == NULL || tmpl->has_value));
+    gtk_action_set_sensitive(data->add_suboption_button,
+                             g_list_length(tmpl->subopts) != g_list_length(opt->subopts));
+}
+
+static void on_cancel(GtkAction *act, PluginData *data)
+{
+    gtk_widget_destroy(GTK_WIDGET(data->edit_window));
+}
+
+static void on_save(GtkAction *act, PluginData *data)
+{
+    // a) if actions list or command line changed then remove old binding and add new
+    // b) else if keys changed then update binding
+    // c) else skip (d) and (e)
+    // d) gtk_action_set_sensitive(data->save_action, TRUE)
+    // e) _main_refresh(data)
+    gtk_widget_destroy(GTK_WIDGET(data->edit_window));
+}
+
+static void on_add_action(GtkAction *act, PluginData *data)
+{
+    data->edit_mode = EDIT_MODE_ADD;
+    /* fill frame with empty data, set choices from data->edit_template, hide value */
+    fill_edit_frame(data, NULL, data->edit_template, NULL);
+    gtk_widget_hide(GTK_WIDGET(data->edit_values));
+    gtk_widget_hide(GTK_WIDGET(data->edit_value));
+    gtk_widget_show(data->edit_frame);
+    gtk_widget_grab_focus(data->edit_frame);
+}
+
+static void on_add_option(GtkAction *act, PluginData *data)
+{
+    data->edit_mode = EDIT_MODE_ADD;
+    /* fill frame with empty data, set choices from data->edit_template */
+    fill_edit_frame(data, NULL, data->edit_template, data->edit_options_copy);
+    gtk_widget_show(data->edit_frame);
+    gtk_widget_grab_focus(data->edit_frame);
+}
+
+static void on_remove(GtkAction *act, PluginData *data)
+{
+    //find and remove option from data->edit_options_copy
+    //remove selected row from model
+}
+
+static void on_edit(GtkAction *act, PluginData *data)
+{
+    const LXHotkeyAttr *opt;
+    const GList *tmpl_list;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GList single = { .prev = NULL, .next = NULL };
+
+    if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
+                                         &model, &iter))
+        /* no item selected */
+        return;
+    /* name - only current from selection */
+    gtk_tree_model_get(model, &iter, 2, &opt, -1);
+    /* values - from template */
+    tmpl_list = get_parent_template_list(model, &iter, data);
+    if (tmpl_list == data->edit_template) /* it's action */
+        return;
+    single.data = (gpointer)find_template_for_option(model, &iter, tmpl_list);
+    if (single.data == NULL)
+    {
+        g_warning("no template found for option '%s'", opt->name);
+        return;
+    }
+    data->edit_mode = EDIT_MODE_EDIT;
+    /* fill frame from selection */
+    fill_edit_frame(data, opt, &single, NULL);
+    gtk_widget_show(data->edit_frame);
+    gtk_widget_grab_focus(data->edit_frame);
+}
+
+static void on_add_suboption(GtkAction *act, PluginData *data)
+{
+    const LXHotkeyAttr *opt, *tmpl;
+    const GList *tmpl_list;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+
+    if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
+                                         &model, &iter))
+        /* no item selected */
+        return;
+
+    tmpl_list = get_parent_template_list(model, &iter, data);
+    tmpl = find_template_for_option(model, &iter, tmpl_list);
+    if (tmpl == NULL)
+        /* no options found */
+        return;
+
+    tmpl_list = get_options_from_template(tmpl, data);
+    gtk_tree_model_get(model, &iter, 2, &opt, -1);
+    data->edit_mode = EDIT_MODE_OPTION;
+    /* fill frame with empty data and set name choices from selection's subopts */
+    fill_edit_frame(data, NULL, tmpl_list, opt->subopts);
+    gtk_widget_show(data->edit_frame);
+    gtk_widget_grab_focus(data->edit_frame);
+}
+
+static const char edit_xml[] =
+"<toolbar>"
+    "<toolitem action='Cancel'/>"
+    "<toolitem action='Save'/>"
+    "<separator/>"
+    "<toolitem action='AddAction'/>"
+    "<toolitem action='AddOption'/>"
+    "<toolitem action='Remove'/>"
+    "<toolitem action='Change'/>"
+    "<separator/>"
+    "<toolitem action='AddSubOption'/>"
+    /* "<separator/>"
+    "<toolitem action='Help'/>" */
+"</toolbar>";
+
+static GtkActionEntry actions[] =
+{
+    { "Cancel", GTK_STOCK_CANCEL, NULL, NULL, N_("Discard changes"), G_CALLBACK(on_cancel) },
+    { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Accept changes"), G_CALLBACK(on_save) },
+    { "AddAction", GTK_STOCK_ADD, NULL, NULL, N_("Add an action"), G_CALLBACK(on_add_action) },
+    { "AddOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to this command"),
+                G_CALLBACK(on_add_option) },
+    { "Remove", GTK_STOCK_DELETE, NULL, "", N_("Remove selection"), G_CALLBACK(on_remove) },
+    { "Change", GTK_STOCK_EDIT, NULL, NULL, N_("Change selected option"), G_CALLBACK(on_edit) },
+    { "AddSubOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to selection"),
+                G_CALLBACK(on_add_suboption) }
+};
+
+/* Button for keybinding click - taken from LXPanel, simplified a bit */
+static void on_focus_in_event(GtkButton *test, GdkEvent *event, PluginData *data)
+{
+    gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(test)), TRUE, GDK_CURRENT_TIME);
+}
+
+static void on_focus_out_event(GtkButton *test, GdkEvent *event, PluginData *data)
+{
+    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+}
+
+static gboolean on_key_event(GtkButton *test, GdkEventKey *event, PluginData *data)
+{
+    GdkModifierType state;
+    char *text;
+
+    /* ignore Tab completely so user can leave focus */
+    if (event->keyval == GDK_KEY_Tab)
+        return FALSE;
+    /* request mods directly, event->state isn't updated yet */
+    gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(test)),
+                           NULL, NULL, &state);
+    /* special support for Win key, it doesn't work sometimes */
+    if ((state & GDK_SUPER_MASK) == 0 && (state & GDK_MOD4_MASK) != 0)
+        state |= GDK_SUPER_MASK;
+    state &= gtk_accelerator_get_default_mod_mask();
+    /* if mod key event then update test label and go */
+    if (event->is_modifier)
+    {
+        if (state != 0)
+        {
+            text = gtk_accelerator_get_label(0, state);
+            gtk_button_set_label(test, text);
+            g_free(text);
+        }
+        /* if no modifiers currently then show original state */
+        else
+            gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
+        return FALSE;
+    }
+    /* if not keypress query then ignore key press */
+    if (event->type != GDK_KEY_PRESS)
+        return FALSE;
+    /* update the label now */
+    text = gtk_accelerator_get_label(event->keyval, state);
+    gtk_button_set_label(test, text);
+    /* drop single printable and printable with single Shift, Ctrl, Alt */
+    if (event->length != 0 && (state == 0 || state == GDK_SHIFT_MASK ||
+                               state == GDK_CONTROL_MASK || state == GDK_MOD1_MASK))
+    {
+        GtkWidget* dlg;
+        dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+                                     _("Key combination '%s' cannot be used as"
+                                       " a global hotkey, sorry."), text);
+        g_free(text);
+        gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
+        gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
+        gtk_dialog_run(GTK_DIALOG(dlg));
+        gtk_widget_destroy(dlg);
+        gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
+        return FALSE;
+    }
+    g_free(text);
+    /* save new value now */
+    text = gtk_accelerator_name(event->keyval, state);
+    g_object_set_data_full(G_OBJECT(test), "accelerator_name", text, g_free);
+    return FALSE;
+}
+
+static GtkWidget *key_button_new(PluginData *data, const char *hotkey)
+{
+    GtkWidget *w;
+    char *label;
+    guint keyval = 0;
+    GdkModifierType state = 0;
+
+    if (hotkey)
+        gtk_accelerator_parse(hotkey, &keyval, &state);
+    label = gtk_accelerator_get_label(keyval, state);
+    w = gtk_button_new_with_label(label);
+    g_object_set_data_full(G_OBJECT(w), "original_label", label, g_free);
+    g_signal_connect(w, "focus-in-event", G_CALLBACK(on_focus_in_event), data);
+    g_signal_connect(w, "focus-out-event", G_CALLBACK(on_focus_out_event), data);
+    g_signal_connect(w, "key-press-event", G_CALLBACK(on_key_event), data);
+    g_signal_connect(w, "key-release-event", G_CALLBACK(on_key_event), data);
+    return w;
+}
+
+#if !GLIB_CHECK_VERSION(2, 34, 0)
+static GList *g_list_copy_deep(GList *list, GCopyFunc func, gpointer user_data)
+{
+    GList *copy = NULL;
+
+    while (list)
+    {
+        copy = g_list_prepend(copy, func(list->data, user_data));
+        list = list->next;
+    }
+    return g_list_reverse(copy);
+}
+#endif
+
+/* used by edit_action() to be able to edit */
+static GList *copy_options(GList *orig)
+{
+    GList *copy = NULL;
+
+    /* copy contents recursively */
+    while (orig)
+    {
+        LXHotkeyAttr *attr = g_slice_new(LXHotkeyAttr);
+        LXHotkeyAttr *attr_orig = orig->data;
+
+        attr->name = g_strdup(attr_orig->name);
+        attr->values = g_list_copy_deep(attr_orig->values, (GCopyFunc)g_strdup, NULL);
+        attr->subopts = copy_options(attr_orig->subopts);
+        attr->desc = g_strdup(attr_orig->desc);
+        attr->has_actions = attr_orig->has_actions;
+        copy = g_list_prepend(copy, attr);
+        orig = orig->next;
+    }
+    return g_list_reverse(copy);
+}
+
+#define free_options(acts) g_list_free_full(acts, (GDestroyNotify)option_free)
+static void option_free(LXHotkeyAttr *attr)
+{
+    g_free(attr->name);
+    g_list_free_full(attr->values, g_free);
+    free_options(attr->subopts);
+    g_free(attr->desc);
+    g_slice_free(LXHotkeyAttr, attr);
+}
+
+static void add_options_to_tree(GtkTreeStore *store, GtkTreeIter *parent_iter,
+                                GList *list)
+{
+    LXHotkeyAttr *opt;
+    GtkTreeIter iter;
+
+    while (list)
+    {
+        opt = list->data;
+        gtk_tree_store_insert_with_values(store, &iter, parent_iter, -1,
+                                          0, opt->name,
+                                          1, opt->values ? opt->values->data : NULL,
+                                          2, opt, -1);
+        if (opt->subopts)
+            add_options_to_tree(store, &iter, opt->subopts);
+        list = list->next;
+    }
+}
+
+static void update_options_tree(PluginData *data)
+{
+    GtkTreeStore *store = gtk_tree_store_new(3, G_TYPE_STRING, /* option name */
+                                                G_TYPE_STRING, /* option value */
+                                                G_TYPE_POINTER); /* LXHotkeyAttr */
+
+    add_options_to_tree(store, NULL, data->edit_options_copy);
+    gtk_tree_view_set_model(data->edit_tree, GTK_TREE_MODEL(store));
+    gtk_tree_view_expand_all(data->edit_tree);
+    g_object_unref(store);
+}
+
+#define edit_is_active(data) (data->edit_mode != EDIT_MODE_NONE)
+
+static void cancel_edit(PluginData *data)
+{
+    data->edit_mode = EDIT_MODE_NONE;
+    gtk_widget_hide(data->edit_frame);
+}
+
+static void on_selection_changed(GtkTreeSelection *selection, PluginData *data)
+{
+    if (edit_is_active(data))
+        //FIXME: ask confirmation and revert, is that possible?
+        cancel_edit(data);
+    //update toolbar buttons visibility
+    update_edit_toolbar(data);
+}
+
+static void on_option_changed(GtkComboBox *box, PluginData *data)
+{
+    const LXHotkeyAttr *opt, *tmpl;
+    const GList *values;
+    GtkTreeModel *model;
+    GtkListStore *values_store;
+    GtkTreeIter iter;
+    int i, sel;
+    gboolean is_action = FALSE;
+
+    /* g_debug("on_option_changed"); */
+    opt = NULL;
+    if (data->edit_mode == EDIT_MODE_ADD)
+        is_action = (data->current_page == data->acts);
+    else if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
+                                             &model, &iter))
+    {
+        gtk_tree_model_get(model, &iter, 2, &opt, -1);
+        if (data->current_page == data->acts)
+            is_action = (get_parent_template_list(model, &iter, data) == data->edit_template);
+    }
+    if (!gtk_combo_box_get_active_iter(box, &iter))
+        /* no item selected */
+        return;
+    gtk_tree_model_get(gtk_combo_box_get_model(box), &iter, 2, &tmpl, -1);
+    if (tmpl->has_actions || is_action ||
+        (tmpl->subopts != NULL && !tmpl->has_value))
+    {
+        /* either it's action, or option has suboptions instead of values */
+        gtk_widget_hide(data->edit_value_label);
+        gtk_widget_hide(GTK_WIDGET(data->edit_value));
+        gtk_widget_hide(GTK_WIDGET(data->edit_values));
+        gtk_widget_hide(data->edit_value_num);
+        gtk_widget_hide(data->edit_value_num_label);
+    }
+    else if ((values = tmpl->values) != NULL)
+    {
+        values_store = GTK_LIST_STORE(gtk_list_store_new(2, G_TYPE_STRING, /* description */
+                                                            G_TYPE_STRING)); /* value */
+        for (sel = 0, i = 0; values; values = values->next, i++)
+        {
+            gtk_list_store_insert_with_values(values_store, NULL, i, 0, _(values->data),
+                                                                     1, values->data, -1);
+            if (opt && opt->values && g_strcmp0(opt->values->data, values->data) == 0)
+                sel = i;
+        }
+        gtk_combo_box_set_model(data->edit_values, GTK_TREE_MODEL(values_store));
+        g_object_unref(values_store);
+        gtk_combo_box_set_active(data->edit_values, sel);
+        gtk_widget_show(data->edit_value_label);
+        gtk_widget_show(GTK_WIDGET(data->edit_values));
+        gtk_widget_hide(GTK_WIDGET(data->edit_value));
+    }
+    else
+    {
+        gtk_widget_show(data->edit_value_label);
+        gtk_widget_hide(data->edit_value_num);
+        gtk_widget_hide(data->edit_value_num_label);
+        gtk_widget_hide(GTK_WIDGET(data->edit_values));
+        gtk_widget_show(GTK_WIDGET(data->edit_value));
+        if (opt && opt->values)
+            gtk_entry_set_text(data->edit_value, opt->values->data);
+        else
+            gtk_entry_set_text(data->edit_value, "");
+    }
+}
+
+static void on_value_changed(GtkComboBox *box, PluginData *data)
+{
+    const char *value;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+
+    model = gtk_combo_box_get_model(box);
+    if (!gtk_combo_box_get_active_iter(box, &iter))
+        /* no value chosen */
+        goto _general;
+    gtk_tree_model_get(model, &iter, 1, &value, -1);
+    if (!value)
+        /* illegal really */
+        goto _general;
+    if (value[0] == '#')
+    {
+        /* if value is # then show data->edit_value_num */
+        gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
+                                  -1000.0, +1000.0);
+        gtk_widget_show(data->edit_value_num);
+        gtk_label_set_text(GTK_LABEL(data->edit_value_num_label), "");
+        gtk_widget_hide(data->edit_value_num_label);
+    }
+    else if (value[0] == '%')
+    {
+        /* if value is % then show data->edit_value_num with label "%" */
+        gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
+                                  0.0, +100.0);
+        gtk_widget_show(data->edit_value_num);
+        gtk_label_set_text(GTK_LABEL(data->edit_value_num_label), "%");
+        gtk_widget_show(data->edit_value_num_label);
+    }
+    else
+    {
+_general:
+        /* else hide both data->edit_value_num and label */
+        gtk_widget_hide(data->edit_value_num);
+        gtk_widget_hide(data->edit_value_num_label);
+    }
+}
+
+static void on_apply_button(GtkButton *btn, PluginData *data)
+{
+    //insert/update option in data->edit_options_copy
+    //add/update row in the model
+    //update_edit_toolbar(data);
+    cancel_edit(data);
+}
+
+static void on_cancel_button(GtkButton *btn, PluginData *data)
+{
+    cancel_edit(data);
+}
+
+/* free all allocated data */
+void _edit_cleanup(PluginData *data)
+{
+    if (data->edit_window)
+    {
+        cancel_edit(data);
+        g_object_remove_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
+        gtk_widget_destroy(GTK_WIDGET(data->edit_window));
+        data->edit_window = NULL;
+    }
+    if (data->edit_options_copy)
+    {
+        free_options(data->edit_options_copy);
+        data->edit_options_copy = NULL;
+    }
+}
+
+void _edit_action(PluginData *data, GError **error)
+{
+    LXHotkeyGlobal *act = NULL;
+    LXHotkeyApp *app = NULL;
+    const char *accel1 = NULL, *accel2 = NULL;
+    GtkBox *vbox, *xbox;
+    GtkUIManager *ui;
+    GtkActionGroup *act_grp;
+    GtkAccelGroup *accel_grp;
+    GtkWidget *widget, *align;
+    GtkToolbar *toolbar;
+    GtkCellRenderer *column;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    gboolean is_action = FALSE;
+
+    if (data->edit_window)
+    {
+        /* OOPS, another edit is still opened */
+        return;
+    }
+    /* do cleanup */
+    _edit_cleanup(data);
+    /* get a template */
+    if (data->current_page == data->acts)
+    {
+        if (data->cb->get_wm_actions == NULL) /* not available for edit */
+            return;
+        data->edit_template = data->cb->get_wm_actions(data->config, NULL);
+        //FIXME: test for error
+        is_action = TRUE;
+        if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->acts),
+                                            &model, &iter))
+            gtk_tree_model_get(model, &iter, 4, &act, -1);
+        if (act)
+        {
+            /* if there is a selection then copy its options */
+            data->edit_options_copy = copy_options(act->actions);
+            accel1 = act->accel1;
+            accel2 = act->accel2;
+        }
+    }
+    else
+    {
+        if (data->cb->get_app_options == NULL) /* not available for edit */
+            return;
+        data->edit_template = data->cb->get_app_options(data->config, NULL);
+        //FIXME: test for error
+        if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->apps),
+                                            &model, &iter))
+            gtk_tree_model_get(model, &iter, 3, &app, -1);
+        if (app)
+        {
+            /* if there is a selection then copy its options */
+            data->edit_options_copy = copy_options(app->options);
+            accel1 = app->accel1;
+            accel2 = app->accel2;
+        }
+    }
+
+    /* create a window with a GtkVBox inside */
+    data->edit_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+    gtk_window_set_default_size(data->edit_window, 240, 10);
+    gtk_window_set_transient_for(data->edit_window,
+                                 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(data->notebook))));
+    g_object_add_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
+    vbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
+
+    /* add the toolbar */
+    ui = gtk_ui_manager_new();
+    act_grp = gtk_action_group_new("Edit");
+    gtk_action_group_set_translation_domain(act_grp, NULL);
+    gtk_action_group_add_actions(act_grp, actions, G_N_ELEMENTS(actions), data);
+    accel_grp = gtk_ui_manager_get_accel_group(ui);
+    gtk_window_add_accel_group(GTK_WINDOW(data->edit_window), accel_grp);
+    gtk_ui_manager_insert_action_group(ui, act_grp, 0);
+    gtk_ui_manager_add_ui_from_string(ui, edit_xml, -1, NULL);
+    g_object_unref(act_grp);
+    widget = gtk_ui_manager_get_widget(ui, "/toolbar");
+    toolbar = GTK_TOOLBAR(widget);
+    //TODO: 'Change' -- also 2click and Enter
+    gtk_toolbar_set_icon_size(toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR);
+    gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS);
+    gtk_toolbar_set_show_arrow(toolbar, FALSE);
+    data->add_option_button = gtk_ui_manager_get_action(ui, "/toolbar/AddOption");
+    data->rm_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Remove");
+    data->edit_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Change");
+    data->add_suboption_button = gtk_ui_manager_get_action(ui, "/toolbar/AddSubOption");
+    gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
+
+    /* add frames for accel1 and accel2 */
+    xbox = (GtkBox *)gtk_hbox_new(TRUE, 0);
+    widget = gtk_frame_new(_("Hotkey 1"));
+    data->edit_key1 = key_button_new(data, accel1);
+    gtk_container_add(GTK_CONTAINER(widget), data->edit_key1);
+    gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
+    widget = gtk_frame_new(_("Hotkey 2"));
+    data->edit_key2 = key_button_new(data, accel2);
+    gtk_container_add(GTK_CONTAINER(widget), data->edit_key2);
+    gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
+    gtk_box_pack_start(vbox, GTK_WIDGET(xbox), FALSE, TRUE, 0);
+
+    /* add frame with all options */
+    widget = gtk_frame_new(NULL);
+    gtk_frame_set_shadow_type(GTK_FRAME(widget), GTK_SHADOW_IN);
+    xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
+    if (is_action)
+    {
+        align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+        gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Actions:")));
+        gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    }
+    else
+    {
+        /* for application add a GtkEntry for exec line */
+        align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+        gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Command line:")));
+        gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+        data->edit_exec = GTK_ENTRY(gtk_entry_new());
+        if (app)
+            gtk_entry_set_text(data->edit_exec, app->exec);
+        align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+        gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
+        gtk_container_add(GTK_CONTAINER(align), GTK_WIDGET(data->edit_exec));
+        gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+        align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+        gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Options:")));
+        gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    }
+    data->edit_tree = GTK_TREE_VIEW(gtk_tree_view_new());
+    gtk_box_pack_start(xbox, GTK_WIDGET(data->edit_tree), TRUE, TRUE, 0);
+    gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(xbox));
+    gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
+    gtk_tree_view_insert_column_with_attributes(data->edit_tree, 0, NULL,
+                                                gtk_cell_renderer_text_new(),
+                                                "text", 0, NULL);
+    gtk_tree_view_insert_column_with_attributes(data->edit_tree, 1, NULL,
+                                                gtk_cell_renderer_text_new(),
+                                                "text", 1, NULL);
+    gtk_tree_view_set_headers_visible(data->edit_tree, FALSE);
+    //FIXME: connect "row-activated" for Edit
+    g_signal_connect(gtk_tree_view_get_selection(data->edit_tree), "changed",
+                     G_CALLBACK(on_selection_changed), data);
+    update_options_tree(data);
+
+    /* frame with fields for editing, hidden for now */
+    data->edit_frame = gtk_frame_new(_("Add action"));
+    xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
+    /* combobox for option/action name */
+    align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+    widget = gtk_label_new(NULL);
+    gtk_label_set_markup(GTK_LABEL(widget), _("<b>Name:</b>"));
+    gtk_container_add(GTK_CONTAINER(align), widget);
+    gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
+    widget = gtk_hbox_new(FALSE, 0);
+    data->edit_option_name = gtk_label_new(NULL);
+    gtk_box_pack_start(GTK_BOX(widget), data->edit_option_name, FALSE, TRUE, 0);
+    data->edit_actions = (GtkComboBox *)gtk_combo_box_new();
+    g_signal_connect(data->edit_actions, "changed",
+                     G_CALLBACK(on_option_changed), data);
+    column = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_actions), column, TRUE);
+    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_actions), column,
+                                   "text", 0, NULL);
+    gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_actions), TRUE, TRUE, 0);
+    gtk_container_add(GTK_CONTAINER(align), widget);
+    gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    /* entry or combobox for option value */
+    data->edit_value_label = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
+    widget = gtk_label_new(NULL);
+    gtk_label_set_markup(GTK_LABEL(widget), _("<b>Value:</b>"));
+    gtk_container_add(GTK_CONTAINER(data->edit_value_label), widget);
+    gtk_box_pack_start(xbox, data->edit_value_label, FALSE, TRUE, 0);
+    align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
+    widget = gtk_hbox_new(FALSE, 0);
+    data->edit_value = (GtkEntry *)gtk_entry_new();
+    gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value), TRUE, TRUE, 0);
+    data->edit_values = (GtkComboBox *)gtk_combo_box_new();
+    column = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_values), column, TRUE);
+    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_values), column,
+                                   "text", 0, NULL);
+    gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_values), TRUE, TRUE, 0);
+    g_signal_connect(data->edit_values, "changed",
+                     G_CALLBACK(on_value_changed), data);
+    data->edit_value_num = gtk_spin_button_new_with_range(-1000.0, +1000.0, 1.0);
+    gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num), FALSE, TRUE, 0);
+    data->edit_value_num_label = gtk_label_new(NULL);
+    gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num_label), FALSE, TRUE, 0);
+    gtk_container_add(GTK_CONTAINER(align), widget);
+    gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    /* buttons 'Cancel' and 'Apply' */
+    align = gtk_alignment_new(1.0, 0.0, 0.0, 0.0);
+    gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
+    widget = gtk_hbox_new(TRUE, 4);
+    gtk_container_add(GTK_CONTAINER(align), widget);
+    align = gtk_button_new_from_stock(GTK_STOCK_APPLY); /* reuse align */
+    g_signal_connect(align, "clicked", G_CALLBACK(on_apply_button), data);
+    gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
+    align = gtk_button_new_from_stock(GTK_STOCK_CANCEL); /* reuse align */
+    g_signal_connect(align, "clicked", G_CALLBACK(on_cancel_button), data);
+    gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
+    gtk_container_add(GTK_CONTAINER(data->edit_frame), GTK_WIDGET(xbox));
+    gtk_box_pack_start(vbox, data->edit_frame, TRUE, TRUE, 0);
+
+    gtk_widget_show_all(GTK_WIDGET(vbox));
+    gtk_widget_hide(data->edit_frame);
+    /* hide one of AddAction or AddOption */
+    if (is_action)
+    {
+        /* AddOption is visible for exec only */
+        gtk_action_set_visible(data->add_option_button, FALSE);
+    }
+    else
+    {
+        /* AddAction is visible for action only */
+        GtkAction *act = gtk_ui_manager_get_action(ui, "/toolbar/AddAction");
+        gtk_action_set_visible(act, FALSE);
+    }
+    gtk_container_add(GTK_CONTAINER(data->edit_window), GTK_WIDGET(vbox));
+    update_edit_toolbar(data);
+    gtk_window_present(data->edit_window);
+    gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree));
+}
diff --git a/plugins/gtk/edit.h b/plugins/gtk/edit.h
new file mode 100644 (file)
index 0000000..556058c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
+ *
+ * This file is a part of LXHotkey project.
+ *
+ * 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.
+ */
+
+#ifndef _EDIT_H_
+#define _EDIT_H_ 1
+
+#include <gtk/gtk.h>
+
+typedef struct {
+    /* general data */
+    const gchar *wm;
+    const LXHotkeyPluginInit *cb;
+    gpointer *config;
+    /* main window elements */
+    GtkNotebook *notebook;
+    GtkTreeView *acts, *apps;
+    GtkAction *save_action;
+    GtkAction *del_action;
+    GtkAction *edit_action;
+    GtkTreeView *current_page;
+    /* edit window elements */
+    GtkWindow *edit_window;
+    GList *edit_options_copy;
+    const GList *edit_template;
+    GtkWidget *edit_key1, *edit_key2;
+    GtkEntry *edit_exec;
+    GtkTreeView *edit_tree;
+    /* edit window toolbar elements */
+    GtkAction *add_option_button;
+    GtkAction *rm_option_button;
+    GtkAction *edit_option_button;
+    GtkAction *add_suboption_button;
+    /* edit frame elements */
+    GtkWidget *edit_frame;
+    GtkComboBox *edit_actions;
+    GtkWidget *edit_option_name; //GtkLabel
+    GtkWidget *edit_value_label;
+    GtkEntry *edit_value;
+    GtkComboBox *edit_values;
+    GtkWidget *edit_value_num; //GtkSpinButton
+    GtkWidget *edit_value_num_label; //GtkLabel
+    gint edit_mode;
+} PluginData;
+
+#define LXHOTKEY_ICON "preferences-desktop-keyboard"
+
+void _main_refresh(PluginData *data);
+
+void _edit_action(PluginData *data, GError **error);
+
+void _edit_cleanup(PluginData *data);
+
+#endif /* _EDIT_H_ */
index 91f9111..a097e3c 100644 (file)
 #endif
 
 #include "lxhotkey.h"
+#include "edit.h"
 
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
 
-#define LXHOTKEY_ICON "preferences-desktop-keyboard"
-
 static int inited = 0;
 
-typedef struct {
-    const gchar *wm;
-    const LXHotkeyPluginInit *cb;
-    gpointer *config;
-    GtkNotebook *notebook;
-    GtkTreeView *acts, *apps;
-    GtkAction *save_action;
-    GtkAction *del_action;
-    GtkAction *edit_action;
-    GtkTreeView *current_page;
-} PluginData;
-
 static const char menu_xml[] =
 "<menubar>"
   "<menu action='FileMenu'>"
@@ -72,6 +59,12 @@ static const char menu_xml[] =
 static void set_actions_list(PluginData *data);
 static void set_apps_list(PluginData *data);
 
+void _main_refresh(PluginData *data)
+{
+    set_actions_list(data);
+    set_apps_list(data);
+}
+
 static void on_reload(GtkAction *act, PluginData *data)
 {
     GError *error = NULL;
@@ -83,8 +76,7 @@ static void on_reload(GtkAction *act, PluginData *data)
         //FIXME: show errors instead
         g_error_free(error);
     }
-    set_actions_list(data);
-    set_apps_list(data);
+    _main_refresh(data);
     gtk_action_set_sensitive(data->save_action, FALSE);
 }
 
@@ -102,6 +94,8 @@ static void on_quit(GtkAction *act, PluginData *data)
 
 static void on_new(GtkAction *act, PluginData *data)
 {
+    gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(data->current_page));
+    _edit_action(data, NULL);
 }
 
 static void on_del(GtkAction *act, PluginData *data)
@@ -110,6 +104,7 @@ static void on_del(GtkAction *act, PluginData *data)
 
 static void on_edit(GtkAction *act, PluginData *data)
 {
+    _edit_action(data, NULL);
 }
 
 static void on_about(GtkAction *act, PluginData *data)
@@ -177,6 +172,7 @@ static void on_notebook_switch_page(GtkNotebook *nb, GtkTreeView *page, guint nu
 {
     gboolean has_selection;
 
+    _edit_cleanup(data); /* abort edit */
     data->current_page = page;
     has_selection = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(page), NULL, NULL);
     gtk_action_set_sensitive(data->del_action, has_selection);
@@ -190,6 +186,7 @@ static void on_selection_changed(GtkTreeSelection *selection, PluginData *data)
     if (gtk_tree_view_get_selection(data->current_page) != selection)
         return;
 
+    _edit_cleanup(data); /* abort edit */
     has_selection = gtk_tree_selection_get_selected(selection, NULL, NULL);
     gtk_action_set_sensitive(data->del_action, has_selection);
     gtk_action_set_sensitive(data->edit_action, has_selection);
@@ -197,7 +194,9 @@ static void on_selection_changed(GtkTreeSelection *selection, PluginData *data)
 
 static void set_actions_list(PluginData *data)
 {
-    GtkListStore *model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+    GtkListStore *model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING,
+                                                G_TYPE_STRING, G_TYPE_STRING,
+                                                G_TYPE_POINTER);
     GList *acts = data->cb->get_wm_keys(*data->config, "*", NULL);
     GList *l;
     LXHotkeyGlobal *act;
@@ -224,7 +223,8 @@ static void set_actions_list(PluginData *data)
         gtk_list_store_insert_with_values(model, &iter, -1, 0, attr->name,
                                                             1, val,
                                                             2, act->accel1,
-                                                            3, act->accel2, -1);
+                                                            3, act->accel2,
+                                                            4, act, -1);
         g_free(_val);
         //FIXME: this is a stub, it should show something better than just first action
     }
@@ -235,7 +235,8 @@ static void set_actions_list(PluginData *data)
 
 static void set_apps_list(PluginData *data)
 {
-    GtkListStore *model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+    GtkListStore *model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING,
+                                                G_TYPE_STRING, G_TYPE_POINTER);
     GList *apps = data->cb->get_app_keys(*data->config, "*", NULL);
     GList *l;
     LXHotkeyApp *app;
@@ -246,7 +247,8 @@ static void set_apps_list(PluginData *data)
         app = l->data;
         gtk_list_store_insert_with_values(model, &iter, -1, 0, app->exec,
                                                             1, app->accel1,
-                                                            2, app->accel2, -1);
+                                                            2, app->accel2,
+                                                            3, app, -1);
     }
     g_list_free(apps);
     gtk_tree_view_set_model(data->apps, GTK_TREE_MODEL(model));
@@ -268,6 +270,26 @@ static void module_gtk_run(const gchar *wm, const LXHotkeyPluginInit *cb,
         gtk_init(&inited, NULL);
     inited = 1;
 
+    /* force style for GtkComboBox, it's ugly when list reaches screen bottom */
+#if GTK_CHECK_VERSION(3, 0, 0)
+    GtkCssProvider *provider = gtk_css_provider_new();
+    if (gtk_css_provider_load_from_data(provider,
+                                        "#gtk-widget {\n"
+                                        "-GtkComboBox-appears-as-list : 1;\n"
+                                        "}\n", -1, NULL))
+        gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
+                                                  GTK_STYLE_PROVIDER(provider),
+                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    g_object_unref(provider);
+#else
+    gtk_rc_parse_string("style 'default-style'\n"
+                        "{\n"
+                        "  GtkComboBox::appears-as-list = 1\n"
+                        "}\n"
+                        "class 'GtkWidget' style 'default-style'");
+#endif
+
+    memset(&data, 0, sizeof(data));
     data.wm = wm;
     data.cb = cb;
     data.config = config;
@@ -341,7 +363,6 @@ static void module_gtk_run(const gchar *wm, const LXHotkeyPluginInit *cb,
                          G_CALLBACK(on_selection_changed), &data);
         gtk_notebook_append_page(data.notebook, GTK_WIDGET(data.acts),
                                  gtk_label_new(_("Actions")));
-        data.current_page = data.acts;
     }
 
     if (cb->get_app_keys)
@@ -365,9 +386,11 @@ static void module_gtk_run(const gchar *wm, const LXHotkeyPluginInit *cb,
     }
 
     /* and finally run it all */
+    data.current_page = (GtkTreeView *)gtk_notebook_get_nth_page(data.notebook, 0);
     gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(vbox));
     gtk_widget_show_all(win);
     gtk_main();
+    _edit_cleanup(&data);
 }
 
 static void module_gtk_alert(GError *error)
index 60520ad..a124bae 100644 (file)
@@ -22,6 +22,8 @@
 # include "config.h"
 #endif
 
+#define WANT_OPTIONS_EQUAL
+
 #include "lxhotkey.h"
 
 #include <X11/Xlib.h>
@@ -81,27 +83,6 @@ static void lkxeys_app_free(LXHotkeyApp *data)
     g_free(data);
 }
 
-/* recursively compare two lists of LXHotkeyAttr */
-static gboolean options_equal(GList *opts1, GList *opts2)
-{
-    while (opts1 && opts2) {
-        LXHotkeyAttr *attr1 = opts1->data, *attr2 = opts2->data;
-
-        if (g_strcmp0(attr1->name, attr2->name) != 0)
-            return FALSE;
-        if (attr1->values && attr2->values) {
-            if (g_strcmp0(attr1->values->data, attr2->values->data) != 0)
-                return FALSE;
-        } else if (attr1->values || attr2->values)
-            return FALSE;
-        if (!options_equal(attr1->subopts, attr2->subopts))
-            return FALSE;
-        opts1 = opts1->next;
-        opts2 = opts2->next;
-    }
-    return (opts1 == NULL && opts2 == NULL);
-}
-
 /* convert from OB format (A-Return) into GDK format (<Alt>Return) */
 static gchar *obkey_to_key(const gchar *obkey)
 {
index ec0252f..2b182aa 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2016-10-25 03:56+0300\n"
+"POT-Creation-Date: 2016-10-30 17:59+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -85,591 +85,659 @@ msgstr ""
 msgid "action '%s' does not support options."
 msgstr ""
 
-#: ../src/lxhotkey.c:473
+#: ../src/lxhotkey.c:472
 #, c-format
 msgid "LXHotkey: sorry, cannot configure keys remotely.\n"
 msgstr ""
 
-#: ../src/lxhotkey.c:500
+#: ../src/lxhotkey.c:499
 #, c-format
 msgid "Window manager %s isn't supported now, sorry."
 msgstr ""
 
-#: ../src/lxhotkey.c:507
+#: ../src/lxhotkey.c:506
 msgid "Problems loading configuration: "
 msgstr ""
 
-#: ../src/lxhotkey.c:516
+#: ../src/lxhotkey.c:519
 #, c-format
 msgid "GUI type %s currently isn't supported."
 msgstr ""
 
 #. invalid request
-#: ../src/lxhotkey.c:533 ../src/lxhotkey.c:609
+#: ../src/lxhotkey.c:536 ../src/lxhotkey.c:612
 msgid "Invalid request: "
 msgstr ""
 
-#: ../src/lxhotkey.c:543 ../src/lxhotkey.c:621
+#: ../src/lxhotkey.c:546 ../src/lxhotkey.c:624
 msgid "Problems saving configuration: "
 msgstr ""
 
-#: ../src/lxhotkey.c:560
+#: ../src/lxhotkey.c:563
 msgid "ACTION(s)"
 msgstr ""
 
-#: ../src/lxhotkey.c:560 ../src/lxhotkey.c:638
+#: ../src/lxhotkey.c:563 ../src/lxhotkey.c:641
 msgid "KEY(s)"
 msgstr ""
 
-#: ../src/lxhotkey.c:638
+#: ../src/lxhotkey.c:641
 msgid "EXEC"
 msgstr ""
 
-#: ../src/lxhotkey.c:659
+#: ../src/lxhotkey.c:662
 msgid "Requested operation isn't supported."
 msgstr ""
 
-#: ../plugins/openbox.c:203
+#: ../plugins/openbox/openbox.c:184
 msgid "Failed to reconfigure Openbox."
 msgstr ""
 
 #. reuse GList
 #. reuse GList
-#: ../plugins/openbox.c:222
+#: ../plugins/openbox/openbox.c:203
 msgid "yes"
 msgstr ""
 
-#: ../plugins/openbox.c:222
+#: ../plugins/openbox/openbox.c:203
 msgid "no"
 msgstr ""
 
-#: ../plugins/openbox.c:225
+#: ../plugins/openbox/openbox.c:206
 msgid "enabled"
 msgstr ""
 
-#: ../plugins/openbox.c:226
+#: ../plugins/openbox/openbox.c:207
 msgid "wmclass"
 msgstr ""
 
-#: ../plugins/openbox.c:227
+#: ../plugins/openbox/openbox.c:208
 msgid "name"
 msgstr ""
 
-#: ../plugins/openbox.c:228
+#: ../plugins/openbox/openbox.c:209
 msgid "icon"
 msgstr ""
 
-#: ../plugins/openbox.c:233 ../plugins/openbox.c:305
+#: ../plugins/openbox/openbox.c:214 ../plugins/openbox/openbox.c:286
 msgid "command"
 msgstr ""
 
-#: ../plugins/openbox.c:234 ../plugins/openbox.c:310
+#: ../plugins/openbox/openbox.c:215 ../plugins/openbox/openbox.c:291
 msgid "prompt"
 msgstr ""
 
-#: ../plugins/openbox.c:235
+#: ../plugins/openbox/openbox.c:216
 msgid "startupnotify"
 msgstr ""
 
-#: ../plugins/openbox.c:239 ../plugins/openbox.c:342
+#: ../plugins/openbox/openbox.c:220 ../plugins/openbox/openbox.c:323
 msgid "center"
 msgstr ""
 
-#: ../plugins/openbox.c:240
+#: ../plugins/openbox/openbox.c:221
 msgid "default"
 msgstr ""
 
-#: ../plugins/openbox.c:240
+#: ../plugins/openbox/openbox.c:221
 msgid "primary"
 msgstr ""
 
-#: ../plugins/openbox.c:240
+#: ../plugins/openbox/openbox.c:221
 msgid "mouse"
 msgstr ""
 
-#: ../plugins/openbox.c:241
+#: ../plugins/openbox/openbox.c:222
 msgid "active"
 msgstr ""
 
-#: ../plugins/openbox.c:241 ../plugins/openbox.c:344
+#: ../plugins/openbox/openbox.c:222 ../plugins/openbox/openbox.c:325
 msgid "all"
 msgstr ""
 
-#: ../plugins/openbox.c:246 ../plugins/openbox.c:351
+#: ../plugins/openbox/openbox.c:227 ../plugins/openbox/openbox.c:332
 msgid "monitor"
 msgstr ""
 
-#: ../plugins/openbox.c:251
+#: ../plugins/openbox/openbox.c:232
 msgid "menu"
 msgstr ""
 
-#: ../plugins/openbox.c:252
+#: ../plugins/openbox/openbox.c:233
 msgid "position"
 msgstr ""
 
-#: ../plugins/openbox.c:256
+#: ../plugins/openbox/openbox.c:237
 msgid "list"
 msgstr ""
 
-#: ../plugins/openbox.c:256
+#: ../plugins/openbox/openbox.c:237
 msgid "icons"
 msgstr ""
 
-#: ../plugins/openbox.c:256
+#: ../plugins/openbox/openbox.c:237
 msgid "none"
 msgstr ""
 
-#: ../plugins/openbox.c:259 ../plugins/openbox.c:277
+#: ../plugins/openbox/openbox.c:240 ../plugins/openbox/openbox.c:258
 msgid "dialog"
 msgstr ""
 
-#: ../plugins/openbox.c:260 ../plugins/openbox.c:278
+#: ../plugins/openbox/openbox.c:241 ../plugins/openbox/openbox.c:259
 msgid "bar"
 msgstr ""
 
-#: ../plugins/openbox.c:261 ../plugins/openbox.c:279
+#: ../plugins/openbox/openbox.c:242 ../plugins/openbox/openbox.c:260
 msgid "raise"
 msgstr ""
 
-#: ../plugins/openbox.c:262
+#: ../plugins/openbox/openbox.c:243
 msgid "allDesktops"
 msgstr ""
 
-#: ../plugins/openbox.c:263 ../plugins/openbox.c:280
+#: ../plugins/openbox/openbox.c:244 ../plugins/openbox/openbox.c:261
 msgid "panels"
 msgstr ""
 
-#: ../plugins/openbox.c:264
+#: ../plugins/openbox/openbox.c:245
 msgid "desktop"
 msgstr ""
 
-#: ../plugins/openbox.c:265
+#: ../plugins/openbox/openbox.c:246
 msgid "linear"
 msgstr ""
 
-#: ../plugins/openbox.c:266
+#: ../plugins/openbox/openbox.c:247
 msgid "interactive"
 msgstr ""
 
-#: ../plugins/openbox.c:267 ../plugins/openbox.c:282
+#: ../plugins/openbox/openbox.c:248 ../plugins/openbox/openbox.c:263
 msgid "finalactions"
 msgstr ""
 
-#: ../plugins/openbox.c:271 ../plugins/openbox.c:287 ../plugins/openbox.c:322
-#: ../plugins/openbox.c:373
+#: ../plugins/openbox/openbox.c:252 ../plugins/openbox/openbox.c:268
+#: ../plugins/openbox/openbox.c:303 ../plugins/openbox/openbox.c:354
 msgid "north"
 msgstr ""
 
-#: ../plugins/openbox.c:271
+#: ../plugins/openbox/openbox.c:252
 msgid "northeast"
 msgstr ""
 
-#: ../plugins/openbox.c:271 ../plugins/openbox.c:288 ../plugins/openbox.c:323
-#: ../plugins/openbox.c:373
+#: ../plugins/openbox/openbox.c:252 ../plugins/openbox/openbox.c:269
+#: ../plugins/openbox/openbox.c:304 ../plugins/openbox/openbox.c:354
 msgid "east"
 msgstr ""
 
-#: ../plugins/openbox.c:272
+#: ../plugins/openbox/openbox.c:253
 msgid "southeast"
 msgstr ""
 
-#: ../plugins/openbox.c:272 ../plugins/openbox.c:287 ../plugins/openbox.c:322
-#: ../plugins/openbox.c:373
+#: ../plugins/openbox/openbox.c:253 ../plugins/openbox/openbox.c:268
+#: ../plugins/openbox/openbox.c:303 ../plugins/openbox/openbox.c:354
 msgid "south"
 msgstr ""
 
-#: ../plugins/openbox.c:272
+#: ../plugins/openbox/openbox.c:253
 msgid "southwest"
 msgstr ""
 
-#: ../plugins/openbox.c:273 ../plugins/openbox.c:288 ../plugins/openbox.c:323
-#: ../plugins/openbox.c:373
+#: ../plugins/openbox/openbox.c:254 ../plugins/openbox/openbox.c:269
+#: ../plugins/openbox/openbox.c:304 ../plugins/openbox/openbox.c:354
 msgid "west"
 msgstr ""
 
-#: ../plugins/openbox.c:273
+#: ../plugins/openbox/openbox.c:254
 msgid "northwest"
 msgstr ""
 
-#: ../plugins/openbox.c:276 ../plugins/openbox.c:317 ../plugins/openbox.c:376
+#: ../plugins/openbox/openbox.c:257 ../plugins/openbox/openbox.c:298
+#: ../plugins/openbox/openbox.c:357
 msgid "direction"
 msgstr ""
 
-#: ../plugins/openbox.c:281
+#: ../plugins/openbox/openbox.c:262
 msgid "desktops"
 msgstr ""
 
-#: ../plugins/openbox.c:286 ../plugins/openbox.c:297 ../plugins/openbox.c:321
-#: ../plugins/openbox.c:342 ../plugins/openbox.c:343 ../plugins/openbox.c:344
+#: ../plugins/openbox/openbox.c:267 ../plugins/openbox/openbox.c:278
+#: ../plugins/openbox/openbox.c:302 ../plugins/openbox/openbox.c:323
+#: ../plugins/openbox/openbox.c:324 ../plugins/openbox/openbox.c:325
 msgid "current"
 msgstr ""
 
-#: ../plugins/openbox.c:286 ../plugins/openbox.c:321 ../plugins/openbox.c:344
+#: ../plugins/openbox/openbox.c:267 ../plugins/openbox/openbox.c:302
+#: ../plugins/openbox/openbox.c:325
 msgid "next"
 msgstr ""
 
-#: ../plugins/openbox.c:286 ../plugins/openbox.c:321
+#: ../plugins/openbox/openbox.c:267 ../plugins/openbox/openbox.c:302
 msgid "previous"
 msgstr ""
 
-#: ../plugins/openbox.c:287 ../plugins/openbox.c:297 ../plugins/openbox.c:322
+#: ../plugins/openbox/openbox.c:268 ../plugins/openbox/openbox.c:278
+#: ../plugins/openbox/openbox.c:303
 msgid "last"
 msgstr ""
 
-#: ../plugins/openbox.c:287 ../plugins/openbox.c:322
+#: ../plugins/openbox/openbox.c:268 ../plugins/openbox/openbox.c:303
 msgid "up"
 msgstr ""
 
-#: ../plugins/openbox.c:288 ../plugins/openbox.c:323
+#: ../plugins/openbox/openbox.c:269 ../plugins/openbox/openbox.c:304
 msgid "down"
 msgstr ""
 
-#: ../plugins/openbox.c:288 ../plugins/openbox.c:323 ../plugins/openbox.c:333
-#: ../plugins/openbox.c:366
+#: ../plugins/openbox/openbox.c:269 ../plugins/openbox/openbox.c:304
+#: ../plugins/openbox/openbox.c:314 ../plugins/openbox/openbox.c:347
 msgid "left"
 msgstr ""
 
-#: ../plugins/openbox.c:289 ../plugins/openbox.c:324 ../plugins/openbox.c:333
-#: ../plugins/openbox.c:367
+#: ../plugins/openbox/openbox.c:270 ../plugins/openbox/openbox.c:305
+#: ../plugins/openbox/openbox.c:314 ../plugins/openbox/openbox.c:348
 msgid "right"
 msgstr ""
 
-#: ../plugins/openbox.c:292 ../plugins/openbox.c:327
+#: ../plugins/openbox/openbox.c:273 ../plugins/openbox/openbox.c:308
 msgid "to"
 msgstr ""
 
-#: ../plugins/openbox.c:293 ../plugins/openbox.c:328
+#: ../plugins/openbox/openbox.c:274 ../plugins/openbox/openbox.c:309
 msgid "wrap"
 msgstr ""
 
-#: ../plugins/openbox.c:300
+#: ../plugins/openbox/openbox.c:281
 msgid "where"
 msgstr ""
 
-#: ../plugins/openbox.c:314
+#: ../plugins/openbox/openbox.c:295
 msgid "both"
 msgstr ""
 
-#: ../plugins/openbox.c:314
+#: ../plugins/openbox/openbox.c:295
 msgid "horizontal"
 msgstr ""
 
-#: ../plugins/openbox.c:314
+#: ../plugins/openbox/openbox.c:295
 msgid "vertical"
 msgstr ""
 
-#: ../plugins/openbox.c:329
+#: ../plugins/openbox/openbox.c:310
 msgid "follow"
 msgstr ""
 
-#: ../plugins/openbox.c:333 ../plugins/openbox.c:368 ../plugins/openbox.c:380
+#: ../plugins/openbox/openbox.c:314 ../plugins/openbox/openbox.c:349
+#: ../plugins/openbox/openbox.c:361
 msgid "top"
 msgstr ""
 
-#: ../plugins/openbox.c:333 ../plugins/openbox.c:369 ../plugins/openbox.c:380
+#: ../plugins/openbox/openbox.c:314 ../plugins/openbox/openbox.c:350
+#: ../plugins/openbox/openbox.c:361
 msgid "bottom"
 msgstr ""
 
-#: ../plugins/openbox.c:334
+#: ../plugins/openbox/openbox.c:315
 msgid "topleft"
 msgstr ""
 
-#: ../plugins/openbox.c:334
+#: ../plugins/openbox/openbox.c:315
 msgid "topright"
 msgstr ""
 
-#: ../plugins/openbox.c:334
+#: ../plugins/openbox/openbox.c:315
 msgid "bottomleft"
 msgstr ""
 
-#: ../plugins/openbox.c:335
+#: ../plugins/openbox/openbox.c:316
 msgid "bottomright"
 msgstr ""
 
-#: ../plugins/openbox.c:338
+#: ../plugins/openbox/openbox.c:319
 msgid "edge"
 msgstr ""
 
-#: ../plugins/openbox.c:344
+#: ../plugins/openbox/openbox.c:325
 msgid "prev"
 msgstr ""
 
-#: ../plugins/openbox.c:349
+#: ../plugins/openbox/openbox.c:330
 msgid "width"
 msgstr ""
 
-#: ../plugins/openbox.c:350
+#: ../plugins/openbox/openbox.c:331
 msgid "height"
 msgstr ""
 
-#: ../plugins/openbox.c:380
+#: ../plugins/openbox/openbox.c:361
 msgid "normal"
 msgstr ""
 
-#: ../plugins/openbox.c:383
+#: ../plugins/openbox/openbox.c:364
 msgid "layer"
 msgstr ""
 
 #. global actions
-#: ../plugins/openbox.c:389
+#: ../plugins/openbox/openbox.c:370
 msgid "Execute"
 msgstr ""
 
-#: ../plugins/openbox.c:390
+#: ../plugins/openbox/openbox.c:371
 msgid "ShowMenu"
 msgstr ""
 
-#: ../plugins/openbox.c:391
+#: ../plugins/openbox/openbox.c:372
 msgid "NextWindow"
 msgstr ""
 
-#: ../plugins/openbox.c:392
+#: ../plugins/openbox/openbox.c:373
 msgid "PreviousWindow"
 msgstr ""
 
-#: ../plugins/openbox.c:393
+#: ../plugins/openbox/openbox.c:374
 msgid "DirectionalCycleWindows"
 msgstr ""
 
-#: ../plugins/openbox.c:394
+#: ../plugins/openbox/openbox.c:375
 msgid "DirectionalTargetWindow"
 msgstr ""
 
-#: ../plugins/openbox.c:395
+#: ../plugins/openbox/openbox.c:376
 msgid "GoToDesktop"
 msgstr ""
 
-#: ../plugins/openbox.c:396
+#: ../plugins/openbox/openbox.c:377
 msgid "AddDesktop"
 msgstr ""
 
-#: ../plugins/openbox.c:397
+#: ../plugins/openbox/openbox.c:378
 msgid "RemoveDesktop"
 msgstr ""
 
-#: ../plugins/openbox.c:398
+#: ../plugins/openbox/openbox.c:379
 msgid "ToggleDockAutohide"
 msgstr ""
 
-#: ../plugins/openbox.c:399
+#: ../plugins/openbox/openbox.c:380
 msgid "Reconfigure"
 msgstr ""
 
-#: ../plugins/openbox.c:400
+#: ../plugins/openbox/openbox.c:381
 msgid "Restart"
 msgstr ""
 
-#: ../plugins/openbox.c:401
+#: ../plugins/openbox/openbox.c:382
 msgid "Exit"
 msgstr ""
 
 #. windows actions
-#: ../plugins/openbox.c:403
+#: ../plugins/openbox/openbox.c:384
 msgid "Focus"
 msgstr ""
 
-#: ../plugins/openbox.c:404
+#: ../plugins/openbox/openbox.c:385
 msgid "Raise"
 msgstr ""
 
-#: ../plugins/openbox.c:405
+#: ../plugins/openbox/openbox.c:386
 msgid "Lower"
 msgstr ""
 
-#: ../plugins/openbox.c:406
+#: ../plugins/openbox/openbox.c:387
 msgid "RaiseLower"
 msgstr ""
 
-#: ../plugins/openbox.c:407
+#: ../plugins/openbox/openbox.c:388
 msgid "Unfocus"
 msgstr ""
 
-#: ../plugins/openbox.c:408
+#: ../plugins/openbox/openbox.c:389
 msgid "FocusToBottom"
 msgstr ""
 
-#: ../plugins/openbox.c:409
+#: ../plugins/openbox/openbox.c:390
 msgid "Iconify"
 msgstr ""
 
-#: ../plugins/openbox.c:410
+#: ../plugins/openbox/openbox.c:391
 msgid "Close"
 msgstr ""
 
-#: ../plugins/openbox.c:411
+#: ../plugins/openbox/openbox.c:392
 msgid "ToggleShade"
 msgstr ""
 
-#: ../plugins/openbox.c:412
+#: ../plugins/openbox/openbox.c:393
 msgid "Shade"
 msgstr ""
 
-#: ../plugins/openbox.c:413
+#: ../plugins/openbox/openbox.c:394
 msgid "Unshade"
 msgstr ""
 
-#: ../plugins/openbox.c:414
+#: ../plugins/openbox/openbox.c:395
 msgid "ToggleOmnipresent"
 msgstr ""
 
-#: ../plugins/openbox.c:415
+#: ../plugins/openbox/openbox.c:396
 msgid "ToggleMaximize"
 msgstr ""
 
-#: ../plugins/openbox.c:416
+#: ../plugins/openbox/openbox.c:397
 msgid "Maximize"
 msgstr ""
 
-#: ../plugins/openbox.c:417
+#: ../plugins/openbox/openbox.c:398
 msgid "Unmaximize"
 msgstr ""
 
-#: ../plugins/openbox.c:418
+#: ../plugins/openbox/openbox.c:399
 msgid "ToggleFullscreen"
 msgstr ""
 
-#: ../plugins/openbox.c:419
+#: ../plugins/openbox/openbox.c:400
 msgid "ToggleDecorations"
 msgstr ""
 
-#: ../plugins/openbox.c:420
+#: ../plugins/openbox/openbox.c:401
 msgid "Decorate"
 msgstr ""
 
-#: ../plugins/openbox.c:421
+#: ../plugins/openbox/openbox.c:402
 msgid "Undecorate"
 msgstr ""
 
-#: ../plugins/openbox.c:422
+#: ../plugins/openbox/openbox.c:403
 msgid "SendToDesktop"
 msgstr ""
 
-#: ../plugins/openbox.c:423
+#: ../plugins/openbox/openbox.c:404
 msgid "Move"
 msgstr ""
 
-#: ../plugins/openbox.c:424
+#: ../plugins/openbox/openbox.c:405
 msgid "Resize"
 msgstr ""
 
-#: ../plugins/openbox.c:425
+#: ../plugins/openbox/openbox.c:406
 msgid "MoveResizeTo"
 msgstr ""
 
-#: ../plugins/openbox.c:426
+#: ../plugins/openbox/openbox.c:407
 msgid "MoveRelative"
 msgstr ""
 
-#: ../plugins/openbox.c:427
+#: ../plugins/openbox/openbox.c:408
 msgid "ResizeRelative"
 msgstr ""
 
-#: ../plugins/openbox.c:428
+#: ../plugins/openbox/openbox.c:409
 msgid "MoveToEdge"
 msgstr ""
 
-#: ../plugins/openbox.c:429
+#: ../plugins/openbox/openbox.c:410
 msgid "GrowToEdge"
 msgstr ""
 
-#: ../plugins/openbox.c:430
+#: ../plugins/openbox/openbox.c:411
 msgid "ShrinkToEdge"
 msgstr ""
 
-#: ../plugins/openbox.c:431
+#: ../plugins/openbox/openbox.c:412
 msgid "GrowToFill"
 msgstr ""
 
-#: ../plugins/openbox.c:432
+#: ../plugins/openbox/openbox.c:413
 msgid "ToggleAlwaysOnTop"
 msgstr ""
 
-#: ../plugins/openbox.c:433
+#: ../plugins/openbox/openbox.c:414
 msgid "ToggleAlwaysOnBottom"
 msgstr ""
 
-#: ../plugins/openbox.c:434
+#: ../plugins/openbox/openbox.c:415
 msgid "SendToLayer"
 msgstr ""
 
-#: ../plugins/openbox.c:516
+#: ../plugins/openbox/openbox.c:506
 msgid "Duplicate <keyboard> section in the rc.xml file."
 msgstr ""
 
-#: ../plugins/openbox.c:541 ../plugins/openbox.c:547 ../plugins/openbox.c:782
+#: ../plugins/openbox/openbox.c:531 ../plugins/openbox/openbox.c:537
+#: ../plugins/openbox/openbox.c:779
 msgid "Internal error."
 msgstr ""
 
-#: ../plugins/openbox.c:590
+#: ../plugins/openbox/openbox.c:581
 msgid "rc.xml error: no key is set for a keybind."
 msgstr ""
 
-#: ../plugins/openbox.c:661
+#: ../plugins/openbox/openbox.c:652
 msgid "Invalid rc.xml: action with a sub-action."
 msgstr ""
 
-#: ../plugins/openbox.c:771
+#: ../plugins/openbox/openbox.c:768
 msgid "rc.xml error: no name is set for action."
 msgstr ""
 
-#: ../plugins/openbox.c:801
+#: ../plugins/openbox/openbox.c:798
 #, c-format
 msgid "rc.xml error: empty tag <%s> is prohibited."
 msgstr ""
 
-#: ../plugins/openbox.c:894
+#: ../plugins/openbox/openbox.c:887
 msgid "Could not find the rc.xml file anywhere."
 msgstr ""
 
-#: ../plugins/openbox.c:940 ../plugins/openbox.c:1053
-#: ../plugins/openbox.c:1197 ../plugins/openbox.c:1215
+#: ../plugins/openbox/openbox.c:935 ../plugins/openbox/openbox.c:1048
+#: ../plugins/openbox/openbox.c:1192 ../plugins/openbox/openbox.c:1210
 msgid "No WM configuration is available."
 msgstr ""
 
-#: ../plugins/openbox.c:1057
+#: ../plugins/openbox/openbox.c:1052
 msgid "Keybinding should activate at least one action."
 msgstr ""
 
-#: ../plugins/openbox.c:1079 ../plugins/openbox.c:1089
-#: ../plugins/openbox.c:1241 ../plugins/openbox.c:1251
+#: ../plugins/openbox/openbox.c:1074 ../plugins/openbox/openbox.c:1084
+#: ../plugins/openbox/openbox.c:1236 ../plugins/openbox/openbox.c:1246
 #, c-format
 msgid "Hotkey '%s' is already bound to an action."
 msgstr ""
 
-#: ../plugins/openbox.c:1219
+#: ../plugins/openbox/openbox.c:1214
 msgid "The exec line cannot be empty."
 msgstr ""
 
-#: ../plugins/gtk.c:86
+#. TRANSLATORS: Replace this string with your names, one name per line.
+#: ../plugins/gtk/gtk.c:118
+msgid "translator-credits"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:126
+msgid "Copyright (C) 2016"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:127
+msgid "Keyboard shortcuts configurator"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:150
 msgid "_File"
 msgstr ""
 
-#: ../plugins/gtk.c:89
+#: ../plugins/gtk/gtk.c:151
+msgid "_Reload"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:152
+msgid ""
+"Drop all unsaved changes and reload all keys from Window Manager "
+"configuration"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:155
+msgid "Save all changes and apply them to Window Manager configuration"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:158
+msgid "Exit the application without saving"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:159
 msgid "_Edit"
 msgstr ""
 
-#: ../plugins/gtk.c:93
+#: ../plugins/gtk/gtk.c:160
+msgid "Create new action"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:162
+msgid "Remove selected action"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:164
+msgid "Change selected action"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:166
 msgid "_Help"
 msgstr ""
 
-#. ...
-#: ../plugins/gtk.c:165
+#: ../plugins/gtk/gtk.c:348
+msgid "Action"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:351
+msgid "Option"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:354 ../plugins/gtk/gtk.c:374
+msgid "Hotkey 1"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:357 ../plugins/gtk/gtk.c:377
+msgid "Hotkey 2"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:365
 msgid "Actions"
 msgstr ""
 
-#. ...
-#: ../plugins/gtk.c:170
+#: ../plugins/gtk/gtk.c:371
+msgid "Command"
+msgstr ""
+
+#: ../plugins/gtk/gtk.c:385
 msgid "Programs"
 msgstr ""
 
-#: ../plugins/lxhotkey-gtk.desktop.in.h:1
+#: ../plugins/gtk/lxhotkey-gtk.desktop.in.h:1
 msgid "Setup Hot Keys"
 msgstr ""
 
-#: ../plugins/lxhotkey-gtk.desktop.in.h:2
+#: ../plugins/gtk/lxhotkey-gtk.desktop.in.h:2
 msgid "View and change Window Manager global shortcuts"
 msgstr ""
 
-#: ../plugins/lxhotkey-gtk.desktop.in.h:3
+#: ../plugins/gtk/lxhotkey-gtk.desktop.in.h:3
 msgid "hotkey;shortcut;system;settings;"
 msgstr ""
index 04488f3..ea07a1f 100644 (file)
@@ -448,7 +448,6 @@ int main(int argc, char *argv[])
     gboolean do_gui = FALSE;
 
     /* init localizations */
-    setlocale(LC_ALL, "");
 #ifdef ENABLE_NLS
     bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
     bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
index e1f1c17..b123c74 100644 (file)
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
  * @subopts: (element-type LXHotkeyAttr): (allow-none): list of suboptions
  * @desc: (allow-none): action or option description
  * @has_actions: %TRUE if @subopts contains actions, %FALSE if @subopts contains options
+ * @has_value: %TRUE if option has value even if @subopts isn't %NULL
  *
  * Data descriptor for actions and options. Actions are ativated by keybinding.
  * Each action may contain arbitrary number of options that alter its execution.
@@ -54,8 +55,48 @@ typedef struct {
     GList *subopts;
     gchar *desc;
     gboolean has_actions;
+    gboolean has_value;
 } LXHotkeyAttr;
 
+#ifdef WANT_OPTIONS_EQUAL
+static gboolean values_equal(GList *vals1, GList *vals2)
+{
+    while (vals1 && vals2) {
+        if (g_strcmp0(vals1->data, vals2->data) != 0)
+            return FALSE;
+        vals1 = vals1->next;
+        vals2 = vals2->next;
+    }
+    return (vals1 == NULL && vals2 == NULL);
+}
+
+/**
+ * options_equal
+ * @opts1: (element-type LXHotkeyAttr): list of options
+ * @opts2: (element-type LXHotkeyAttr): list of options
+ *
+ * Recursively compare two lists of options.
+ *
+ * Returns: %TRUE if two lists are equal.
+ */
+static gboolean options_equal(GList *opts1, GList *opts2)
+{
+    while (opts1 && opts2) {
+        LXHotkeyAttr *attr1 = opts1->data, *attr2 = opts2->data;
+
+        if (g_strcmp0(attr1->name, attr2->name) != 0)
+            return FALSE;
+        if (!values_equal(attr1->values, attr2->values))
+            return FALSE;
+        if (!options_equal(attr1->subopts, attr2->subopts))
+            return FALSE;
+        opts1 = opts1->next;
+        opts2 = opts2->next;
+    }
+    return (opts1 == NULL && opts2 == NULL);
+}
+#endif /* WANT_OPTIONS_EQUAL */
+
 /**
  * LXHotkeyGlobal:
  * @actions: (element-type LXHotkeyAttr): list of actions
@@ -80,7 +121,7 @@ typedef struct {
 /**
  * LXHotkeyApp:
  * @exec: a command line to execute
- * @actions: (element-type LXHotkeyAttr): (allow-none): list of options
+ * @options: (element-type LXHotkeyAttr): (allow-none): list of options
  * @accel1: a keybinding to activate @exec, in GDK accelerator format
  * @accel2: optional alternate keybinding to activate @exec
  * @data1: a pointer for using by WM plugin