GTK+ plugin: fix editing option for command, and Edit button sensitivity.
[lxde/lxhotkey.git] / plugins / gtk / edit.c
1 /*
2 * Copyright (C) 2016 Andriy Grytsenko <andrej@rep.kiev.ua>
3 *
4 * This file is a part of LXHotkey project.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #define WANT_OPTIONS_EQUAL
26
27 #include "lxhotkey.h"
28 #include "edit.h"
29
30 #include <stdlib.h>
31 #include <glib/gi18n-lib.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
34
35 #if !GTK_CHECK_VERSION(2, 21, 0)
36 # define GDK_KEY_Tab GDK_Tab
37 # define GDK_KEY_BackSpace GDK_BackSpace
38 # define GDK_KEY_Escape GDK_Escape
39 # define GDK_KEY_space GDK_space
40 #endif
41
42 enum {
43 EDIT_MODE_NONE,
44 EDIT_MODE_ADD, /* add action */
45 EDIT_MODE_EDIT, /* change selected */
46 EDIT_MODE_OPTION /* add suboption */
47 };
48
49 static const LXHotkeyAttr *find_template_for_option(GtkTreeModel *model,
50 GtkTreeIter *iter,
51 const GList *t_list)
52 {
53 const LXHotkeyAttr *opt, *tmpl;
54
55 gtk_tree_model_get(model, iter, 2, &opt, -1);
56 while (t_list)
57 {
58 tmpl = t_list->data;
59 if (g_strcmp0(tmpl->name, opt->name) == 0)
60 return tmpl;
61 t_list = t_list->next;
62 }
63 return NULL;
64 }
65
66 static const GList *get_options_from_template(const LXHotkeyAttr *tmpl, PluginData *data)
67 {
68 if (tmpl == NULL)
69 return NULL;
70 else if (tmpl->has_actions)
71 return data->edit_template;
72 else
73 return tmpl->subopts;
74 }
75
76 static const GList *get_parent_template_list(GtkTreeModel *model, GtkTreeIter *iter,
77 PluginData *data)
78 {
79 const GList *tmpl_list;
80 GtkTreeIter parent_iter;
81 const LXHotkeyAttr *parent;
82
83 if (!gtk_tree_model_iter_parent(model, &parent_iter, iter))
84 return data->edit_template;
85 /* get list for parent of parent first - recursion is here */
86 tmpl_list = get_parent_template_list(model, &parent_iter, data);
87 /* now find parent in that list */
88 parent = find_template_for_option(model, &parent_iter, tmpl_list);
89 return get_options_from_template(parent, data);
90 }
91
92 static void fill_edit_frame(PluginData *data, const LXHotkeyAttr *opt,
93 const GList *subopts, const GList *exempt)
94 {
95 GtkListStore *names_store;
96 const LXHotkeyAttr *sub;
97 const GList *l;
98 int i = 0;
99
100 names_store = GTK_LIST_STORE(gtk_list_store_new(3, G_TYPE_STRING, /* description */
101 G_TYPE_STRING, /* name */
102 G_TYPE_POINTER)); /* template */
103 while (subopts)
104 {
105 sub = subopts->data;
106 /* ignore existing opts */
107 for (l = exempt; l; l = l->next)
108 if (strcmp(sub->name, ((LXHotkeyAttr *)l->data)->name) == 0)
109 break;
110 if (l == NULL)
111 gtk_list_store_insert_with_values(names_store, NULL, i++, 0, _(sub->name),
112 1, sub->name,
113 2, sub, -1);
114 subopts = subopts->next;
115 }
116 gtk_combo_box_set_model(data->edit_actions, GTK_TREE_MODEL(names_store));
117 g_object_unref(names_store);
118 gtk_combo_box_set_active(data->edit_actions, 0);
119 /* values box will be set by changing active callback */
120 gtk_widget_set_visible(GTK_WIDGET(data->edit_actions), opt == NULL); /* visible on add */
121 gtk_widget_set_visible(data->edit_option_name, opt != NULL); /* visible on edit */
122 if (opt)
123 gtk_label_set_text(GTK_LABEL(data->edit_option_name), _(opt->name));
124 }
125
126 static void update_edit_toolbar(PluginData *data)
127 {
128 const LXHotkeyAttr *opt, *tmpl;
129 const GList *tmpl_list;
130 GtkTreeModel *model;
131 GtkTreeIter iter;
132 gboolean is_action;
133
134 /* update AddOption button -- exec only */
135 if (gtk_action_get_visible(data->add_option_button))
136 {
137 /* make not clickable if no more options to add */
138 gtk_action_set_sensitive(data->add_option_button,
139 g_list_length((GList *)data->edit_template) != g_list_length(data->edit_options_copy));
140 }
141 /* update Remove button */
142 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
143 &model, &iter))
144 {
145 gtk_action_set_sensitive(data->rm_option_button, FALSE);
146 gtk_action_set_sensitive(data->edit_option_button, FALSE);
147 gtk_action_set_sensitive(data->add_suboption_button, FALSE);
148 return;
149 }
150 gtk_action_set_sensitive(data->rm_option_button, TRUE);
151 gtk_tree_model_get(model, &iter, 2, &opt, -1);
152 tmpl_list = get_parent_template_list(model, &iter, data);
153 is_action = (data->current_page == data->acts && tmpl_list == data->edit_template);
154 while (tmpl_list)
155 {
156 tmpl = tmpl_list->data;
157 if (g_strcmp0(tmpl->name, opt->name) == 0)
158 break;
159 tmpl_list = tmpl_list->next;
160 }
161 if (G_UNLIKELY(tmpl_list == NULL))
162 {
163 /* option isn't supported, probably deprecated one */
164 gtk_action_set_sensitive(data->edit_option_button, FALSE);
165 gtk_action_set_sensitive(data->add_suboption_button, FALSE);
166 return;
167 }
168 gtk_action_set_sensitive(data->edit_option_button,
169 !is_action && (tmpl->subopts == NULL || tmpl->has_value));
170 gtk_action_set_sensitive(data->add_suboption_button,
171 (tmpl->has_actions ||
172 g_list_length(tmpl->subopts) != g_list_length(opt->subopts)));
173 }
174
175 static void on_cancel(GtkAction *action, PluginData *data)
176 {
177 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
178 }
179
180 static void on_save(GtkAction *action, PluginData *data)
181 {
182 GError *error = NULL;
183 LXHotkeyGlobal *act = NULL;
184 LXHotkeyApp *app = NULL;
185 GtkTreeModel *model;
186 GtkTreeIter iter;
187 LXHotkeyGlobal new_act;
188 LXHotkeyApp new_app;
189 gboolean ok = FALSE;
190
191 if (data->current_page == data->acts)
192 {
193 /* it's global */
194 new_act.accel1 = g_object_get_data(G_OBJECT(data->edit_key1), "accelerator_name");
195 new_act.accel2 = g_object_get_data(G_OBJECT(data->edit_key2), "accelerator_name");
196 if (new_act.accel1 == NULL || new_act.accel1[0] == 0)
197 {
198 new_act.accel1 = new_act.accel2;
199 new_act.accel2 = NULL;
200 }
201 new_act.actions = data->edit_options_copy;
202 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->acts),
203 &model, &iter))
204 gtk_tree_model_get(model, &iter, 4, &act, -1);
205 if (act)
206 {
207 /* global edited */
208 if (!options_equal(act->actions, new_act.actions))
209 {
210 /* actions list changed, remove old binding and add new */
211 LXHotkeyGlobal rem_act = *act;
212
213 rem_act.accel1 = rem_act.accel2 = NULL;
214 if (!data->cb->set_wm_key(*data->config, &rem_act, &error))
215 goto _exit;
216 }
217 else if (g_strcmp0(act->accel1, new_act.accel1) == 0 &&
218 g_strcmp0(act->accel2, new_act.accel2) == 0)
219 /* nothing was changed */
220 goto _exit;
221 }
222 /* else it was added */
223 ok = data->cb->set_wm_key(*data->config, &new_act, &error);
224 }
225 else
226 {
227 /* it's application */
228 new_app.accel1 = g_object_get_data(G_OBJECT(data->edit_key1), "accelerator_name");
229 new_app.accel2 = g_object_get_data(G_OBJECT(data->edit_key2), "accelerator_name");
230 if (new_app.accel1 == NULL || new_app.accel1[0] == 0)
231 {
232 new_app.accel1 = new_app.accel2;
233 new_app.accel2 = NULL;
234 }
235 new_app.exec = (char *)gtk_entry_get_text(data->edit_exec);
236 new_app.options = data->edit_options_copy;
237 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->apps),
238 &model, &iter))
239 gtk_tree_model_get(model, &iter, 3, &app, -1);
240 if (app)
241 {
242 /* app edited */
243 if (g_strcmp0(app->exec, new_app.exec) != 0 ||
244 !options_equal(app->options, new_app.options))
245 {
246 /* exec line or options list changed, remove old binding and add new */
247 LXHotkeyApp rem_app = *app;
248
249 rem_app.accel1 = rem_app.accel2 = NULL;
250 if (!data->cb->set_app_key(*data->config, &rem_app, &error))
251 goto _exit;
252 }
253 else if (g_strcmp0(app->accel1, new_app.accel1) == 0 &&
254 g_strcmp0(app->accel2, new_app.accel2) == 0)
255 /* nothing was changed */
256 goto _exit;
257 }
258 /* else it was added */
259 ok = data->cb->set_app_key(*data->config, &new_app, &error);
260 }
261
262 _exit:
263 if (error)
264 {
265 _show_error(_("Apply error: "), error);
266 g_error_free(error);
267 }
268 /* update main window */
269 if (ok)
270 gtk_action_set_sensitive(data->save_action, TRUE);
271 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
272 if (ok)
273 _main_refresh(data);
274 }
275
276 static void on_add_action(GtkAction *action, PluginData *data)
277 {
278 /* fill frame with empty data, set choices from data->edit_template, hide value */
279 data->edit_mode = EDIT_MODE_ADD;
280 gtk_frame_set_label(GTK_FRAME(data->edit_frame), _("Add action"));
281 fill_edit_frame(data, NULL, data->edit_template, NULL);
282 gtk_widget_hide(GTK_WIDGET(data->edit_values));
283 gtk_widget_hide(GTK_WIDGET(data->edit_value));
284 gtk_widget_show(data->edit_frame);
285 gtk_widget_grab_focus(data->edit_frame);
286 }
287
288 static void on_add_option(GtkAction *act, PluginData *data)
289 {
290 /* fill frame with empty data, set choices from data->edit_template */
291 data->edit_mode = EDIT_MODE_ADD;
292 gtk_frame_set_label(GTK_FRAME(data->edit_frame), _("Add option"));
293 fill_edit_frame(data, NULL, data->edit_template, data->edit_options_copy);
294 gtk_widget_show(data->edit_frame);
295 gtk_widget_grab_focus(data->edit_frame);
296 }
297
298 #define free_options(acts) g_list_free_full(acts, (GDestroyNotify)option_free)
299 static void option_free(LXHotkeyAttr *attr)
300 {
301 g_free(attr->name);
302 g_list_free_full(attr->values, g_free);
303 free_options(attr->subopts);
304 g_free(attr->desc);
305 g_slice_free(LXHotkeyAttr, attr);
306 }
307
308 static void on_remove(GtkAction *act, PluginData *data)
309 {
310 LXHotkeyAttr *opt, *parent;
311 GtkTreeModel *model;
312 GtkTreeIter iter, parent_iter;
313
314 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
315 &model, &iter))
316 /* no item selected */
317 return;
318 /* find and remove option from data->edit_options_copy */
319 gtk_tree_model_get(model, &iter, 2, &opt, -1);
320 if (gtk_tree_model_iter_parent(model, &parent_iter, &iter))
321 {
322 gtk_tree_model_get(model, &parent_iter, 2, &parent, -1);
323 parent->subopts = g_list_remove(parent->subopts, opt);
324 }
325 else
326 data->edit_options_copy = g_list_remove(data->edit_options_copy, opt);
327 option_free(opt);
328 /* remove selected row from model */
329 gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
330 gtk_action_set_sensitive(data->edit_apply_button, TRUE);
331 }
332
333 static void start_edit(GtkTreeModel *model, GtkTreeIter *iter, PluginData *data)
334 {
335 const LXHotkeyAttr *opt;
336 const GList *tmpl_list;
337 GList single = { .prev = NULL, .next = NULL };
338
339 /* name - only current from selection */
340 gtk_tree_model_get(model, iter, 2, &opt, -1);
341 /* values - from template */
342 tmpl_list = get_parent_template_list(model, iter, data);
343 if (data->current_page == data->acts &&
344 tmpl_list == data->edit_template) /* it's action */
345 return;
346 single.data = (gpointer)find_template_for_option(model, iter, tmpl_list);
347 if (single.data == NULL)
348 {
349 g_warning("no template found for option '%s'", opt->name);
350 return;
351 }
352 /* fill frame from selection */
353 data->edit_mode = EDIT_MODE_EDIT;
354 gtk_frame_set_label(GTK_FRAME(data->edit_frame), _("Change option"));
355 fill_edit_frame(data, opt, &single, NULL);
356 gtk_widget_show(data->edit_frame);
357 gtk_widget_grab_focus(data->edit_frame);
358 }
359
360 static void on_edit(GtkAction *act, PluginData *data)
361 {
362 GtkTreeModel *model;
363 GtkTreeIter iter;
364
365 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
366 &model, &iter))
367 /* no item selected */
368 return;
369 start_edit(model, &iter, data);
370 }
371
372 static void on_add_suboption(GtkAction *act, PluginData *data)
373 {
374 const LXHotkeyAttr *opt, *tmpl;
375 const GList *tmpl_list;
376 GtkTreeModel *model;
377 GtkTreeIter iter;
378
379 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
380 &model, &iter))
381 /* no item selected */
382 return;
383
384 tmpl_list = get_parent_template_list(model, &iter, data);
385 tmpl = find_template_for_option(model, &iter, tmpl_list);
386 if (tmpl == NULL)
387 /* no options found */
388 return;
389 tmpl_list = get_options_from_template(tmpl, data);
390 gtk_tree_model_get(model, &iter, 2, &opt, -1);
391 data->edit_mode = EDIT_MODE_OPTION;
392 /* fill frame with empty data and set name choices from selection's subopts */
393 if (tmpl->has_actions)
394 gtk_frame_set_label(GTK_FRAME(data->edit_frame), _("Add action"));
395 else
396 gtk_frame_set_label(GTK_FRAME(data->edit_frame), _("Add option"));
397 fill_edit_frame(data, NULL, tmpl_list, opt->subopts);
398 gtk_widget_show(data->edit_frame);
399 gtk_widget_grab_focus(data->edit_frame);
400 }
401
402 static const char edit_xml[] =
403 "<toolbar>"
404 "<toolitem action='Cancel'/>"
405 "<toolitem action='Save'/>"
406 "<separator/>"
407 "<toolitem action='AddAction'/>"
408 "<toolitem action='AddOption'/>"
409 "<toolitem action='Remove'/>"
410 "<toolitem action='Change'/>"
411 "<separator/>"
412 "<toolitem action='AddSubOption'/>"
413 /* "<separator/>"
414 "<toolitem action='Help'/>" */
415 "</toolbar>";
416
417 static GtkActionEntry actions[] =
418 {
419 { "Cancel", GTK_STOCK_CANCEL, NULL, NULL, N_("Discard changes"), G_CALLBACK(on_cancel) },
420 { "Save", GTK_STOCK_APPLY, NULL, NULL, N_("Accept changes"), G_CALLBACK(on_save) },
421 { "AddAction", GTK_STOCK_NEW, NULL, NULL, N_("Add an action"), G_CALLBACK(on_add_action) },
422 { "AddOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to this command"),
423 G_CALLBACK(on_add_option) },
424 { "Remove", GTK_STOCK_DELETE, NULL, "", N_("Remove selection"), G_CALLBACK(on_remove) },
425 { "Change", GTK_STOCK_EDIT, NULL, NULL, N_("Change selected option"), G_CALLBACK(on_edit) },
426 { "AddSubOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to selection"),
427 G_CALLBACK(on_add_suboption) }
428 };
429
430 /* Button for keybinding click - taken from LXPanel, simplified a bit */
431 static void on_focus_in_event(GtkButton *test, GdkEvent *event, PluginData *data)
432 {
433 gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(test)), TRUE, GDK_CURRENT_TIME);
434 }
435
436 static void on_focus_out_event(GtkButton *test, GdkEvent *event, PluginData *data)
437 {
438 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
439 }
440
441 static gboolean on_key_event(GtkButton *test, GdkEventKey *event, PluginData *data)
442 {
443 GdkModifierType state;
444 char *text;
445 const char *label;
446
447 /* ignore Tab completely so user can leave focus */
448 if (event->keyval == GDK_KEY_Tab)
449 return FALSE;
450 /* request mods directly, event->state isn't updated yet */
451 gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(test)),
452 NULL, NULL, &state);
453 /* special support for Win key, it doesn't work sometimes */
454 if ((state & GDK_SUPER_MASK) == 0 && (state & GDK_MOD4_MASK) != 0)
455 state |= GDK_SUPER_MASK;
456 state &= gtk_accelerator_get_default_mod_mask();
457 /* if mod key event then update test label and go */
458 if (event->is_modifier)
459 {
460 if (state != 0)
461 {
462 text = gtk_accelerator_get_label(0, state);
463 gtk_button_set_label(test, text);
464 g_free(text);
465 }
466 /* if no modifiers currently then show original state */
467 else
468 gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
469 return FALSE;
470 }
471 /* if not keypress query then ignore key press */
472 if (event->type != GDK_KEY_PRESS)
473 return FALSE;
474 /* if Escape pressed then reset to last saved */
475 if (state == 0 && event->keyval == GDK_KEY_Escape)
476 {
477 gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
478 goto _done;
479 }
480 /* if BackSpace pressed then just clear the button */
481 if (state == 0 && event->keyval == GDK_KEY_BackSpace)
482 {
483 gtk_button_set_label(test, "");
484 g_object_set_data(G_OBJECT(test), "accelerator_name", NULL);
485 g_object_set_data(G_OBJECT(test), "original_label", NULL);
486 _done:
487 gtk_action_set_sensitive(data->edit_apply_button,
488 ((label = gtk_button_get_label(GTK_BUTTON(data->edit_key1))) && label[0]) ||
489 ((gtk_button_get_label(GTK_BUTTON(data->edit_key2))) && label[0]));
490 if (data->edit_exec)
491 gtk_widget_grab_focus(GTK_WIDGET(data->edit_exec));
492 else
493 gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree));
494 return FALSE;
495 }
496 /* update the label now */
497 text = gtk_accelerator_get_label(event->keyval, state);
498 gtk_button_set_label(test, text);
499 /* drop single printable and printable with single Shift, Ctrl, Alt */
500 if (event->length != 0 && (state == 0 || state == GDK_SHIFT_MASK ||
501 state == GDK_CONTROL_MASK || state == GDK_MOD1_MASK) &&
502 /* but make an exception for Alt+Space, it should be acceptable */
503 (event->keyval != GDK_KEY_space || state != GDK_MOD1_MASK))
504 {
505 GtkWidget* dlg;
506 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
507 _("Key combination '%s' cannot be used as"
508 " a global hotkey, sorry."), text);
509 g_free(text);
510 gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
511 gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
512 gtk_dialog_run(GTK_DIALOG(dlg));
513 gtk_widget_destroy(dlg);
514 gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
515 gtk_action_set_sensitive(data->edit_apply_button,
516 ((label = gtk_button_get_label(GTK_BUTTON(data->edit_key1))) && label[0]) ||
517 ((gtk_button_get_label(GTK_BUTTON(data->edit_key2))) && label[0]));
518 return FALSE;
519 }
520 /* save new value now */
521 g_object_set_data_full(G_OBJECT(test), "original_label", text, g_free);
522 text = gtk_accelerator_name(event->keyval, state);
523 g_object_set_data_full(G_OBJECT(test), "accelerator_name", text, g_free);
524 gtk_action_set_sensitive(data->edit_apply_button, TRUE);
525 /* change focus onto exec line or actions tree now */
526 if (data->edit_exec)
527 gtk_widget_grab_focus(GTK_WIDGET(data->edit_exec));
528 else
529 gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree));
530 return FALSE;
531 }
532
533 static GtkWidget *key_button_new(PluginData *data, const char *hotkey)
534 {
535 GtkWidget *w;
536 char *label;
537 guint keyval = 0;
538 GdkModifierType state = 0;
539
540 if (hotkey)
541 gtk_accelerator_parse(hotkey, &keyval, &state);
542 label = gtk_accelerator_get_label(keyval, state);
543 w = gtk_button_new_with_label(label);
544 g_object_set_data_full(G_OBJECT(w), "accelerator_name", g_strdup(hotkey), g_free);
545 g_object_set_data_full(G_OBJECT(w), "original_label", label, g_free);
546 g_signal_connect(w, "focus-in-event", G_CALLBACK(on_focus_in_event), data);
547 g_signal_connect(w, "focus-out-event", G_CALLBACK(on_focus_out_event), data);
548 g_signal_connect(w, "key-press-event", G_CALLBACK(on_key_event), data);
549 g_signal_connect(w, "key-release-event", G_CALLBACK(on_key_event), data);
550 return w;
551 }
552
553 #if !GLIB_CHECK_VERSION(2, 34, 0)
554 static GList *g_list_copy_deep(GList *list, GCopyFunc func, gpointer user_data)
555 {
556 GList *copy = NULL;
557
558 while (list)
559 {
560 copy = g_list_prepend(copy, func(list->data, user_data));
561 list = list->next;
562 }
563 return g_list_reverse(copy);
564 }
565 #endif
566
567 /* used by edit_action() to be able to edit */
568 static GList *copy_options(GList *orig)
569 {
570 GList *copy = NULL;
571
572 /* copy contents recursively */
573 while (orig)
574 {
575 LXHotkeyAttr *attr = g_slice_new(LXHotkeyAttr);
576 LXHotkeyAttr *attr_orig = orig->data;
577
578 attr->name = g_strdup(attr_orig->name);
579 attr->values = g_list_copy_deep(attr_orig->values, (GCopyFunc)g_strdup, NULL);
580 attr->subopts = copy_options(attr_orig->subopts);
581 attr->desc = g_strdup(attr_orig->desc);
582 attr->has_actions = FALSE;
583 attr->has_value = FALSE;
584 copy = g_list_prepend(copy, attr);
585 orig = orig->next;
586 }
587 return g_list_reverse(copy);
588 }
589
590 static void add_options_to_tree(GtkTreeStore *store, GtkTreeIter *parent_iter,
591 GList *list)
592 {
593 LXHotkeyAttr *opt;
594 GtkTreeIter iter;
595 const char *val;
596
597 while (list)
598 {
599 opt = list->data;
600 val = opt->values ? opt->values->data : NULL;
601 gtk_tree_store_insert_with_values(store, &iter, parent_iter, -1,
602 0, opt->name,
603 1, val,
604 2, opt,
605 3, _(opt->name),
606 4, (val && val[0]) ? _(val) : NULL, -1);
607 if (opt->subopts)
608 add_options_to_tree(store, &iter, opt->subopts);
609 list = list->next;
610 }
611 }
612
613 static void update_options_tree(PluginData *data)
614 {
615 GtkTreeStore *store = gtk_tree_store_new(5, G_TYPE_STRING, /* option name */
616 G_TYPE_STRING, /* option value */
617 G_TYPE_POINTER, /* LXHotkeyAttr */
618 G_TYPE_STRING, /* shown name */
619 G_TYPE_STRING); /* shown value */
620
621 add_options_to_tree(store, NULL, data->edit_options_copy);
622 gtk_tree_view_set_model(data->edit_tree, GTK_TREE_MODEL(store));
623 gtk_tree_view_expand_all(data->edit_tree);
624 g_object_unref(store);
625 }
626
627 #define edit_is_active(data) (data->edit_mode != EDIT_MODE_NONE)
628
629 static void cancel_edit(PluginData *data)
630 {
631 data->edit_mode = EDIT_MODE_NONE;
632 gtk_widget_hide(data->edit_frame);
633 }
634
635 static void on_exec_changed(GtkEntry *exec, PluginData *data)
636 {
637 const char *value;
638
639 //FIXME: compare with original exec? is that too heavy?
640 if (((value = gtk_button_get_label(GTK_BUTTON(data->edit_key1))) == NULL || value[0] == 0) &&
641 ((value = gtk_button_get_label(GTK_BUTTON(data->edit_key2))) == NULL || value[0] == 0))
642 gtk_action_set_sensitive(data->edit_apply_button, FALSE);
643 else
644 gtk_action_set_sensitive(data->edit_apply_button, TRUE);
645 }
646
647 static void on_row_activated(GtkTreeView *view, GtkTreePath *path,
648 GtkTreeViewColumn *column, PluginData *data)
649 {
650 GtkTreeModel *model;
651 GtkTreeIter iter;
652
653 model = gtk_tree_view_get_model(view);
654 if (!gtk_tree_model_get_iter(model, &iter, path))
655 /* invalid path */
656 return;
657 start_edit(model, &iter, data);
658 }
659
660 static void on_selection_changed(GtkTreeSelection *selection, PluginData *data)
661 {
662 if (edit_is_active(data))
663 //FIXME: ask confirmation and revert, is that possible?
664 cancel_edit(data);
665 //update toolbar buttons visibility
666 update_edit_toolbar(data);
667 }
668
669 static void on_option_changed(GtkComboBox *box, PluginData *data)
670 {
671 const LXHotkeyAttr *opt, *tmpl;
672 const GList *values;
673 GtkTreeModel *model;
674 GtkListStore *values_store;
675 GtkTreeIter iter;
676 int i, sel;
677 gboolean is_action = FALSE;
678
679 /* g_debug("on_option_changed"); */
680 opt = NULL;
681 if (data->edit_mode == EDIT_MODE_ADD)
682 is_action = (data->current_page == data->acts);
683 else if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
684 &model, &iter))
685 {
686 if (data->edit_mode == EDIT_MODE_EDIT)
687 {
688 gtk_tree_model_get(model, &iter, 2, &opt, -1);
689 if (data->current_page == data->acts)
690 is_action = (get_parent_template_list(model, &iter, data) == data->edit_template);
691 }
692 else /* EDIT_MODE_OPTION */
693 {
694 tmpl = find_template_for_option(model, &iter,
695 get_parent_template_list(model, &iter, data));
696 if (tmpl)
697 is_action = tmpl->has_actions;
698 }
699 }
700 if (!gtk_combo_box_get_active_iter(box, &iter))
701 /* no item selected */
702 return;
703 gtk_tree_model_get(gtk_combo_box_get_model(box), &iter, 2, &tmpl, -1);
704 if (tmpl->has_actions || is_action ||
705 (tmpl->subopts != NULL && !tmpl->has_value))
706 {
707 /* either it's action, or option has suboptions instead of values */
708 gtk_widget_hide(data->edit_value_label);
709 gtk_widget_hide(GTK_WIDGET(data->edit_value));
710 gtk_widget_hide(GTK_WIDGET(data->edit_values));
711 gtk_widget_hide(data->edit_value_num);
712 gtk_widget_hide(data->edit_value_num_label);
713 }
714 else if ((values = tmpl->values) != NULL)
715 {
716 values_store = GTK_LIST_STORE(gtk_list_store_new(2, G_TYPE_STRING, /* description */
717 G_TYPE_STRING)); /* value */
718 for (sel = 0, i = 0; values; values = values->next, i++)
719 {
720 gtk_list_store_insert_with_values(values_store, NULL, i, 0, _(values->data),
721 1, values->data, -1);
722 if (opt && opt->values)
723 {
724 if (((char *)values->data)[0] == '#')
725 {
726 size_t len = strspn(opt->values->data, "0123456789");
727 /* test if value is an integer number */
728 if (len == strlen(opt->values->data))
729 sel = i;
730 }
731 else if (((char *)values->data)[0] == '%')
732 {
733 const char *str = opt->values->data;
734 size_t len = strspn(str, "0123456789");
735 /* test if value is either a fraction or a percent value */
736 if (len > 0 && (str[len] == '%' || str[len] == '/'))
737 sel = i;
738 }
739 else if (g_strcmp0(opt->values->data, values->data) == 0)
740 sel = i;
741 }
742 }
743 gtk_combo_box_set_model(data->edit_values, GTK_TREE_MODEL(values_store));
744 g_object_unref(values_store);
745 gtk_combo_box_set_active(data->edit_values, sel);
746 gtk_widget_show(data->edit_value_label);
747 gtk_widget_show(GTK_WIDGET(data->edit_values));
748 gtk_widget_hide(GTK_WIDGET(data->edit_value));
749 }
750 else
751 {
752 gtk_widget_show(data->edit_value_label);
753 gtk_widget_hide(data->edit_value_num);
754 gtk_widget_hide(data->edit_value_num_label);
755 gtk_widget_hide(GTK_WIDGET(data->edit_values));
756 gtk_widget_show(GTK_WIDGET(data->edit_value));
757 if (opt && opt->values)
758 gtk_entry_set_text(data->edit_value, opt->values->data);
759 else
760 gtk_entry_set_text(data->edit_value, "");
761 }
762 }
763
764 static void on_value_changed(GtkComboBox *box, PluginData *data)
765 {
766 LXHotkeyAttr *opt;
767 const char *value;
768 GtkTreeModel *model;
769 GtkTreeIter iter;
770 gdouble num = 0;
771 long div;
772
773 model = gtk_combo_box_get_model(box);
774 if (!gtk_combo_box_get_active_iter(box, &iter))
775 /* no value chosen */
776 goto _general;
777 gtk_tree_model_get(model, &iter, 1, &value, -1);
778 if (!value)
779 /* illegal really */
780 goto _general;
781 if (value[0] == '#')
782 {
783 /* if value is # then show data->edit_value_num */
784 gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
785 -1000.0, +1000.0);
786 if (data->edit_mode == EDIT_MODE_EDIT &&
787 gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
788 &model, &iter))
789 {
790 gtk_tree_model_get(model, &iter, 2, &opt, -1);
791 if (opt && opt->values)
792 num = strtol(opt->values->data, NULL, 10);
793 }
794 gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->edit_value_num), num);
795 gtk_widget_show(data->edit_value_num);
796 gtk_widget_hide(data->edit_value_num_label);
797 }
798 else if (value[0] == '%')
799 {
800 /* if value is % then show data->edit_value_num with label "%" */
801 gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
802 0.0, +100.0);
803 if (data->edit_mode == EDIT_MODE_EDIT &&
804 gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
805 &model, &iter))
806 {
807 gtk_tree_model_get(model, &iter, 2, &opt, -1);
808 if (opt && opt->values)
809 {
810 value = opt->values->data;
811 num = strtol(value, (char **)&value, 10);
812 if (*value == '/')
813 {
814 /* convert fraction into percent */
815 div = strtol(value + 1, NULL, 10);
816 div = MAX(div, 1);
817 num *= 100 / div;
818 }
819 }
820 }
821 gtk_spin_button_set_value(GTK_SPIN_BUTTON(data->edit_value_num), num);
822 gtk_widget_show(data->edit_value_num);
823 gtk_label_set_text(GTK_LABEL(data->edit_value_num_label), "%");
824 gtk_widget_show(data->edit_value_num_label);
825 }
826 else
827 {
828 _general:
829 /* else hide both data->edit_value_num and label */
830 gtk_widget_hide(data->edit_value_num);
831 gtk_widget_hide(data->edit_value_num_label);
832 }
833 }
834
835 static void apply_options(PluginData *data, LXHotkeyAttr *opt)
836 {
837 GtkTreeModel *model;
838 GtkTreeIter iter;
839 char *name, *_value = NULL;
840 const char *value = NULL;
841 gboolean changed = FALSE;
842
843 /* process name */
844 if (gtk_widget_get_visible(GTK_WIDGET(data->edit_actions)) &&
845 gtk_combo_box_get_active_iter(data->edit_actions, &iter))
846 {
847 model = gtk_combo_box_get_model(data->edit_actions);
848 gtk_tree_model_get(model, &iter, 1, &name, -1);
849 if (g_strcmp0(opt->name, name) == 0)
850 g_free(name);
851 else
852 {
853 g_free(opt->name);
854 opt->name = name;
855 changed = TRUE;
856 }
857 }
858 /* process value */
859 if (gtk_widget_get_visible(data->edit_value_num))
860 {
861 gdouble num_val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(data->edit_value_num));
862 if (gtk_widget_get_visible(data->edit_value_num_label))
863 value = _value = g_strdup_printf("%d%s", (int)num_val,
864 gtk_label_get_text(GTK_LABEL(data->edit_value_num_label)));
865 else
866 value = _value = g_strdup_printf("%d", (int)num_val);
867 }
868 else if (gtk_widget_get_visible(GTK_WIDGET(data->edit_values)) &&
869 gtk_combo_box_get_active_iter(data->edit_values, &iter))
870 {
871 model = gtk_combo_box_get_model(data->edit_values);
872 gtk_tree_model_get(model, &iter, 1, &_value, -1);
873 value = _value;
874 }
875 else if (gtk_widget_get_visible(GTK_WIDGET(data->edit_value)))
876 {
877 value = gtk_entry_get_text(data->edit_value);
878 }
879 if (opt->values && g_strcmp0(opt->values->data, value) == 0)
880 g_free(_value);
881 else
882 {
883 if (_value == NULL)
884 value = g_strdup(value);
885 if (opt->values == NULL)
886 opt->values = g_list_prepend(NULL, (gpointer)value);
887 else
888 {
889 g_free(opt->values->data);
890 opt->values->data = (gpointer)value;
891 }
892 changed = TRUE;
893 }
894 /* process changed state */
895 if (((value = gtk_button_get_label(GTK_BUTTON(data->edit_key1))) == NULL || value[0] == 0) &&
896 ((value = gtk_button_get_label(GTK_BUTTON(data->edit_key2))) == NULL || value[0] == 0))
897 gtk_action_set_sensitive(data->edit_apply_button, FALSE);
898 else if (changed)
899 gtk_action_set_sensitive(data->edit_apply_button, TRUE);
900 }
901
902 static void on_apply_button(GtkButton *btn, PluginData *data)
903 {
904 LXHotkeyAttr *opt;
905 GtkTreeModel *model;
906 GtkTreeIter iter;
907 const char *val;
908
909 switch (data->edit_mode)
910 {
911 case EDIT_MODE_ADD:
912 opt = g_slice_new0(LXHotkeyAttr);
913 apply_options(data, opt);
914 /* insert new option/action */
915 data->edit_options_copy = g_list_append(data->edit_options_copy, opt);
916 model = gtk_tree_view_get_model(data->edit_tree);
917 /* update the tree */
918 val = opt->values ? opt->values->data : NULL;
919 gtk_tree_store_insert_with_values(GTK_TREE_STORE(model), NULL, NULL, -1,
920 0, opt->name,
921 1, val,
922 2, opt,
923 3, _(opt->name),
924 4, (val && val[0]) ? _(val) : NULL, -1);
925 /* update toolbar */
926 update_edit_toolbar(data);
927 break;
928 case EDIT_MODE_EDIT:
929 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
930 &model, &iter))
931 {
932 gtk_tree_model_get(model, &iter, 2, &opt, -1);
933 apply_options(data, opt);
934 val = opt->values ? opt->values->data : NULL;
935 gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
936 1, val,
937 4, (val && val[0]) ? _(val) : NULL, -1);
938 update_edit_toolbar(data);
939 }
940 break;
941 case EDIT_MODE_OPTION:
942 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
943 &model, &iter))
944 {
945 LXHotkeyAttr *parent;
946 gtk_tree_model_get(model, &iter, 2, &parent, -1);
947 opt = g_slice_new0(LXHotkeyAttr);
948 apply_options(data, opt);
949 parent->subopts = g_list_append(parent->subopts, opt);
950 model = gtk_tree_view_get_model(data->edit_tree);
951 val = opt->values ? opt->values->data : NULL;
952 gtk_tree_store_insert_with_values(GTK_TREE_STORE(model), NULL, &iter, -1,
953 0, opt->name,
954 1, val,
955 2, opt,
956 3, _(opt->name),
957 4, (val && val[0]) ? _(val) : NULL, -1);
958 gtk_tree_view_expand_all(data->edit_tree);
959 update_edit_toolbar(data);
960 }
961 break;
962 case EDIT_MODE_NONE:
963 default:
964 break;
965 }
966 cancel_edit(data);
967 }
968
969 static void on_cancel_button(GtkButton *btn, PluginData *data)
970 {
971 cancel_edit(data);
972 }
973
974 /* free all allocated data */
975 void _edit_cleanup(PluginData *data)
976 {
977 if (data->edit_window)
978 {
979 cancel_edit(data);
980 g_object_remove_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
981 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
982 data->edit_window = NULL;
983 }
984 if (data->edit_options_copy)
985 {
986 free_options(data->edit_options_copy);
987 data->edit_options_copy = NULL;
988 }
989 }
990
991 void _edit_action(PluginData *data, GError **error)
992 {
993 LXHotkeyGlobal *act = NULL;
994 LXHotkeyApp *app = NULL;
995 const char *accel1 = NULL, *accel2 = NULL;
996 GtkBox *vbox, *xbox;
997 GtkUIManager *ui;
998 GtkActionGroup *act_grp;
999 GtkAccelGroup *accel_grp;
1000 GtkWidget *widget, *align;
1001 GtkToolbar *toolbar;
1002 GtkCellRenderer *column;
1003 GtkTreeModel *model;
1004 GtkTreeIter iter;
1005 gboolean is_action = FALSE;
1006
1007 if (data->edit_window)
1008 {
1009 /* OOPS, another edit is still opened */
1010 return;
1011 }
1012 /* do cleanup */
1013 _edit_cleanup(data);
1014 /* get a template */
1015 if (data->current_page == data->acts)
1016 {
1017 if (data->cb->get_wm_actions == NULL) /* not available for edit */
1018 return;
1019 if (data->cb->set_wm_key == NULL) /* not available for save */
1020 return;
1021 data->edit_template = data->cb->get_wm_actions(*data->config, NULL);
1022 //FIXME: test for error
1023 is_action = TRUE;
1024 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->acts),
1025 &model, &iter))
1026 gtk_tree_model_get(model, &iter, 4, &act, -1);
1027 if (act)
1028 {
1029 /* if there is a selection then copy its options */
1030 data->edit_options_copy = copy_options(act->actions);
1031 accel1 = act->accel1;
1032 accel2 = act->accel2;
1033 }
1034 }
1035 else
1036 {
1037 if (data->cb->get_app_options == NULL) /* not available for edit */
1038 return;
1039 if (data->cb->set_app_key == NULL) /* not available for save */
1040 return;
1041 data->edit_template = data->cb->get_app_options(*data->config, NULL);
1042 //FIXME: test for error
1043 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->apps),
1044 &model, &iter))
1045 gtk_tree_model_get(model, &iter, 3, &app, -1);
1046 if (app)
1047 {
1048 /* if there is a selection then copy its options */
1049 data->edit_options_copy = copy_options(app->options);
1050 accel1 = app->accel1;
1051 accel2 = app->accel2;
1052 }
1053 }
1054
1055 /* create a window with a GtkVBox inside */
1056 data->edit_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
1057 gtk_window_set_default_size(data->edit_window, 240, 10);
1058 gtk_window_set_transient_for(data->edit_window,
1059 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(data->notebook))));
1060 g_object_add_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
1061 vbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
1062
1063 /* add the toolbar */
1064 ui = gtk_ui_manager_new();
1065 act_grp = gtk_action_group_new("Edit");
1066 gtk_action_group_set_translation_domain(act_grp, NULL);
1067 gtk_action_group_add_actions(act_grp, actions, G_N_ELEMENTS(actions), data);
1068 accel_grp = gtk_ui_manager_get_accel_group(ui);
1069 gtk_window_add_accel_group(GTK_WINDOW(data->edit_window), accel_grp);
1070 gtk_ui_manager_insert_action_group(ui, act_grp, 0);
1071 gtk_ui_manager_add_ui_from_string(ui, edit_xml, -1, NULL);
1072 g_object_unref(act_grp);
1073 widget = gtk_ui_manager_get_widget(ui, "/toolbar");
1074 toolbar = GTK_TOOLBAR(widget);
1075 //TODO: 'Change' -- also 2click and Enter
1076 gtk_toolbar_set_icon_size(toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR);
1077 gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS);
1078 gtk_toolbar_set_show_arrow(toolbar, FALSE);
1079 data->edit_apply_button = gtk_ui_manager_get_action(ui, "/toolbar/Save");
1080 data->add_option_button = gtk_ui_manager_get_action(ui, "/toolbar/AddOption");
1081 data->rm_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Remove");
1082 data->edit_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Change");
1083 data->add_suboption_button = gtk_ui_manager_get_action(ui, "/toolbar/AddSubOption");
1084 gtk_action_set_sensitive(data->edit_apply_button, FALSE);
1085 gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
1086
1087 /* add frames for accel1 and accel2 */
1088 xbox = (GtkBox *)gtk_hbox_new(TRUE, 0);
1089 widget = gtk_frame_new(_("Hotkey 1"));
1090 data->edit_key1 = key_button_new(data, accel1);
1091 gtk_container_add(GTK_CONTAINER(widget), data->edit_key1);
1092 gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
1093 widget = gtk_frame_new(_("Hotkey 2"));
1094 data->edit_key2 = key_button_new(data, accel2);
1095 gtk_container_add(GTK_CONTAINER(widget), data->edit_key2);
1096 gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
1097 gtk_box_pack_start(vbox, GTK_WIDGET(xbox), FALSE, TRUE, 0);
1098
1099 /* add frame with all options */
1100 widget = gtk_frame_new(NULL);
1101 gtk_frame_set_shadow_type(GTK_FRAME(widget), GTK_SHADOW_IN);
1102 xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
1103 if (is_action)
1104 {
1105 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
1106 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Actions:")));
1107 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1108 data->edit_exec = NULL;
1109 }
1110 else
1111 {
1112 /* for application add a GtkEntry for exec line */
1113 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
1114 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Command line:")));
1115 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1116 data->edit_exec = GTK_ENTRY(gtk_entry_new());
1117 g_signal_connect(data->edit_exec, "changed", G_CALLBACK(on_exec_changed), data);
1118 if (app && app->exec)
1119 gtk_entry_set_text(data->edit_exec, app->exec);
1120 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
1121 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
1122 gtk_container_add(GTK_CONTAINER(align), GTK_WIDGET(data->edit_exec));
1123 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1124 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
1125 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Options:")));
1126 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1127 }
1128 data->edit_tree = GTK_TREE_VIEW(gtk_tree_view_new());
1129 gtk_box_pack_start(xbox, GTK_WIDGET(data->edit_tree), TRUE, TRUE, 0);
1130 gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(xbox));
1131 gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
1132 gtk_tree_view_insert_column_with_attributes(data->edit_tree, 0, NULL,
1133 gtk_cell_renderer_text_new(),
1134 "text", 3, NULL);
1135 gtk_tree_view_insert_column_with_attributes(data->edit_tree, 1, NULL,
1136 gtk_cell_renderer_text_new(),
1137 "text", 4, NULL);
1138 gtk_tree_view_set_headers_visible(data->edit_tree, FALSE);
1139 g_signal_connect(data->edit_tree, "row-activated", G_CALLBACK(on_row_activated), data);
1140
1141 /* frame with fields for editing, hidden for now */
1142 data->edit_frame = gtk_frame_new(NULL);
1143 xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
1144 /* combobox for option/action name */
1145 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
1146 widget = gtk_label_new(NULL);
1147 gtk_label_set_markup(GTK_LABEL(widget), _("<b>Name:</b>"));
1148 gtk_container_add(GTK_CONTAINER(align), widget);
1149 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1150 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
1151 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
1152 widget = gtk_hbox_new(FALSE, 0);
1153 data->edit_option_name = gtk_label_new(NULL);
1154 gtk_box_pack_start(GTK_BOX(widget), data->edit_option_name, FALSE, TRUE, 0);
1155 data->edit_actions = (GtkComboBox *)gtk_combo_box_new();
1156 g_signal_connect(data->edit_actions, "changed",
1157 G_CALLBACK(on_option_changed), data);
1158 column = gtk_cell_renderer_text_new();
1159 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_actions), column, TRUE);
1160 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_actions), column,
1161 "text", 0, NULL);
1162 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_actions), TRUE, TRUE, 0);
1163 gtk_container_add(GTK_CONTAINER(align), widget);
1164 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1165 /* entry or combobox for option value */
1166 data->edit_value_label = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
1167 widget = gtk_label_new(NULL);
1168 gtk_label_set_markup(GTK_LABEL(widget), _("<b>Value:</b>"));
1169 gtk_container_add(GTK_CONTAINER(data->edit_value_label), widget);
1170 gtk_box_pack_start(xbox, data->edit_value_label, FALSE, TRUE, 0);
1171 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
1172 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
1173 widget = gtk_hbox_new(FALSE, 0);
1174 data->edit_value = (GtkEntry *)gtk_entry_new();
1175 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value), TRUE, TRUE, 0);
1176 data->edit_values = (GtkComboBox *)gtk_combo_box_new();
1177 column = gtk_cell_renderer_text_new();
1178 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_values), column, TRUE);
1179 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_values), column,
1180 "text", 0, NULL);
1181 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_values), TRUE, TRUE, 0);
1182 g_signal_connect(data->edit_values, "changed",
1183 G_CALLBACK(on_value_changed), data);
1184 data->edit_value_num = gtk_spin_button_new_with_range(-1000.0, +1000.0, 1.0);
1185 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num), FALSE, TRUE, 0);
1186 data->edit_value_num_label = gtk_label_new(NULL);
1187 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num_label), FALSE, TRUE, 0);
1188 gtk_container_add(GTK_CONTAINER(align), widget);
1189 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1190 /* buttons 'Cancel' and 'Apply' */
1191 align = gtk_alignment_new(1.0, 0.0, 0.0, 0.0);
1192 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
1193 widget = gtk_hbox_new(TRUE, 4);
1194 gtk_container_add(GTK_CONTAINER(align), widget);
1195 align = gtk_button_new_from_stock(GTK_STOCK_APPLY); /* reuse align */
1196 g_signal_connect(align, "clicked", G_CALLBACK(on_apply_button), data);
1197 gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
1198 align = gtk_button_new_from_stock(GTK_STOCK_CANCEL); /* reuse align */
1199 g_signal_connect(align, "clicked", G_CALLBACK(on_cancel_button), data);
1200 gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
1201 gtk_container_add(GTK_CONTAINER(data->edit_frame), GTK_WIDGET(xbox));
1202 gtk_box_pack_start(vbox, data->edit_frame, TRUE, TRUE, 0);
1203
1204 gtk_widget_show_all(GTK_WIDGET(vbox));
1205 gtk_widget_hide(data->edit_frame);
1206 /* hide one of AddAction or AddOption */
1207 if (is_action)
1208 {
1209 /* AddOption is visible for exec only */
1210 gtk_action_set_visible(data->add_option_button, FALSE);
1211 }
1212 else
1213 {
1214 /* AddAction is visible for action only */
1215 GtkAction *act = gtk_ui_manager_get_action(ui, "/toolbar/AddAction");
1216 gtk_action_set_visible(act, FALSE);
1217 }
1218 gtk_container_add(GTK_CONTAINER(data->edit_window), GTK_WIDGET(vbox));
1219 g_signal_connect(gtk_tree_view_get_selection(data->edit_tree), "changed",
1220 G_CALLBACK(on_selection_changed), data);
1221 update_options_tree(data);
1222 update_edit_toolbar(data);
1223 gtk_window_present(data->edit_window);
1224 gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree));
1225 }