GTK+ plugin: implementation of edit window. No save available yet, just look.
[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 <glib/gi18n-lib.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33
34 #if !GTK_CHECK_VERSION(2, 21, 0)
35 # define GDK_KEY_Tab GDK_Tab
36 #endif
37
38 enum {
39 EDIT_MODE_NONE,
40 EDIT_MODE_ADD, /* add action */
41 EDIT_MODE_EDIT, /* change selected */
42 EDIT_MODE_OPTION /* add suboption */
43 };
44
45 static const LXHotkeyAttr *find_template_for_option(GtkTreeModel *model,
46 GtkTreeIter *iter,
47 const GList *t_list)
48 {
49 const LXHotkeyAttr *opt, *tmpl;
50
51 gtk_tree_model_get(model, iter, 2, &opt, -1);
52 while (t_list)
53 {
54 tmpl = t_list->data;
55 if (g_strcmp0(tmpl->name, opt->name) == 0)
56 return tmpl;
57 t_list = t_list->next;
58 }
59 return NULL;
60 }
61
62 static const GList *get_options_from_template(const LXHotkeyAttr *tmpl, PluginData *data)
63 {
64 if (tmpl == NULL)
65 return NULL;
66 else if (tmpl->has_actions)
67 return data->edit_template;
68 else
69 return tmpl->subopts;
70 }
71
72 static const GList *get_parent_template_list(GtkTreeModel *model, GtkTreeIter *iter,
73 PluginData *data)
74 {
75 const GList *tmpl_list;
76 GtkTreeIter parent_iter;
77 const LXHotkeyAttr *parent;
78
79 if (!gtk_tree_model_iter_parent(model, &parent_iter, iter))
80 return data->edit_template;
81 /* get list for parent of parent first - recursion is here */
82 tmpl_list = get_parent_template_list(model, &parent_iter, data);
83 /* now find parent in that list */
84 parent = find_template_for_option(model, &parent_iter, tmpl_list);
85 return get_options_from_template(parent, data);
86 }
87
88 static void fill_edit_frame(PluginData *data, const LXHotkeyAttr *opt,
89 const GList *subopts, const GList *exempt)
90 {
91 GtkListStore *names_store;
92 const LXHotkeyAttr *sub;
93 const GList *l;
94 int i = 0;
95
96 names_store = GTK_LIST_STORE(gtk_list_store_new(3, G_TYPE_STRING, /* description */
97 G_TYPE_STRING, /* name */
98 G_TYPE_POINTER)); /* template */
99 while (subopts)
100 {
101 sub = subopts->data;
102 /* ignore existing opts */
103 for (l = exempt; l; l = l->next)
104 if (strcmp(sub->name, ((LXHotkeyAttr *)l->data)->name) == 0)
105 break;
106 if (l == NULL)
107 gtk_list_store_insert_with_values(names_store, NULL, i++, 0, _(sub->name),
108 1, sub->name,
109 2, sub, -1);
110 subopts = subopts->next;
111 }
112 gtk_combo_box_set_model(data->edit_actions, GTK_TREE_MODEL(names_store));
113 g_object_unref(names_store);
114 gtk_combo_box_set_active(data->edit_actions, 0);
115 /* values box will be set by changing active callback */
116 gtk_widget_set_visible(GTK_WIDGET(data->edit_actions), opt == NULL); /* visible on add */
117 gtk_widget_set_visible(data->edit_option_name, opt != NULL); /* visible on edit */
118 if (opt)
119 gtk_label_set_text(GTK_LABEL(data->edit_option_name), _(opt->name));
120 }
121
122 static void update_edit_toolbar(PluginData *data)
123 {
124 const LXHotkeyAttr *opt, *tmpl;
125 const GList *tmpl_list;
126 GtkTreeModel *model;
127 GtkTreeIter iter;
128
129 /* update AddOption button -- exec only */
130 if (gtk_action_get_visible(data->add_option_button))
131 {
132 /* make not clickable if no more options to add */
133 gtk_action_set_sensitive(data->add_option_button,
134 g_list_length((GList *)data->edit_template) != g_list_length(data->edit_options_copy));
135 }
136 /* update Remove button */
137 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
138 &model, &iter))
139 {
140 gtk_action_set_sensitive(data->rm_option_button, FALSE);
141 gtk_action_set_sensitive(data->edit_option_button, FALSE);
142 gtk_action_set_sensitive(data->add_suboption_button, FALSE);
143 return;
144 }
145 gtk_action_set_sensitive(data->rm_option_button, TRUE);
146 gtk_tree_model_get(model, &iter, 2, &opt, -1);
147 tmpl_list = get_parent_template_list(model, &iter, data);
148 while (tmpl_list)
149 {
150 tmpl = tmpl_list->data;
151 if (g_strcmp0(tmpl->name, opt->name) == 0)
152 break;
153 tmpl_list = tmpl_list->next;
154 }
155 if (G_UNLIKELY(tmpl_list == NULL))
156 {
157 /* option isn't supported, probably deprecated one */
158 gtk_action_set_sensitive(data->edit_option_button, FALSE);
159 gtk_action_set_sensitive(data->add_suboption_button, FALSE);
160 return;
161 }
162 gtk_action_set_sensitive(data->edit_option_button,
163 (tmpl->subopts == NULL || tmpl->has_value));
164 gtk_action_set_sensitive(data->add_suboption_button,
165 g_list_length(tmpl->subopts) != g_list_length(opt->subopts));
166 }
167
168 static void on_cancel(GtkAction *act, PluginData *data)
169 {
170 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
171 }
172
173 static void on_save(GtkAction *act, PluginData *data)
174 {
175 // a) if actions list or command line changed then remove old binding and add new
176 // b) else if keys changed then update binding
177 // c) else skip (d) and (e)
178 // d) gtk_action_set_sensitive(data->save_action, TRUE)
179 // e) _main_refresh(data)
180 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
181 }
182
183 static void on_add_action(GtkAction *act, PluginData *data)
184 {
185 data->edit_mode = EDIT_MODE_ADD;
186 /* fill frame with empty data, set choices from data->edit_template, hide value */
187 fill_edit_frame(data, NULL, data->edit_template, NULL);
188 gtk_widget_hide(GTK_WIDGET(data->edit_values));
189 gtk_widget_hide(GTK_WIDGET(data->edit_value));
190 gtk_widget_show(data->edit_frame);
191 gtk_widget_grab_focus(data->edit_frame);
192 }
193
194 static void on_add_option(GtkAction *act, PluginData *data)
195 {
196 data->edit_mode = EDIT_MODE_ADD;
197 /* fill frame with empty data, set choices from data->edit_template */
198 fill_edit_frame(data, NULL, data->edit_template, data->edit_options_copy);
199 gtk_widget_show(data->edit_frame);
200 gtk_widget_grab_focus(data->edit_frame);
201 }
202
203 static void on_remove(GtkAction *act, PluginData *data)
204 {
205 //find and remove option from data->edit_options_copy
206 //remove selected row from model
207 }
208
209 static void on_edit(GtkAction *act, PluginData *data)
210 {
211 const LXHotkeyAttr *opt;
212 const GList *tmpl_list;
213 GtkTreeModel *model;
214 GtkTreeIter iter;
215 GList single = { .prev = NULL, .next = NULL };
216
217 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
218 &model, &iter))
219 /* no item selected */
220 return;
221 /* name - only current from selection */
222 gtk_tree_model_get(model, &iter, 2, &opt, -1);
223 /* values - from template */
224 tmpl_list = get_parent_template_list(model, &iter, data);
225 if (tmpl_list == data->edit_template) /* it's action */
226 return;
227 single.data = (gpointer)find_template_for_option(model, &iter, tmpl_list);
228 if (single.data == NULL)
229 {
230 g_warning("no template found for option '%s'", opt->name);
231 return;
232 }
233 data->edit_mode = EDIT_MODE_EDIT;
234 /* fill frame from selection */
235 fill_edit_frame(data, opt, &single, NULL);
236 gtk_widget_show(data->edit_frame);
237 gtk_widget_grab_focus(data->edit_frame);
238 }
239
240 static void on_add_suboption(GtkAction *act, PluginData *data)
241 {
242 const LXHotkeyAttr *opt, *tmpl;
243 const GList *tmpl_list;
244 GtkTreeModel *model;
245 GtkTreeIter iter;
246
247 if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
248 &model, &iter))
249 /* no item selected */
250 return;
251
252 tmpl_list = get_parent_template_list(model, &iter, data);
253 tmpl = find_template_for_option(model, &iter, tmpl_list);
254 if (tmpl == NULL)
255 /* no options found */
256 return;
257
258 tmpl_list = get_options_from_template(tmpl, data);
259 gtk_tree_model_get(model, &iter, 2, &opt, -1);
260 data->edit_mode = EDIT_MODE_OPTION;
261 /* fill frame with empty data and set name choices from selection's subopts */
262 fill_edit_frame(data, NULL, tmpl_list, opt->subopts);
263 gtk_widget_show(data->edit_frame);
264 gtk_widget_grab_focus(data->edit_frame);
265 }
266
267 static const char edit_xml[] =
268 "<toolbar>"
269 "<toolitem action='Cancel'/>"
270 "<toolitem action='Save'/>"
271 "<separator/>"
272 "<toolitem action='AddAction'/>"
273 "<toolitem action='AddOption'/>"
274 "<toolitem action='Remove'/>"
275 "<toolitem action='Change'/>"
276 "<separator/>"
277 "<toolitem action='AddSubOption'/>"
278 /* "<separator/>"
279 "<toolitem action='Help'/>" */
280 "</toolbar>";
281
282 static GtkActionEntry actions[] =
283 {
284 { "Cancel", GTK_STOCK_CANCEL, NULL, NULL, N_("Discard changes"), G_CALLBACK(on_cancel) },
285 { "Save", GTK_STOCK_SAVE, NULL, NULL, N_("Accept changes"), G_CALLBACK(on_save) },
286 { "AddAction", GTK_STOCK_ADD, NULL, NULL, N_("Add an action"), G_CALLBACK(on_add_action) },
287 { "AddOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to this command"),
288 G_CALLBACK(on_add_option) },
289 { "Remove", GTK_STOCK_DELETE, NULL, "", N_("Remove selection"), G_CALLBACK(on_remove) },
290 { "Change", GTK_STOCK_EDIT, NULL, NULL, N_("Change selected option"), G_CALLBACK(on_edit) },
291 { "AddSubOption", GTK_STOCK_ADD, NULL, NULL, N_("Add an option to selection"),
292 G_CALLBACK(on_add_suboption) }
293 };
294
295 /* Button for keybinding click - taken from LXPanel, simplified a bit */
296 static void on_focus_in_event(GtkButton *test, GdkEvent *event, PluginData *data)
297 {
298 gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(test)), TRUE, GDK_CURRENT_TIME);
299 }
300
301 static void on_focus_out_event(GtkButton *test, GdkEvent *event, PluginData *data)
302 {
303 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
304 }
305
306 static gboolean on_key_event(GtkButton *test, GdkEventKey *event, PluginData *data)
307 {
308 GdkModifierType state;
309 char *text;
310
311 /* ignore Tab completely so user can leave focus */
312 if (event->keyval == GDK_KEY_Tab)
313 return FALSE;
314 /* request mods directly, event->state isn't updated yet */
315 gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(test)),
316 NULL, NULL, &state);
317 /* special support for Win key, it doesn't work sometimes */
318 if ((state & GDK_SUPER_MASK) == 0 && (state & GDK_MOD4_MASK) != 0)
319 state |= GDK_SUPER_MASK;
320 state &= gtk_accelerator_get_default_mod_mask();
321 /* if mod key event then update test label and go */
322 if (event->is_modifier)
323 {
324 if (state != 0)
325 {
326 text = gtk_accelerator_get_label(0, state);
327 gtk_button_set_label(test, text);
328 g_free(text);
329 }
330 /* if no modifiers currently then show original state */
331 else
332 gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
333 return FALSE;
334 }
335 /* if not keypress query then ignore key press */
336 if (event->type != GDK_KEY_PRESS)
337 return FALSE;
338 /* update the label now */
339 text = gtk_accelerator_get_label(event->keyval, state);
340 gtk_button_set_label(test, text);
341 /* drop single printable and printable with single Shift, Ctrl, Alt */
342 if (event->length != 0 && (state == 0 || state == GDK_SHIFT_MASK ||
343 state == GDK_CONTROL_MASK || state == GDK_MOD1_MASK))
344 {
345 GtkWidget* dlg;
346 dlg = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
347 _("Key combination '%s' cannot be used as"
348 " a global hotkey, sorry."), text);
349 g_free(text);
350 gtk_window_set_title(GTK_WINDOW(dlg), _("Error"));
351 gtk_window_set_keep_above(GTK_WINDOW(dlg), TRUE);
352 gtk_dialog_run(GTK_DIALOG(dlg));
353 gtk_widget_destroy(dlg);
354 gtk_button_set_label(test, g_object_get_data(G_OBJECT(test), "original_label"));
355 return FALSE;
356 }
357 g_free(text);
358 /* save new value now */
359 text = gtk_accelerator_name(event->keyval, state);
360 g_object_set_data_full(G_OBJECT(test), "accelerator_name", text, g_free);
361 return FALSE;
362 }
363
364 static GtkWidget *key_button_new(PluginData *data, const char *hotkey)
365 {
366 GtkWidget *w;
367 char *label;
368 guint keyval = 0;
369 GdkModifierType state = 0;
370
371 if (hotkey)
372 gtk_accelerator_parse(hotkey, &keyval, &state);
373 label = gtk_accelerator_get_label(keyval, state);
374 w = gtk_button_new_with_label(label);
375 g_object_set_data_full(G_OBJECT(w), "original_label", label, g_free);
376 g_signal_connect(w, "focus-in-event", G_CALLBACK(on_focus_in_event), data);
377 g_signal_connect(w, "focus-out-event", G_CALLBACK(on_focus_out_event), data);
378 g_signal_connect(w, "key-press-event", G_CALLBACK(on_key_event), data);
379 g_signal_connect(w, "key-release-event", G_CALLBACK(on_key_event), data);
380 return w;
381 }
382
383 #if !GLIB_CHECK_VERSION(2, 34, 0)
384 static GList *g_list_copy_deep(GList *list, GCopyFunc func, gpointer user_data)
385 {
386 GList *copy = NULL;
387
388 while (list)
389 {
390 copy = g_list_prepend(copy, func(list->data, user_data));
391 list = list->next;
392 }
393 return g_list_reverse(copy);
394 }
395 #endif
396
397 /* used by edit_action() to be able to edit */
398 static GList *copy_options(GList *orig)
399 {
400 GList *copy = NULL;
401
402 /* copy contents recursively */
403 while (orig)
404 {
405 LXHotkeyAttr *attr = g_slice_new(LXHotkeyAttr);
406 LXHotkeyAttr *attr_orig = orig->data;
407
408 attr->name = g_strdup(attr_orig->name);
409 attr->values = g_list_copy_deep(attr_orig->values, (GCopyFunc)g_strdup, NULL);
410 attr->subopts = copy_options(attr_orig->subopts);
411 attr->desc = g_strdup(attr_orig->desc);
412 attr->has_actions = attr_orig->has_actions;
413 copy = g_list_prepend(copy, attr);
414 orig = orig->next;
415 }
416 return g_list_reverse(copy);
417 }
418
419 #define free_options(acts) g_list_free_full(acts, (GDestroyNotify)option_free)
420 static void option_free(LXHotkeyAttr *attr)
421 {
422 g_free(attr->name);
423 g_list_free_full(attr->values, g_free);
424 free_options(attr->subopts);
425 g_free(attr->desc);
426 g_slice_free(LXHotkeyAttr, attr);
427 }
428
429 static void add_options_to_tree(GtkTreeStore *store, GtkTreeIter *parent_iter,
430 GList *list)
431 {
432 LXHotkeyAttr *opt;
433 GtkTreeIter iter;
434
435 while (list)
436 {
437 opt = list->data;
438 gtk_tree_store_insert_with_values(store, &iter, parent_iter, -1,
439 0, opt->name,
440 1, opt->values ? opt->values->data : NULL,
441 2, opt, -1);
442 if (opt->subopts)
443 add_options_to_tree(store, &iter, opt->subopts);
444 list = list->next;
445 }
446 }
447
448 static void update_options_tree(PluginData *data)
449 {
450 GtkTreeStore *store = gtk_tree_store_new(3, G_TYPE_STRING, /* option name */
451 G_TYPE_STRING, /* option value */
452 G_TYPE_POINTER); /* LXHotkeyAttr */
453
454 add_options_to_tree(store, NULL, data->edit_options_copy);
455 gtk_tree_view_set_model(data->edit_tree, GTK_TREE_MODEL(store));
456 gtk_tree_view_expand_all(data->edit_tree);
457 g_object_unref(store);
458 }
459
460 #define edit_is_active(data) (data->edit_mode != EDIT_MODE_NONE)
461
462 static void cancel_edit(PluginData *data)
463 {
464 data->edit_mode = EDIT_MODE_NONE;
465 gtk_widget_hide(data->edit_frame);
466 }
467
468 static void on_selection_changed(GtkTreeSelection *selection, PluginData *data)
469 {
470 if (edit_is_active(data))
471 //FIXME: ask confirmation and revert, is that possible?
472 cancel_edit(data);
473 //update toolbar buttons visibility
474 update_edit_toolbar(data);
475 }
476
477 static void on_option_changed(GtkComboBox *box, PluginData *data)
478 {
479 const LXHotkeyAttr *opt, *tmpl;
480 const GList *values;
481 GtkTreeModel *model;
482 GtkListStore *values_store;
483 GtkTreeIter iter;
484 int i, sel;
485 gboolean is_action = FALSE;
486
487 /* g_debug("on_option_changed"); */
488 opt = NULL;
489 if (data->edit_mode == EDIT_MODE_ADD)
490 is_action = (data->current_page == data->acts);
491 else if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->edit_tree),
492 &model, &iter))
493 {
494 gtk_tree_model_get(model, &iter, 2, &opt, -1);
495 if (data->current_page == data->acts)
496 is_action = (get_parent_template_list(model, &iter, data) == data->edit_template);
497 }
498 if (!gtk_combo_box_get_active_iter(box, &iter))
499 /* no item selected */
500 return;
501 gtk_tree_model_get(gtk_combo_box_get_model(box), &iter, 2, &tmpl, -1);
502 if (tmpl->has_actions || is_action ||
503 (tmpl->subopts != NULL && !tmpl->has_value))
504 {
505 /* either it's action, or option has suboptions instead of values */
506 gtk_widget_hide(data->edit_value_label);
507 gtk_widget_hide(GTK_WIDGET(data->edit_value));
508 gtk_widget_hide(GTK_WIDGET(data->edit_values));
509 gtk_widget_hide(data->edit_value_num);
510 gtk_widget_hide(data->edit_value_num_label);
511 }
512 else if ((values = tmpl->values) != NULL)
513 {
514 values_store = GTK_LIST_STORE(gtk_list_store_new(2, G_TYPE_STRING, /* description */
515 G_TYPE_STRING)); /* value */
516 for (sel = 0, i = 0; values; values = values->next, i++)
517 {
518 gtk_list_store_insert_with_values(values_store, NULL, i, 0, _(values->data),
519 1, values->data, -1);
520 if (opt && opt->values && g_strcmp0(opt->values->data, values->data) == 0)
521 sel = i;
522 }
523 gtk_combo_box_set_model(data->edit_values, GTK_TREE_MODEL(values_store));
524 g_object_unref(values_store);
525 gtk_combo_box_set_active(data->edit_values, sel);
526 gtk_widget_show(data->edit_value_label);
527 gtk_widget_show(GTK_WIDGET(data->edit_values));
528 gtk_widget_hide(GTK_WIDGET(data->edit_value));
529 }
530 else
531 {
532 gtk_widget_show(data->edit_value_label);
533 gtk_widget_hide(data->edit_value_num);
534 gtk_widget_hide(data->edit_value_num_label);
535 gtk_widget_hide(GTK_WIDGET(data->edit_values));
536 gtk_widget_show(GTK_WIDGET(data->edit_value));
537 if (opt && opt->values)
538 gtk_entry_set_text(data->edit_value, opt->values->data);
539 else
540 gtk_entry_set_text(data->edit_value, "");
541 }
542 }
543
544 static void on_value_changed(GtkComboBox *box, PluginData *data)
545 {
546 const char *value;
547 GtkTreeModel *model;
548 GtkTreeIter iter;
549
550 model = gtk_combo_box_get_model(box);
551 if (!gtk_combo_box_get_active_iter(box, &iter))
552 /* no value chosen */
553 goto _general;
554 gtk_tree_model_get(model, &iter, 1, &value, -1);
555 if (!value)
556 /* illegal really */
557 goto _general;
558 if (value[0] == '#')
559 {
560 /* if value is # then show data->edit_value_num */
561 gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
562 -1000.0, +1000.0);
563 gtk_widget_show(data->edit_value_num);
564 gtk_label_set_text(GTK_LABEL(data->edit_value_num_label), "");
565 gtk_widget_hide(data->edit_value_num_label);
566 }
567 else if (value[0] == '%')
568 {
569 /* if value is % then show data->edit_value_num with label "%" */
570 gtk_spin_button_set_range(GTK_SPIN_BUTTON(data->edit_value_num),
571 0.0, +100.0);
572 gtk_widget_show(data->edit_value_num);
573 gtk_label_set_text(GTK_LABEL(data->edit_value_num_label), "%");
574 gtk_widget_show(data->edit_value_num_label);
575 }
576 else
577 {
578 _general:
579 /* else hide both data->edit_value_num and label */
580 gtk_widget_hide(data->edit_value_num);
581 gtk_widget_hide(data->edit_value_num_label);
582 }
583 }
584
585 static void on_apply_button(GtkButton *btn, PluginData *data)
586 {
587 //insert/update option in data->edit_options_copy
588 //add/update row in the model
589 //update_edit_toolbar(data);
590 cancel_edit(data);
591 }
592
593 static void on_cancel_button(GtkButton *btn, PluginData *data)
594 {
595 cancel_edit(data);
596 }
597
598 /* free all allocated data */
599 void _edit_cleanup(PluginData *data)
600 {
601 if (data->edit_window)
602 {
603 cancel_edit(data);
604 g_object_remove_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
605 gtk_widget_destroy(GTK_WIDGET(data->edit_window));
606 data->edit_window = NULL;
607 }
608 if (data->edit_options_copy)
609 {
610 free_options(data->edit_options_copy);
611 data->edit_options_copy = NULL;
612 }
613 }
614
615 void _edit_action(PluginData *data, GError **error)
616 {
617 LXHotkeyGlobal *act = NULL;
618 LXHotkeyApp *app = NULL;
619 const char *accel1 = NULL, *accel2 = NULL;
620 GtkBox *vbox, *xbox;
621 GtkUIManager *ui;
622 GtkActionGroup *act_grp;
623 GtkAccelGroup *accel_grp;
624 GtkWidget *widget, *align;
625 GtkToolbar *toolbar;
626 GtkCellRenderer *column;
627 GtkTreeModel *model;
628 GtkTreeIter iter;
629 gboolean is_action = FALSE;
630
631 if (data->edit_window)
632 {
633 /* OOPS, another edit is still opened */
634 return;
635 }
636 /* do cleanup */
637 _edit_cleanup(data);
638 /* get a template */
639 if (data->current_page == data->acts)
640 {
641 if (data->cb->get_wm_actions == NULL) /* not available for edit */
642 return;
643 data->edit_template = data->cb->get_wm_actions(data->config, NULL);
644 //FIXME: test for error
645 is_action = TRUE;
646 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->acts),
647 &model, &iter))
648 gtk_tree_model_get(model, &iter, 4, &act, -1);
649 if (act)
650 {
651 /* if there is a selection then copy its options */
652 data->edit_options_copy = copy_options(act->actions);
653 accel1 = act->accel1;
654 accel2 = act->accel2;
655 }
656 }
657 else
658 {
659 if (data->cb->get_app_options == NULL) /* not available for edit */
660 return;
661 data->edit_template = data->cb->get_app_options(data->config, NULL);
662 //FIXME: test for error
663 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(data->apps),
664 &model, &iter))
665 gtk_tree_model_get(model, &iter, 3, &app, -1);
666 if (app)
667 {
668 /* if there is a selection then copy its options */
669 data->edit_options_copy = copy_options(app->options);
670 accel1 = app->accel1;
671 accel2 = app->accel2;
672 }
673 }
674
675 /* create a window with a GtkVBox inside */
676 data->edit_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
677 gtk_window_set_default_size(data->edit_window, 240, 10);
678 gtk_window_set_transient_for(data->edit_window,
679 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(data->notebook))));
680 g_object_add_weak_pointer(G_OBJECT(data->edit_window), (gpointer)&data->edit_window);
681 vbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
682
683 /* add the toolbar */
684 ui = gtk_ui_manager_new();
685 act_grp = gtk_action_group_new("Edit");
686 gtk_action_group_set_translation_domain(act_grp, NULL);
687 gtk_action_group_add_actions(act_grp, actions, G_N_ELEMENTS(actions), data);
688 accel_grp = gtk_ui_manager_get_accel_group(ui);
689 gtk_window_add_accel_group(GTK_WINDOW(data->edit_window), accel_grp);
690 gtk_ui_manager_insert_action_group(ui, act_grp, 0);
691 gtk_ui_manager_add_ui_from_string(ui, edit_xml, -1, NULL);
692 g_object_unref(act_grp);
693 widget = gtk_ui_manager_get_widget(ui, "/toolbar");
694 toolbar = GTK_TOOLBAR(widget);
695 //TODO: 'Change' -- also 2click and Enter
696 gtk_toolbar_set_icon_size(toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR);
697 gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS);
698 gtk_toolbar_set_show_arrow(toolbar, FALSE);
699 data->add_option_button = gtk_ui_manager_get_action(ui, "/toolbar/AddOption");
700 data->rm_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Remove");
701 data->edit_option_button = gtk_ui_manager_get_action(ui, "/toolbar/Change");
702 data->add_suboption_button = gtk_ui_manager_get_action(ui, "/toolbar/AddSubOption");
703 gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
704
705 /* add frames for accel1 and accel2 */
706 xbox = (GtkBox *)gtk_hbox_new(TRUE, 0);
707 widget = gtk_frame_new(_("Hotkey 1"));
708 data->edit_key1 = key_button_new(data, accel1);
709 gtk_container_add(GTK_CONTAINER(widget), data->edit_key1);
710 gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
711 widget = gtk_frame_new(_("Hotkey 2"));
712 data->edit_key2 = key_button_new(data, accel2);
713 gtk_container_add(GTK_CONTAINER(widget), data->edit_key2);
714 gtk_box_pack_start(xbox, widget, TRUE, TRUE, 0);
715 gtk_box_pack_start(vbox, GTK_WIDGET(xbox), FALSE, TRUE, 0);
716
717 /* add frame with all options */
718 widget = gtk_frame_new(NULL);
719 gtk_frame_set_shadow_type(GTK_FRAME(widget), GTK_SHADOW_IN);
720 xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
721 if (is_action)
722 {
723 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
724 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Actions:")));
725 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
726 }
727 else
728 {
729 /* for application add a GtkEntry for exec line */
730 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
731 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Command line:")));
732 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
733 data->edit_exec = GTK_ENTRY(gtk_entry_new());
734 if (app)
735 gtk_entry_set_text(data->edit_exec, app->exec);
736 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
737 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
738 gtk_container_add(GTK_CONTAINER(align), GTK_WIDGET(data->edit_exec));
739 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
740 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
741 gtk_container_add(GTK_CONTAINER(align), gtk_label_new(_("Options:")));
742 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
743 }
744 data->edit_tree = GTK_TREE_VIEW(gtk_tree_view_new());
745 gtk_box_pack_start(xbox, GTK_WIDGET(data->edit_tree), TRUE, TRUE, 0);
746 gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(xbox));
747 gtk_box_pack_start(vbox, widget, FALSE, TRUE, 0);
748 gtk_tree_view_insert_column_with_attributes(data->edit_tree, 0, NULL,
749 gtk_cell_renderer_text_new(),
750 "text", 0, NULL);
751 gtk_tree_view_insert_column_with_attributes(data->edit_tree, 1, NULL,
752 gtk_cell_renderer_text_new(),
753 "text", 1, NULL);
754 gtk_tree_view_set_headers_visible(data->edit_tree, FALSE);
755 //FIXME: connect "row-activated" for Edit
756 g_signal_connect(gtk_tree_view_get_selection(data->edit_tree), "changed",
757 G_CALLBACK(on_selection_changed), data);
758 update_options_tree(data);
759
760 /* frame with fields for editing, hidden for now */
761 data->edit_frame = gtk_frame_new(_("Add action"));
762 xbox = (GtkBox *)gtk_vbox_new(FALSE, 0);
763 /* combobox for option/action name */
764 align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
765 widget = gtk_label_new(NULL);
766 gtk_label_set_markup(GTK_LABEL(widget), _("<b>Name:</b>"));
767 gtk_container_add(GTK_CONTAINER(align), widget);
768 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
769 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
770 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
771 widget = gtk_hbox_new(FALSE, 0);
772 data->edit_option_name = gtk_label_new(NULL);
773 gtk_box_pack_start(GTK_BOX(widget), data->edit_option_name, FALSE, TRUE, 0);
774 data->edit_actions = (GtkComboBox *)gtk_combo_box_new();
775 g_signal_connect(data->edit_actions, "changed",
776 G_CALLBACK(on_option_changed), data);
777 column = gtk_cell_renderer_text_new();
778 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_actions), column, TRUE);
779 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_actions), column,
780 "text", 0, NULL);
781 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_actions), TRUE, TRUE, 0);
782 gtk_container_add(GTK_CONTAINER(align), widget);
783 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
784 /* entry or combobox for option value */
785 data->edit_value_label = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
786 widget = gtk_label_new(NULL);
787 gtk_label_set_markup(GTK_LABEL(widget), _("<b>Value:</b>"));
788 gtk_container_add(GTK_CONTAINER(data->edit_value_label), widget);
789 gtk_box_pack_start(xbox, data->edit_value_label, FALSE, TRUE, 0);
790 align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
791 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 4, 0);
792 widget = gtk_hbox_new(FALSE, 0);
793 data->edit_value = (GtkEntry *)gtk_entry_new();
794 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value), TRUE, TRUE, 0);
795 data->edit_values = (GtkComboBox *)gtk_combo_box_new();
796 column = gtk_cell_renderer_text_new();
797 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(data->edit_values), column, TRUE);
798 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(data->edit_values), column,
799 "text", 0, NULL);
800 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_values), TRUE, TRUE, 0);
801 g_signal_connect(data->edit_values, "changed",
802 G_CALLBACK(on_value_changed), data);
803 data->edit_value_num = gtk_spin_button_new_with_range(-1000.0, +1000.0, 1.0);
804 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num), FALSE, TRUE, 0);
805 data->edit_value_num_label = gtk_label_new(NULL);
806 gtk_box_pack_start(GTK_BOX(widget), GTK_WIDGET(data->edit_value_num_label), FALSE, TRUE, 0);
807 gtk_container_add(GTK_CONTAINER(align), widget);
808 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
809 /* buttons 'Cancel' and 'Apply' */
810 align = gtk_alignment_new(1.0, 0.0, 0.0, 0.0);
811 gtk_box_pack_start(xbox, align, FALSE, TRUE, 0);
812 widget = gtk_hbox_new(TRUE, 4);
813 gtk_container_add(GTK_CONTAINER(align), widget);
814 align = gtk_button_new_from_stock(GTK_STOCK_APPLY); /* reuse align */
815 g_signal_connect(align, "clicked", G_CALLBACK(on_apply_button), data);
816 gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
817 align = gtk_button_new_from_stock(GTK_STOCK_CANCEL); /* reuse align */
818 g_signal_connect(align, "clicked", G_CALLBACK(on_cancel_button), data);
819 gtk_box_pack_end(GTK_BOX(widget), align, FALSE, TRUE, 0);
820 gtk_container_add(GTK_CONTAINER(data->edit_frame), GTK_WIDGET(xbox));
821 gtk_box_pack_start(vbox, data->edit_frame, TRUE, TRUE, 0);
822
823 gtk_widget_show_all(GTK_WIDGET(vbox));
824 gtk_widget_hide(data->edit_frame);
825 /* hide one of AddAction or AddOption */
826 if (is_action)
827 {
828 /* AddOption is visible for exec only */
829 gtk_action_set_visible(data->add_option_button, FALSE);
830 }
831 else
832 {
833 /* AddAction is visible for action only */
834 GtkAction *act = gtk_ui_manager_get_action(ui, "/toolbar/AddAction");
835 gtk_action_set_visible(act, FALSE);
836 }
837 gtk_container_add(GTK_CONTAINER(data->edit_window), GTK_WIDGET(vbox));
838 update_edit_toolbar(data);
839 gtk_window_present(data->edit_window);
840 gtk_widget_grab_focus(GTK_WIDGET(data->edit_tree));
841 }