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