1 /*
2    Kickshaw - A Menu Editor for Openbox
3 
4    Copyright (c) 2010–2018        Marcus Schätzle
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 along
17    with Kickshaw. If not, see http://www.gnu.org/licenses/.
18 */
19 
20 #include <gtk/gtk.h>
21 #include <string.h>
22 
23 #include "definitions_and_enumerations.h"
24 #include "adding_and_deleting.h"
25 
26 static void hide_or_deactivate_widgets (void);
27 void one_of_the_change_values_buttons_pressed (gchar *origin);
28 static GtkTreeIter add_new_execution (gchar *new_menu_element, gboolean same_level);
29 void add_new (gchar *new_menu_element_plus_opt_suppl);
30 static void create_combo_box (void);
31 void option_list_with_headlines (G_GNUC_UNUSED GtkCellLayout   *cell_layout,
32                                                GtkCellRenderer *action_option_combo_box_renderer,
33                                                GtkTreeModel    *action_option_combo_box_model,
34                                                GtkTreeIter     *action_option_combo_box_iter,
35                                  G_GNUC_UNUSED gpointer         data);
36 static guint8 generate_action_option_combo_box_items_for_Exe_and_snotify_opts (gchar *options_type);
37 void generate_items_for_action_option_combo_box (gchar *preset_choice);
38 void show_action_options (void);
39 void show_or_hide_startupnotify_options (void);
40 void single_field_entry (void);
41 void hide_action_option_grid (gchar *origin);
42 static void clear_entries (void);
43 void action_option_insert (gchar *origin);
44 static gboolean check_for_menus (              GtkTreeModel *filter_model,
45                                  G_GNUC_UNUSED GtkTreePath  *filter_path,
46                                                GtkTreeIter  *filter_iter);
47 void remove_menu_id (gchar *menu_id);
48 void remove_all_children (void);
49 void remove_rows (gchar *origin);
50 
51 /*
52 
53     Deactivate or hide those widgets whose actions could interfere during the entry of new values.
54 
55 */
56 
hide_or_deactivate_widgets(void)57 static void hide_or_deactivate_widgets (void)
58 {
59     guint8 tb_cnt;
60 
61     gtk_widget_set_sensitive (ks.mb_edit, FALSE);
62     gtk_widget_set_sensitive (ks.mb_search, FALSE);
63     gtk_widget_set_sensitive (ks.mb_options, FALSE);
64     for (tb_cnt = TB_MOVE_UP; tb_cnt <= TB_FIND; tb_cnt++) {
65         gtk_widget_set_sensitive ((GtkWidget *) ks.tb[tb_cnt], FALSE);
66     }
67     gtk_widget_hide (ks.button_grid);
68     gtk_widget_hide (ks.find_grid);
69 }
70 
71 /*
72 
73     Execute the actions that can be initiated from the "change values" input screen.
74 
75 */
76 
one_of_the_change_values_buttons_pressed(gchar * origin)77 void one_of_the_change_values_buttons_pressed (gchar *origin)
78 {
79     GSList *change_values_user_settings_loop;
80     // AD = adding and deleting, indicating that the enums are only used here
81     enum { AD_FIELD, AD_VALUE };
82     gchar **parameter;
83 
84     gboolean including_action_check_button_active =
85         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]));
86     gchar *new_menu_element = NULL; // Initialization avoids compiler warning.
87     gboolean same_level = TRUE; // default
88 
89     for (change_values_user_settings_loop = ks.change_values_user_settings;
90         change_values_user_settings_loop;
91         change_values_user_settings_loop = change_values_user_settings_loop->next) {
92         parameter = g_strsplit (change_values_user_settings_loop->data, ":", -1);
93         if (STREQ (origin, "done") && STREQ (parameter[AD_FIELD], "new menu element")) {
94             // "value" exists only temporary
95             new_menu_element = g_strdup (parameter[AD_VALUE]);
96         }
97         else if (STREQ (origin, "reset")) {
98             if (STREQ (parameter[AD_FIELD], "new menu element")) {
99                 gchar *default_label_txt = g_strdup_printf ("New %s", parameter[AD_VALUE]);
100                 gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), default_label_txt);
101                 // Cleanup
102                 g_free (default_label_txt);
103 
104                 // To keep the code simple, the rest of this if-clause resets fields even if they are currently not visible.
105                 gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]),
106                                                 "mandatory_missing");
107                 gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[EXECUTE_ENTRY]),
108                                                 "mandatory_missing");
109 
110                 gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), "");
111                 gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]), "");
112             }
113             else if (STREQ (parameter[AD_FIELD], "menu ID field")) {
114                 gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]), parameter[AD_VALUE]);
115             }
116             else if (STREQ (parameter[AD_FIELD], "inside menu")) {
117                 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]),
118                                               STREQ (parameter[AD_VALUE], "true"));
119             }
120             else if (STREQ (parameter[AD_FIELD], "incl. action")) {
121                 including_action_check_button_active = (STREQ (parameter[AD_VALUE], "true"));
122                 // Prevent recursion
123                 g_signal_handler_block (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON],
124                                         ks.handler_id_including_action_check_button);
125                 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]),
126                                               including_action_check_button_active);
127                 g_signal_handler_unblock (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON],
128                                           ks.handler_id_including_action_check_button);
129                 gtk_widget_set_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], including_action_check_button_active);
130                 gtk_widget_set_visible (ks.options_grid, including_action_check_button_active);
131             }
132             else { // action
133                 gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), parameter[AD_VALUE]);
134                 if (streq_any (parameter[AD_VALUE], "Execute", "Restart", NULL)) {
135                     clear_entries ();
136                 }
137                 else if (streq_any (parameter[AD_VALUE], "Exit", "SessionLogout", NULL)) {
138                     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
139                 }
140             }
141         }
142         else if (STREQ (origin, "incl. action") && STREQ (parameter[AD_FIELD], "action")) {
143             gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), parameter[AD_VALUE]);
144             if (!including_action_check_button_active) {
145                 clear_entries ();
146             }
147             else if (streq_any (parameter[AD_VALUE], "Exit", "SessionLogout", NULL)) {
148                 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
149             }
150             gtk_widget_set_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], including_action_check_button_active);
151             gtk_widget_set_visible (ks.options_grid, including_action_check_button_active);
152         }
153 
154         // Cleanup
155         g_strfreev (parameter);
156     }
157 
158     if (STREQ (origin, "done")) {
159         gboolean add_action = (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX] &&
160                                gtk_widget_get_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]));
161         gchar *menu_id = NULL; // Initialization avoids compiler warning.
162         gboolean missing_entry_or_error = FALSE; // default
163 
164         // Missing label for new menu element.
165         if (!(*((gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]))))) {
166             wrong_or_missing (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], ks.menu_element_or_value_entry_css_provider);
167             missing_entry_or_error = TRUE;
168         }
169         else {
170             gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]),
171                                             "mandatory_missing");
172         }
173 
174         // "Execute" missing of new pipe menu.
175         if (STREQ (new_menu_element, "pipe menu") &&
176             !(*((gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]))))) {
177             wrong_or_missing (ks.entry_fields[EXECUTE_ENTRY], ks.execute_entry_css_provider);
178             missing_entry_or_error = TRUE;
179         }
180         else {
181             gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[EXECUTE_ENTRY]),
182                                             "mandatory_missing");
183         }
184 
185         // Prevent empty command field for "Execute".
186         if (add_action &&
187             STREQ ((gchar *) gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])),
188                    "Execute") &&
189             !(*((gchar *) gtk_entry_get_text (GTK_ENTRY (ks.options_fields[COMMAND]))))) {
190             wrong_or_missing (ks.options_fields[COMMAND], ks.execute_options_css_providers[COMMAND]);
191             missing_entry_or_error = TRUE;
192         }
193         else {
194             gtk_style_context_remove_class (gtk_widget_get_style_context (ks.options_fields[COMMAND]), "mandatory_missing");
195         }
196 
197         // Invalid icon file or path
198         const gchar *icon_path = gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]));
199         if (*icon_path && !set_icon (&ks.iter, (gchar *) icon_path, TRUE)) { // TRUE = display error message if error occurs.
200             missing_entry_or_error = TRUE;
201         }
202 
203         // Non-unique menu ID
204         if (!STREQ (new_menu_element, "item")) { // menu or pipe menu
205             menu_id = (gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]));
206             if (G_UNLIKELY (g_slist_find_custom (ks.menu_ids, menu_id, (GCompareFunc) strcmp))) {
207                 show_errmsg ("The chosen menu ID already exists. Please choose another one.");
208                 missing_entry_or_error = TRUE;
209             }
210         }
211 
212         if (missing_entry_or_error) {
213             // Cleanup
214             g_free (new_menu_element);
215 
216             return;
217         }
218 
219         if (!STREQ (new_menu_element, "item")) {
220             ks.menu_ids = g_slist_prepend (ks.menu_ids, g_strdup (menu_id));
221         }
222 
223         if (gtk_widget_get_visible (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON])) {
224             same_level = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]));
225         }
226 
227         GtkTreeIter new_menu_element_iter;
228         ks.iter = new_menu_element_iter = add_new_execution (new_menu_element, same_level);
229 
230         if (add_action) {
231             repopulate_txt_fields_array (); // This is usually called by row_selected () and needed by the following function.
232             action_option_insert ("by combo box");
233         }
234 
235         GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
236         // This triggers row_selected, resulting in a complete reset.
237         gtk_tree_selection_select_iter (selection, &new_menu_element_iter);
238 
239         // Cleanup
240         g_free (new_menu_element);
241     }
242 }
243 
244 /*
245 
246     Adds a new menu element.
247 
248 */
249 
add_new_execution(gchar * new_menu_element,gboolean same_level)250 static GtkTreeIter add_new_execution (gchar *new_menu_element, gboolean same_level)
251 {
252     gpointer new_ts_fields[NUMBER_OF_TS_ELEMENTS] = { NULL }; // Defaults
253 
254     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
255 
256     GtkTreePath *path = NULL; // Default
257     GtkTreeIter new_iter;
258 
259     gboolean path_is_on_toplevel = TRUE; // Default
260 
261     guint8 ts_fields_cnt;
262 
263     if (streq_any (new_menu_element, "menu", "pipe menu", "item", NULL)) {
264         new_ts_fields[TS_TYPE] = new_menu_element;
265         new_ts_fields[TS_MENU_ELEMENT] = (gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]));
266 
267         if (*(gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY])))) {
268             GdkPixbuf *icon_in_original_size;
269 
270             new_ts_fields[TS_ICON_PATH] = (gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]));
271             icon_in_original_size = gdk_pixbuf_new_from_file (new_ts_fields[TS_ICON_PATH], NULL);
272             new_ts_fields[TS_ICON_IMG] = gdk_pixbuf_scale_simple (icon_in_original_size,
273                                                                   ks.font_size + 10, ks.font_size + 10,
274                                                                   GDK_INTERP_BILINEAR);
275             new_ts_fields[TS_ICON_MODIFICATION_TIME] = get_modification_time_for_icon (new_ts_fields[TS_ICON_PATH]);
276 
277             // Cleanup
278             g_object_unref (icon_in_original_size);
279         }
280         if (streq_any (new_menu_element, "menu", "pipe menu", NULL)) {
281             new_ts_fields[TS_MENU_ID] = (gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]));
282             if (STREQ (new_menu_element, "pipe menu") && *(gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY])))) {
283                 new_ts_fields[TS_EXECUTE] = (gchar *) gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]));
284             }
285         }
286     }
287     else if (STREQ (new_menu_element, "separator")) {
288         new_ts_fields[TS_TYPE] = "separator";
289     }
290     else {
291         new_ts_fields[TS_TYPE] = "option";
292         new_ts_fields[TS_MENU_ELEMENT] = new_menu_element;
293 
294         // --- Assigning predefinied values for options. ---
295 
296         if (STREQ (new_menu_element, "enabled") ||
297             streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "SessionLogout", NULL)) { // new_menu_element == "prompt"
298             new_ts_fields[TS_VALUE] = "yes";
299         }
300     }
301 
302 
303     if (gtk_tree_selection_count_selected_rows (selection) &&
304         gtk_tree_path_get_depth (path = gtk_tree_model_get_path (ks.model, &ks.iter)) > 1) {
305         path_is_on_toplevel = FALSE;
306     }
307 
308     // Adding options inside an action or an option block.
309     if (STREQ (ks.txt_fields[TYPE_TXT], "action") ||
310         (STREQ (ks.txt_fields[TYPE_TXT], "option block") && !streq_any (new_menu_element, "prompt", "command", NULL))) {
311         same_level = FALSE;
312     }
313 
314 
315     // --- Add the new row. ---
316 
317 
318     // Prevents that the default check for change of selection(s) gets in the way.
319     g_signal_handler_block (selection, ks.handler_id_row_selected);
320 
321     gtk_tree_selection_unselect_all (selection); // The old selection will be replaced by the one of the new row.
322 
323     if (!path || !same_level) {
324         gtk_tree_store_append (ks.treestore, &new_iter, (!path) ? NULL : &ks.iter);
325 
326         if (path) {
327             gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path, FALSE); // FALSE == just expand immediate children.
328         }
329     }
330     else {
331         GtkTreeIter parent;
332 
333         if (!path_is_on_toplevel) {
334             gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
335         }
336         gtk_tree_store_insert_after (ks.treestore, &new_iter, (path_is_on_toplevel) ? NULL : &parent, &ks.iter);
337     }
338 
339     // Set element visibility of menus, pipe menus, items and separators.
340     if (g_regex_match_simple ("menu|pipe menu|item.*|separator", new_menu_element, 0, 0)) {
341         new_ts_fields[TS_ELEMENT_VISIBILITY] = "visible"; // Default
342         /*
343             path == NULL means that the new menu element has been added at toplevel;
344             such a menu element is always visible after its addition, since it can't have an invisible ancestor.
345         */
346         if (path) {
347             GtkTreePath *new_path = gtk_tree_model_get_path (ks.model, &new_iter);
348             guint8 invisible_ancestor;
349 
350             if ((invisible_ancestor = check_if_invisible_ancestor_exists (ks.model, new_path))) { // Parentheses avoid gcc warning.
351                 new_ts_fields[TS_ELEMENT_VISIBILITY] = (invisible_ancestor == INVISIBLE_ANCESTOR) ?
352                 "invisible dsct. of invisible menu" : "invisible dsct. of invisible orphaned menu";
353             }
354             // Cleanup
355             gtk_tree_path_free (new_path);
356         }
357     }
358 
359     for (ts_fields_cnt = 0; ts_fields_cnt < NUMBER_OF_TS_ELEMENTS; ts_fields_cnt++) {
360         gtk_tree_store_set (ks.treestore, &new_iter, ts_fields_cnt, new_ts_fields[ts_fields_cnt], -1);
361     }
362 
363     // Cleanup
364     gtk_tree_path_free (path);
365     if (new_ts_fields[TS_ICON_IMG]) {
366         g_object_unref (new_ts_fields[TS_ICON_IMG]);
367     }
368     g_free (new_ts_fields[TS_ICON_MODIFICATION_TIME]);
369 
370     if (!gtk_widget_get_visible (ks.change_values_label)) {
371         gtk_tree_selection_select_iter (selection, &new_iter);
372     }
373 
374     /*
375         In case that autosorting is activated:
376         If the type of the new row is...
377         ...a prompt or command option of Execute, ... OR:
378         ...an option of startupnotify, ...
379         ...sort all options of the parent row and move the selection to the new option.
380     */
381      if (ks.autosort_options && STREQ (new_ts_fields[TS_TYPE], "option")) {
382         GtkTreeIter parent;
383 
384         gtk_tree_model_iter_parent (ks.model, &parent, &new_iter);
385         if (gtk_tree_model_iter_n_children (ks.model, &parent) > 1) {
386             sort_execute_or_startupnotify_options_after_insertion (selection, &parent,
387                                                                    (streq_any (new_menu_element, "prompt", "command", NULL)) ?
388                                                                    "Execute" : "startupnotify",
389                                                                    new_menu_element);
390         }
391     }
392 
393     g_signal_handler_unblock (selection, ks.handler_id_row_selected);
394 
395     activate_change_done ();
396 
397     return new_iter;
398 }
399 
400 /*
401 
402     Preperations for the addition of a new menu element and display of the "change values" screen.
403 
404 */
405 
add_new(gchar * new_menu_element_plus_opt_suppl)406 void add_new (gchar *new_menu_element_plus_opt_suppl)
407 {
408     gboolean same_level = TRUE; // Default
409     gchar *new_menu_element = (!g_regex_match_simple ("item.*", new_menu_element_plus_opt_suppl, 0, 0)) ?
410                               new_menu_element_plus_opt_suppl : "item";
411 
412     /*
413         If the selection currently points to a menu a decision has to be made to
414         either put the new element inside that menu or next to it on the same level.
415     */
416     if (STREQ (ks.txt_fields[TYPE_TXT], "menu")) {
417         GtkWidget *dialog;
418         gchar *label_txt;
419 
420         gint result;
421 
422         enum { INSIDE_MENU = 2, CANCEL };
423 
424         label_txt = g_strdup_printf ("Insert new %s on the same level or into the currently selected menu?",
425                                      new_menu_element);
426 
427         create_dialog (&dialog, "Same level or inside menu?", "dialog-question", label_txt,
428                        "_Same level", "_Inside menu", "_Cancel", TRUE); // TRUE == dialog is shown immediately.
429 
430         // Cleanup
431         g_free (label_txt);
432 
433         result = gtk_dialog_run (GTK_DIALOG (dialog));
434         gtk_widget_destroy (dialog);
435         switch (result) {
436             case INSIDE_MENU:
437                 same_level = FALSE;
438                 break;
439             case CANCEL:
440             case GTK_RESPONSE_DELETE_EVENT:
441                 return;
442         }
443     }
444 
445     if (!streq_any (new_menu_element, "menu", "pipe menu", "item", NULL)) {
446         add_new_execution (new_menu_element, same_level);
447         row_selected ();
448     }
449     else {
450         gchar *field_value_pair;
451 
452         gchar *change_values_markup, *default_label_txt;
453 
454         gboolean menu_or_pipe_menu = (!STREQ (new_menu_element, "item"));
455 
456         guint8 entry_cnt;
457 
458         gtk_box_reorder_child (GTK_BOX (ks.main_box), ks.entry_grid, 0);
459 
460         if (STREQ (ks.txt_fields[TYPE_TXT], "menu")) {
461             gtk_widget_show (ks.action_option_grid);
462             gtk_widget_hide (ks.options_grid);
463             gtk_widget_show (ks.new_action_option_widgets[INSIDE_MENU_LABEL]);
464             gtk_widget_show (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]);
465             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]), !same_level);
466             field_value_pair = g_strdup_printf ("inside menu:%s", (same_level) ? "false" : "true");
467             ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, field_value_pair);
468         }
469         if (STREQ (new_menu_element, "item")) {
470             GtkTreeIter action_option_combo_box_iter;
471 
472             gboolean incl_action;
473 
474             guint8 action_cnt;
475 
476 
477             gtk_widget_show (ks.new_action_option_widgets[INCLUDING_ACTION_LABEL]);
478             gtk_widget_show (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]);
479             gtk_widget_show (ks.action_option_grid);
480             incl_action = !STREQ (new_menu_element_plus_opt_suppl, "item w/o action");
481             // Prevent callback
482             g_signal_handler_block (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON],
483                                     ks.handler_id_including_action_check_button);
484             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets [INCLUDING_ACTION_CHECK_BUTTON]), incl_action);
485             g_signal_handler_unblock (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON],
486                                       ks.handler_id_including_action_check_button);
487             field_value_pair = g_strdup_printf ("incl. action:%s", (incl_action) ? "true" : "false");
488             ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, field_value_pair);
489 
490             create_combo_box ();
491 
492             for (action_cnt = 0; action_cnt < NUMBER_OF_ACTIONS; action_cnt++) {
493                 gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1,
494                                                    ACTION_OPTION_COMBO_ITEM, ks.actions[action_cnt], -1);
495             }
496 
497             if (g_regex_match_simple ("item\\+.*", new_menu_element_plus_opt_suppl, 0, 0)) {
498                 gchar *action = extract_substring_via_regex (new_menu_element_plus_opt_suppl, "(?<=\\+).*");
499                 gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), action);
500                 field_value_pair = g_strdup_printf ("action:%s", action);
501                 // Cleanup
502                 g_free (action);
503                 ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, field_value_pair);
504             }
505             else { // "item" or "item w/o action". The action_option combo box is set just in case the user changes his/her mind.
506                 ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, g_strdup ("action:Execute"));
507                 gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), "Execute");
508                 if (!STREQ (new_menu_element_plus_opt_suppl, "item")) {
509                     gtk_widget_hide (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]);
510                 }
511             }
512             // Has to be after a possible setting of the action_option combo box, because this triggers showing the grid.
513             gtk_widget_set_visible (ks.options_grid, incl_action);
514         }
515         else { // menu or pipe menu
516             gtk_widget_hide (ks.options_grid);
517         }
518 
519         hide_or_deactivate_widgets ();
520 
521         change_values_markup = g_strdup_printf ("<span font_desc='%i'>Change values for new %s?</span>",
522                                                 ks.font_size + 2, new_menu_element);
523         gtk_label_set_markup (GTK_LABEL (ks.change_values_label), change_values_markup);
524 
525         // Cleanup
526         g_free (change_values_markup);
527 
528         // Check for change of font size, so that the label's font size can be adjusted in that case.
529         if (!ks.rows_with_icons) {
530             g_timeout_add (1000, (GSourceFunc) check_for_external_file_and_settings_changes, "timeout");
531         }
532 
533         gtk_widget_show (ks.change_values_label);
534         gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
535         gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_CANCEL]);
536         gtk_widget_show (ks.mandatory);
537         if (!STREQ (new_menu_element, "item") && !STREQ (ks.txt_fields[TYPE_TXT], "menu")) {
538             gtk_widget_set_margin_top (ks.mandatory, 5);
539         }
540         gtk_widget_show (ks.separator);
541         gtk_widget_show (ks.change_values_buttons_grid);
542         for (entry_cnt = 0; entry_cnt < NUMBER_OF_ENTRY_FIELDS; entry_cnt++) {
543             g_signal_handler_disconnect (ks.entry_fields[entry_cnt], ks.handler_id_entry_fields[entry_cnt]);
544         }
545         gtk_label_set_markup (GTK_LABEL (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY]),
546                                          " Label (<span foreground='darkred'>*</span>): ");
547         gtk_label_set_markup (GTK_LABEL (ks.entry_labels[EXECUTE_ENTRY]),
548                                          " Execute (<span foreground='darkred'>*</span>): ");
549         gtk_widget_show (ks.icon_chooser);
550         gtk_widget_hide (ks.remove_icon);
551         gtk_widget_show (ks.entry_labels[ICON_PATH_ENTRY]);
552         gtk_widget_show (ks.entry_fields[ICON_PATH_ENTRY]);
553         // In case that currently a (pipe) menu or item with an incorrect iconpath is selected.
554         gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[ICON_PATH_ENTRY]), "mandatory_missing");
555         gtk_widget_set_visible (ks.entry_labels[MENU_ID_ENTRY], menu_or_pipe_menu);
556         gtk_widget_set_visible (ks.entry_fields[MENU_ID_ENTRY], menu_or_pipe_menu);
557         gtk_widget_set_visible (ks.entry_labels[EXECUTE_ENTRY], STREQ (new_menu_element, "pipe menu"));
558         gtk_widget_set_visible (ks.entry_fields[EXECUTE_ENTRY], STREQ (new_menu_element, "pipe menu"));
559 
560         field_value_pair = g_strdup_printf ("new menu element:%s", new_menu_element);
561         ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, field_value_pair);
562         default_label_txt = g_strconcat ("New ", new_menu_element, NULL);
563         gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), default_label_txt);
564         // In case that currently a (pipe) menu or item without label is selected.
565         gtk_widget_set_sensitive (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], TRUE);
566         // Cleanup
567         g_free (default_label_txt);
568 
569         gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), "");
570 
571         if (menu_or_pipe_menu) {
572             gchar *menu_ID;
573             guint menu_id_index = 1;
574 
575             // Menu IDs have to be unique, so the list of menu IDs has to be checked for existing values.
576             while (g_slist_find_custom (ks.menu_ids, menu_ID = g_strdup_printf ("New menu %i", menu_id_index++),
577                                                                                 (GCompareFunc) strcmp)) {
578                 g_free (menu_ID);
579             }
580 
581             field_value_pair = g_strdup_printf ("menu ID field:%s", menu_ID);
582             ks.change_values_user_settings = g_slist_prepend (ks.change_values_user_settings, field_value_pair);
583 
584             gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]), menu_ID);
585             gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]), "");
586 
587             // Cleanup
588             g_free (menu_ID);
589         }
590 
591         gtk_widget_grab_focus (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]);
592 
593         gtk_widget_show (ks.entry_grid);
594     }
595 }
596 
597 /*
598 
599     Creates the action/option combo box. This is done every time from scratch, since a combo box that contains new shorter items
600     doesn't shrink down in size, resulting in a bulky look of the combo box if the size change of the items is considerable.
601 
602 */
603 
create_combo_box(void)604 static void create_combo_box (void)
605 {
606     GtkCellRenderer *action_option_combo_box_renderer;
607 
608     ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX] = gtk_combo_box_new_with_model (ks.action_option_combo_box_model);
609     action_option_combo_box_renderer = gtk_cell_renderer_text_new ();
610     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]),
611                                 action_option_combo_box_renderer, TRUE);
612     gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]),
613                                     action_option_combo_box_renderer, "text", 0, NULL);
614 
615     gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]),
616                                         action_option_combo_box_renderer, option_list_with_headlines,
617                                         NULL, NULL); // No user data, no destroy notify for user data.*/
618 
619     gtk_combo_box_set_id_column (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 0);
620 
621 
622     gtk_grid_attach (GTK_GRID (ks.new_action_option_grid), ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX],
623                      4, 0, 1, 1);
624 
625     gtk_widget_show (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]);
626 
627     ks.handler_id_action_option_combo_box = g_signal_connect (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], "changed",
628                                                               G_CALLBACK (show_action_options), NULL);
629 }
630 
631 /*
632 
633     Sets up a cell layout that includes headlines that are highlighted in colour.
634 
635 */
636 
option_list_with_headlines(G_GNUC_UNUSED GtkCellLayout * cell_layout,GtkCellRenderer * action_option_combo_box_renderer,GtkTreeModel * action_option_combo_box_model,GtkTreeIter * action_option_combo_box_iter,G_GNUC_UNUSED gpointer data)637 void option_list_with_headlines (G_GNUC_UNUSED GtkCellLayout   *cell_layout,
638                                                GtkCellRenderer *action_option_combo_box_renderer,
639                                                GtkTreeModel    *action_option_combo_box_model,
640                                                GtkTreeIter     *action_option_combo_box_iter,
641                                  G_GNUC_UNUSED gpointer         data)
642 {
643     gchar *action_option_combo_item;
644 
645     gboolean headline;
646 
647     gtk_tree_model_get (action_option_combo_box_model, action_option_combo_box_iter,
648                         ACTION_OPTION_COMBO_ITEM, &action_option_combo_item,
649                         -1);
650 
651     headline = g_regex_match_simple ("Add|Choose", action_option_combo_item, G_REGEX_ANCHORED, 0);
652 
653     g_object_set (action_option_combo_box_renderer,
654                   "foreground-rgba", (headline) ? &((GdkRGBA) { 1.0, 1.0, 1.0, 1.0 }) : NULL,
655                   "background-rgba", (g_str_has_prefix (action_option_combo_item, "Choose")) ?
656                                      &((GdkRGBA) { 0.31, 0.31, 0.79, 1.0 }) :
657                                      ((g_str_has_prefix (action_option_combo_item, "Add")) ?
658                                      &((GdkRGBA) { 0.0, 0.0, 0.0, 1.0 }) : NULL),
659                   "sensitive", !headline,
660                   NULL);
661 
662     // Cleanup
663     g_free (action_option_combo_item);
664 }
665 
666 /*
667 
668     Appends possible options for Execute or startupnotify to combobox.
669 
670 */
671 
generate_action_option_combo_box_items_for_Exe_and_snotify_opts(gchar * options_type)672 static guint8 generate_action_option_combo_box_items_for_Exe_and_snotify_opts (gchar *options_type)
673 {
674     gboolean execute = STREQ (options_type, "Execute");
675     GtkTreeIter parent, action_option_combo_box_new_iter;
676     const guint8 number_of_opts = (execute) ? NUMBER_OF_EXECUTE_OPTS : NUMBER_OF_STARTUPNOTIFY_OPTS;
677     const guint allocated_size = sizeof (gboolean) * number_of_opts;
678     gboolean *opts_exist = g_slice_alloc0 (allocated_size); // Initialise all elements to FALSE.
679 
680     guint8 number_of_added_opts = 0; // Start value
681     guint8 opts_cnt;
682 
683     if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], options_type)) { // "Execute" or "startupnotify"
684         parent = ks.iter;
685     }
686     else {
687         gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
688     }
689 
690     check_for_existing_options (&parent, number_of_opts, (execute) ? ks.execute_options : ks.startupnotify_options, opts_exist);
691 
692     for (opts_cnt = 0; opts_cnt < number_of_opts; opts_cnt++) {
693         if (!opts_exist[opts_cnt]) {
694             gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_new_iter, -1,
695                                                ACTION_OPTION_COMBO_ITEM, (execute) ?
696                                                ks.execute_displayed_txts[opts_cnt] : ks.startupnotify_displayed_txts[opts_cnt],
697                                                -1);
698             number_of_added_opts++;
699         }
700     }
701 
702     // Cleanup
703     g_slice_free1 (allocated_size, opts_exist);
704 
705     return number_of_added_opts;
706 }
707 
708 /*
709 
710     Shows the action combo box, to which a dynamically generated list of options has been added.
711 
712 */
713 
generate_items_for_action_option_combo_box(gchar * preset_choice)714 void generate_items_for_action_option_combo_box (gchar *preset_choice)
715 {
716     GtkTreeIter action_option_combo_box_iter;
717 
718     const gchar *bt_add_action_option_label_txt;
719 
720     gchar *add_inside_txt = NULL; // Initialization avoids compiler warning.
721 
722     guint8 number_of_added_actions = 0, number_of_added_action_opts = 0, number_of_added_snotify_opts = 0; // Defaults
723 
724 
725     // --- Creating combo box and building combo list. ---
726 
727 
728     create_combo_box ();
729 
730     // Generate "Choose..." headline according to the text of bt_add_action_option_label.
731     bt_add_action_option_label_txt = gtk_label_get_text (GTK_LABEL (ks.bt_add_action_option_label));
732     gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1,
733                                        ACTION_OPTION_COMBO_ITEM,
734                                        (STREQ (bt_add_action_option_label_txt, "Action")) ? "Choose an action" :
735                                        (STREQ (bt_add_action_option_label_txt, "Option")) ?
736                                        "Choose an option" : "Choose an action/option",
737                                        -1);
738 
739     // Startupnotify options: enabled, name, wmclass, icon
740     if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) &&
741         streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "startupnotify", "enabled", "name", "wmclass", "icon", NULL)) {
742         number_of_added_snotify_opts = generate_action_option_combo_box_items_for_Exe_and_snotify_opts ("startupnotify");
743     }
744 
745     // Execute options: prompt, command and startupnotify
746     if ((STREQ (ks.txt_fields[TYPE_TXT], "action") && STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Execute")) ||
747         (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) &&
748         streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", "startupnotify", NULL))) {
749         number_of_added_action_opts = generate_action_option_combo_box_items_for_Exe_and_snotify_opts ("Execute");
750     }
751 
752     // Exit and SessionLogout option: prompt, Restart option: command
753     if (STREQ (ks.txt_fields[TYPE_TXT], "action") &&
754         streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "Restart", "SessionLogout", NULL) &&
755         !gtk_tree_model_iter_has_child (ks.model, &ks.iter)) {
756         gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1,
757                                            ACTION_OPTION_COMBO_ITEM,
758                                            (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")) ? "Command" : "Prompt",
759                                            -1);
760         number_of_added_action_opts = 1;
761     }
762 
763     // Actions
764     if (streq_any (ks.txt_fields[TYPE_TXT], "item", "action", NULL)) {
765         for (; number_of_added_actions < NUMBER_OF_ACTIONS; number_of_added_actions++) {
766             gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1,
767                                                ACTION_OPTION_COMBO_ITEM, ks.actions[number_of_added_actions],
768                                               -1);
769         }
770     }
771 
772     /*
773         Check if there are two kinds of addable actions/options: the first one inside the current selection,
774         the second one next to the current selection. If so, add headlines to the list to separate them from each other.
775     */
776     if (number_of_added_snotify_opts && number_of_added_action_opts) {
777         add_inside_txt = "Add inside startupnotify";
778     }
779     else if (number_of_added_action_opts && number_of_added_actions) {
780         add_inside_txt = g_strdup_printf ("Add inside currently selected %s action", ks.txt_fields[MENU_ELEMENT_TXT]);
781     }
782 
783     if (add_inside_txt) {
784         gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, 1,
785                                            ACTION_OPTION_COMBO_ITEM, add_inside_txt,
786                                            -1);
787 
788         /*
789             Position of "Add additional action" headline:
790             "Add inside currently selected xxx action" + number of added action options + "Add additional action" ==
791             1 + number_of_added_action_opts + 1 -> number_of_added_action_opts + 2
792             Position of "Add next to startupnotify" headline:
793             "Add inside startupnotify" + number of added startupnotify options + "Add next to startupnotify" ==
794             1 + number_of_added_snotify_opts + 1 -> number_of_added_snotify_opts + 2
795         */
796         gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter,
797                                            ((number_of_added_actions) ?
798                                            number_of_added_action_opts : number_of_added_snotify_opts) + 2,
799                                            ACTION_OPTION_COMBO_ITEM, (number_of_added_actions) ?
800                                            "Add additional action" : "Add next to startupnotify",
801                                            -1);
802 
803         // Cleanup
804         if (number_of_added_actions) {
805             g_free (add_inside_txt);
806         }
807     }
808 
809     // Show the action combo box and deactivate or hide all events and widgets that could interfere.
810     hide_or_deactivate_widgets ();
811 
812     gtk_widget_show (ks.action_option_grid);
813     gtk_widget_hide (ks.options_grid);
814 
815     /*
816         If this function was called by the context menu or a "Startupnotify" button,
817         preselect the appropriate combo box item.
818     */
819     if (preset_choice) {
820         gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), preset_choice);
821         // If there is only one choice, there is nothing to choose from the combo box.
822         if (gtk_tree_model_iter_n_children (ks.action_option_combo_box_model, NULL) == 2) {
823             gtk_widget_set_sensitive (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], FALSE);
824         }
825     }
826     else {
827         gtk_combo_box_set_active (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 0);
828         gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
829     }
830 
831     if (!gtk_widget_get_visible (ks.change_values_label)) {
832         gtk_widget_set_margin_top (ks.new_action_option_grid, 0);
833     }
834 
835     gtk_widget_queue_draw (GTK_WIDGET (ks.treeview)); // Force redrawing of treeview (if a search was active).
836 }
837 
838 /*
839 
840     Shows the fields that may be changed according to the chosen action/option.
841 
842 */
843 
show_action_options(void)844 void show_action_options (void)
845 {
846     const gchar *combo_choice = gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]));
847     gboolean with_new_item = gtk_widget_get_visible (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]);
848 
849     guint8 options_cnt, snotify_opts_cnt;
850 
851     clear_entries ();
852 
853     // Defaults
854     gtk_widget_set_margin_bottom (ks.action_option_grid, 5);
855     gtk_widget_set_visible (ks.options_grid, !STREQ (combo_choice, "Reconfigure"));
856     for (options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; options_cnt++) {
857         gtk_widget_hide (ks.options_labels[options_cnt]);
858         gtk_widget_hide (ks.options_fields[options_cnt]);
859     }
860     gtk_widget_hide (ks.suboptions_grid);
861     for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
862         gtk_widget_show (ks.suboptions_labels[snotify_opts_cnt]);
863         gtk_widget_show (ks.suboptions_fields[snotify_opts_cnt]);
864     }
865     gtk_label_set_text (GTK_LABEL (ks.options_labels[PROMPT]), " Prompt: ");
866     if (!with_new_item) {
867         gtk_widget_show (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
868         gtk_widget_hide (ks.mandatory);
869     }
870     gtk_widget_set_margin_bottom (ks.mandatory, (with_new_item) ? 0 : 5);
871     gtk_widget_grab_focus ((with_new_item) ? ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY] : ks.options_fields[PROMPT]);
872 
873     if (streq_any (combo_choice, "Execute", "Startupnotify", "Enabled", "Name", "WM_CLASS", "Icon", NULL)) {
874         gchar *suboptions_label_txt;
875 
876         for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
877             suboptions_label_txt = g_strconcat (ks.startupnotify_displayed_txts[snotify_opts_cnt],
878                                                 streq_any (combo_choice, "Execute", "Startupnotify", "Enabled", NULL) ? ": " :
879                                                 " (<span foreground='darkred'>*</span>): ",
880                                                 NULL);
881             gtk_label_set_markup (GTK_LABEL (ks.suboptions_labels[snotify_opts_cnt]), suboptions_label_txt);
882 
883             // Cleanup
884             g_free (suboptions_label_txt);
885         }
886     }
887 
888     // "Execute" action
889     if (STREQ (combo_choice, "Execute")) {
890         for (options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; options_cnt++) {
891             gtk_label_set_markup (GTK_LABEL (ks.options_labels[options_cnt]), (options_cnt != COMMAND) ?
892                                                                               ks.options_label_txts[options_cnt] :
893                                                                               " Command (<span foreground='darkred'>*</span>): ");
894             gtk_widget_show (ks.options_labels[options_cnt]);
895             gtk_widget_show (ks.options_fields[options_cnt]);
896         }
897         gtk_widget_show (ks.mandatory);
898         if (!g_signal_handler_is_connected (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options)) {
899             ks.handler_id_show_or_hide_startupnotify_options = g_signal_connect (ks.options_fields[SN_OR_PROMPT], "toggled",
900                                                                                  G_CALLBACK (show_or_hide_startupnotify_options), NULL);
901         }
902     }
903 
904     // "Restart" action or "command" option (the latter for "Restart" and "Execute" actions)
905     if (streq_any (combo_choice, "Restart", "Command", NULL)) {
906         gtk_label_set_markup (GTK_LABEL (ks.options_labels[COMMAND]), (STREQ (combo_choice, "Restart")) ?
907                                                                       " Command: " :
908                                                                       " Command (<span foreground='darkred'>*</span>): ");
909         gtk_widget_show (ks.options_labels[COMMAND]);
910         gtk_widget_show (ks.options_fields[COMMAND]);
911         if (STREQ (combo_choice, "Command")) {
912             gtk_widget_show (ks.mandatory);
913         }
914         gtk_widget_grab_focus (ks.options_fields[COMMAND]);
915     }
916 
917     // "Exit" or "SessionLogout" action or "prompt" option (the latter for "Execute", "Exit" and "SessionLogout" actions)
918     if (streq_any (combo_choice, "Exit", "Prompt", "SessionLogout", NULL)) {
919          if (!STREQ (combo_choice, "Prompt") ||
920             (STREQ (combo_choice, "Prompt") && streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "SessionLogout", NULL))) {
921             gtk_label_set_text (GTK_LABEL (ks.options_labels[SN_OR_PROMPT]), " Prompt: ");
922             gtk_widget_show (ks.options_labels[SN_OR_PROMPT]);
923             if (g_signal_handler_is_connected (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options)) {
924                 g_signal_handler_disconnect (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options);
925             }
926             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
927             gtk_widget_show (ks.options_fields[SN_OR_PROMPT]);
928             gtk_widget_grab_focus (ks.options_fields[SN_OR_PROMPT]);
929         }
930         else { // Option of "Execute" action.
931             gtk_widget_show (ks.options_labels[PROMPT]);
932             gtk_label_set_markup (GTK_LABEL (ks.options_labels[PROMPT]), " Prompt (<span foreground='darkred'>*</span>): ");
933             gtk_widget_show (ks.options_fields[PROMPT]);
934             gtk_widget_show (ks.mandatory);
935 
936         }
937     }
938 
939     // "startupnotify" option and its suboptions
940     if (streq_any (combo_choice, "Startupnotify", "Enabled", "Name", "WM_CLASS", "Icon", NULL)) {
941         gtk_widget_show (ks.suboptions_grid);
942 
943         if (STREQ (combo_choice, "Startupnotify")) {
944             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
945             gtk_widget_grab_focus (ks.suboptions_fields[ENABLED]);
946         }
947         else { // Enabled, Name, WM_CLASS & Icon
948             for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
949                 if (!STREQ (combo_choice, ks.startupnotify_displayed_txts[snotify_opts_cnt])) {
950                     gtk_widget_hide (ks.suboptions_labels[snotify_opts_cnt]);
951                     gtk_widget_hide (ks.suboptions_fields[snotify_opts_cnt]);
952                 }
953                 else {
954                     gtk_widget_grab_focus (ks.suboptions_fields[snotify_opts_cnt]);
955                 }
956             }
957             if (!STREQ (combo_choice, "Enabled")) {
958                 gtk_widget_show (ks.mandatory);
959             }
960         }
961     }
962 }
963 
964 /*
965 
966     Shows the options for startupnotify after the corresponding check box has been clicked.
967 
968 */
969 
show_or_hide_startupnotify_options(void)970 void show_or_hide_startupnotify_options (void)
971 {
972     gboolean show_startupnotify_options = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]));
973 
974     gtk_widget_set_visible (ks.suboptions_grid, show_startupnotify_options);
975     if (!show_startupnotify_options) {
976         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
977         gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[NAME]), "");
978         gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[WM_CLASS]), "");
979         gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[ICON]), "");
980     }
981 }
982 
983 /*
984 
985     If only a single entry field is shown, "Enter" is enough, clicking on "Done" is not necessary.
986 
987 */
988 
single_field_entry(void)989 void single_field_entry (void)
990 {
991     /*
992         Applies to
993         - the _single_ "Execute" options "command" and "prompt"
994         - the _single_ "startupnotify" options "name", "wmclass" and "icon"
995         - the "Restart" action and its option "command".
996 
997         If action fields are shown in conjunctin if item fields, single field entry is not executed.
998     */
999     if (!gtk_widget_get_visible (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]) &&
1000         !streq_any (gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])),
1001                     "Execute", "Startupnotify", NULL)) {
1002         action_option_insert ("by combo box");
1003     }
1004 }
1005 
1006 /*
1007 
1008    Resets everything after the cancel button has been clicked or an insertion has been done.
1009 
1010 */
1011 
hide_action_option_grid(gchar * origin)1012 void hide_action_option_grid (gchar *origin)
1013 {
1014     clear_entries ();
1015     gtk_widget_set_sensitive (ks.mb_options, TRUE);
1016     gtk_widget_hide (ks.action_option_grid);
1017     gtk_widget_set_margin_bottom (ks.action_option_grid, 0);
1018     gtk_widget_set_margin_top (ks.new_action_option_grid, 5);
1019 
1020     gtk_widget_show (ks.button_grid);
1021     if (*ks.search_term->str) {
1022         gtk_widget_show (ks.find_grid);
1023     }
1024     gtk_widget_hide (ks.mandatory);
1025     gtk_widget_set_margin_bottom (ks.mandatory, 0);
1026 
1027     if (STREQ (origin, "cancel button")) {
1028         row_selected (); // Reset visibility of menu bar and toolbar items.
1029     }
1030 }
1031 
1032 /*
1033 
1034     Clears all entries for actions/options.
1035 
1036 */
1037 
clear_entries(void)1038 static void clear_entries (void)
1039 {
1040     guint8 options_cnt, snotify_opts_cnt;
1041 
1042     for (options_cnt = PROMPT; options_cnt <= COMMAND; options_cnt++) {
1043         gtk_entry_set_text (GTK_ENTRY (ks.options_fields[options_cnt]), "");
1044         gtk_style_context_remove_class (gtk_widget_get_style_context (ks.options_fields[options_cnt]), "mandatory_missing");
1045     }
1046     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), FALSE);
1047     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
1048     for (snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
1049         gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt]), "");
1050         gtk_style_context_remove_class (gtk_widget_get_style_context (ks.suboptions_fields[snotify_opts_cnt]),
1051                                         "mandatory_missing");
1052     }
1053 }
1054 
1055 /*
1056 
1057     Inserts an action and/or its option(s) with the entered values.
1058 
1059 */
1060 
action_option_insert(gchar * origin)1061 void action_option_insert (gchar *origin)
1062 {
1063     const gchar *combo_choice = (STREQ (origin, "by combo box")) ?
1064                                 gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])) :
1065                                 "Reconfigure";
1066     gboolean options_check_button_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]));
1067     gboolean enabled_check_button_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]));
1068     const gchar *options_entries[] = { gtk_entry_get_text (GTK_ENTRY (ks.options_fields[PROMPT])),
1069                                        gtk_entry_get_text (GTK_ENTRY (ks.options_fields[COMMAND])) };
1070     const gchar *suboption_entry = NULL; // Initialization avoids compiler warning.
1071 
1072     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
1073     GtkTreeIter new_iter, new_iter2, parent;
1074     GtkTreeIter execute_parent = ks.iter; // Initialization avoids compiler warning.
1075     GtkTreeIter execute_iter;
1076 
1077     gint insertion_position = -1; // Default
1078 
1079     // Defaults
1080     gboolean action = FALSE;
1081     gboolean execute_done = FALSE;
1082     gboolean startupnotify_done = FALSE;
1083     gchar *option_of_execute = NULL, *option_of_startupnotify = NULL;
1084 
1085     // Defaults
1086     gboolean used_Execute_opts[NUMBER_OF_EXECUTE_OPTS] = { FALSE };
1087     guint8 snotify_start = NAME;
1088 
1089     guint8 execute_opts_cnt, snotify_opts_cnt;
1090 
1091 
1092     // If only one entry or a mandatory field was displayed, check if it has been filled out.
1093     if (gtk_widget_get_visible (ks.action_option_grid)) {
1094         if (streq_any (combo_choice, "Execute", "Command", NULL) && !(*options_entries[COMMAND])) {
1095             wrong_or_missing (ks.options_fields[COMMAND], ks.execute_options_css_providers[COMMAND]);
1096             return;
1097         }
1098         else if (STREQ (combo_choice, "Prompt") && // "Execute" action or option of it currently selected.
1099                  gtk_widget_get_visible (ks.options_fields[PROMPT]) && !(*options_entries[PROMPT])) {
1100             wrong_or_missing (ks.options_fields[PROMPT], ks.execute_options_css_providers[PROMPT]);
1101             return;
1102         }
1103         else {
1104              for (snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
1105                 if (STREQ (combo_choice, ks.startupnotify_displayed_txts[snotify_opts_cnt]) &&
1106                     !(*gtk_entry_get_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt])))) {
1107                     // "Enabled" is not part of the css_providers, thus - 1.
1108                     wrong_or_missing (ks.suboptions_fields[snotify_opts_cnt], ks.suboptions_fields_css_providers[snotify_opts_cnt - 1]);
1109                     return;
1110                 }
1111             }
1112         }
1113     }
1114 
1115     gtk_widget_hide (ks.mandatory);
1116     gtk_widget_set_margin_bottom (ks.mandatory, 0);
1117 
1118     /*
1119         Check if the insertion will be done at the current level and before the last child of the current parent.
1120         If so, set the insertion position.
1121     */
1122     if (!(STREQ (ks.txt_fields[TYPE_TXT], "item") ||
1123           (STREQ (ks.txt_fields[TYPE_TXT], "action") &&
1124            streq_any (combo_choice, "Prompt", "Command", "Startupnotify", NULL)) ||
1125           (STREQ (ks.txt_fields[TYPE_TXT], "option block") &&
1126            !streq_any (combo_choice, "Prompt", "Command", NULL)))) {
1127         GtkTreePath *path = gtk_tree_model_get_path (ks.model, &ks.iter);
1128         gint last_path_index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];
1129 
1130         gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
1131         if (gtk_tree_model_iter_n_children (ks.model, &parent) > 1 &&
1132             last_path_index < gtk_tree_model_iter_n_children (ks.model, &parent) - 1) {
1133             insertion_position = last_path_index + 1;
1134         }
1135 
1136         // Cleanup
1137         gtk_tree_path_free (path);
1138     }
1139 
1140 
1141     // --- Create the new row(s). ---
1142 
1143 
1144     // Prevents that the default check for change of selection(s) gets in the way.
1145     g_signal_handler_block (selection, ks.handler_id_row_selected);
1146     gtk_tree_selection_unselect_all (selection); // The old selection will be replaced by the one of the new row.
1147 
1148     // Execute
1149     if (STREQ (combo_choice, "Execute")) {
1150         if (STREQ (ks.txt_fields[TYPE_TXT], "action")) {
1151             gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
1152             ks.iter = parent; // Move up one level and start from there.
1153         }
1154 
1155         gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
1156                                            TS_MENU_ELEMENT, "Execute",
1157                                            TS_TYPE, "action",
1158                                            -1);
1159 
1160         execute_parent = ks.iter;
1161         execute_iter = new_iter;
1162         ks.iter = new_iter; // New base level
1163 
1164         action = TRUE;
1165         execute_done = TRUE;
1166         insertion_position = -1; // Reset for the options, since they should be always appended.
1167     }
1168 
1169     // Prompt - only if it is an Execute option and not empty.
1170     if ((execute_done || STREQ (combo_choice, "Prompt")) && *options_entries[PROMPT]) {
1171         used_Execute_opts[PROMPT] = TRUE;
1172     }
1173 
1174     // Command - only if it is an Execute option.
1175     if ((execute_done ||
1176         (STREQ (combo_choice, "Command") && !STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")))) {
1177         used_Execute_opts[COMMAND] = TRUE;
1178     }
1179 
1180     // Startupnotify
1181     if ((execute_done && options_check_button_state) || STREQ (combo_choice, "Startupnotify")) {
1182         used_Execute_opts[STARTUPNOTIFY] = TRUE;
1183     }
1184 
1185     // Insert Execute option, if used.
1186     for (execute_opts_cnt = 0; execute_opts_cnt < NUMBER_OF_EXECUTE_OPTS; execute_opts_cnt++) {
1187         if (used_Execute_opts[execute_opts_cnt]) {
1188             if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL)) {
1189                 gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
1190                 ks.iter = parent; // Move up one level and start from there.
1191             }
1192             gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
1193                                                TS_MENU_ELEMENT, ks.execute_options[execute_opts_cnt],
1194                                                TS_TYPE, (execute_opts_cnt != STARTUPNOTIFY) ? "option" : "option block",
1195                                                TS_VALUE, (execute_opts_cnt != STARTUPNOTIFY) ?
1196                                                           options_entries[execute_opts_cnt] : NULL,
1197                                                -1);
1198 
1199             if (!execute_done) { // Single option, not via an insertion of an "Execute" action.
1200                 option_of_execute = ks.execute_options[execute_opts_cnt];
1201                 expand_row_from_iter (&ks.iter);
1202                 gtk_tree_selection_select_iter (selection, &new_iter);
1203                 if (execute_opts_cnt == STARTUPNOTIFY) {
1204                     startupnotify_done = TRUE;
1205                 }
1206             }
1207         }
1208     }
1209 
1210     // Setting parent iterator for startupnotify options, if they are added as single elements.
1211     if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) &&
1212         !streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", NULL)) {
1213         if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "startupnotify")) {
1214             new_iter = ks.iter;
1215         }
1216         else {
1217             gtk_tree_model_iter_parent (ks.model, &new_iter, &ks.iter);
1218         }
1219     }
1220 
1221     // Enabled
1222     if ((execute_done && options_check_button_state) || streq_any (combo_choice, "Startupnotify", "Enabled", NULL)) {
1223         snotify_start = ENABLED;
1224     }
1225 
1226     // Insert startupnotify option, if used.
1227     for (snotify_opts_cnt = snotify_start; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
1228         if (snotify_opts_cnt == ENABLED ||
1229             *(suboption_entry = gtk_entry_get_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt])))) {
1230             gtk_tree_store_insert_with_values (ks.treestore, &new_iter2, &new_iter, insertion_position,
1231                                                TS_MENU_ELEMENT, ks.startupnotify_options[snotify_opts_cnt],
1232                                                TS_TYPE, "option",
1233                                                TS_VALUE, (snotify_opts_cnt == ENABLED) ?
1234                                                          ((enabled_check_button_state) ? "yes" : "no") :
1235                                                           suboption_entry,
1236                                               -1);
1237 
1238             expand_row_from_iter (&new_iter);
1239             if (!execute_done && !startupnotify_done) { // Single option.
1240                 option_of_startupnotify = ks.startupnotify_options[snotify_opts_cnt];
1241                 gtk_tree_selection_select_iter (selection, &new_iter2);
1242             }
1243         }
1244     }
1245 
1246     // Action other than Execute
1247     if (streq_any (combo_choice, "Exit", "Reconfigure", "Restart", "SessionLogout", NULL)) {
1248         if (STREQ (ks.txt_fields[TYPE_TXT], "action")) {
1249             gtk_tree_model_iter_parent (ks.model, &parent, &ks.iter);
1250             ks.iter = parent; // Move up one level and start from there.
1251         }
1252 
1253         gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
1254                                            TS_MENU_ELEMENT, combo_choice,
1255                                            TS_TYPE, "action",
1256                                            -1);
1257 
1258         if (!STREQ (combo_choice, "Reconfigure") && !(STREQ (combo_choice, "Restart") && !(*options_entries[COMMAND]))) {
1259             gtk_tree_store_insert_with_values (ks.treestore, &new_iter2, &new_iter, -1,
1260                                                TS_MENU_ELEMENT, (STREQ (combo_choice, "Restart")) ? "command" : "prompt",
1261                                                TS_TYPE, "option",
1262                                                TS_VALUE, (STREQ (combo_choice, "Restart")) ? options_entries[COMMAND] :
1263                                                           ((options_check_button_state) ? "yes" : "no"),
1264                                                -1);
1265         }
1266 
1267         action = TRUE;
1268     }
1269 
1270     // Exit & SessionLogout option (prompt) or Restart option (command)
1271     if ((STREQ (combo_choice, "Prompt") && gtk_widget_get_visible (ks.options_fields[SN_OR_PROMPT])) ||
1272         (STREQ (combo_choice, "Command") && STREQ (ks.txt_fields [MENU_ELEMENT_TXT], "Restart"))) {
1273         gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, -1,
1274                                            TS_MENU_ELEMENT, (STREQ (combo_choice, "Prompt")) ? "prompt" : "command",
1275                                            TS_TYPE, "option",
1276                                            TS_VALUE, (STREQ (combo_choice, "Command")) ? options_entries[COMMAND] :
1277                                                       ((options_check_button_state) ? "yes" : "no"),
1278                                            -1);
1279 
1280         expand_row_from_iter (&ks.iter);
1281         gtk_tree_selection_select_iter (selection, &new_iter);
1282     }
1283 
1284     /*
1285         Show all children of the new action. If the parent row had not been expanded, expand it is well,
1286         but leave all preceding nodes (if any) of the new action collapsed.
1287     */
1288     if (action) {
1289         if (execute_done) {
1290             ks.iter = execute_parent;
1291         }
1292         GtkTreePath *parent_path = gtk_tree_model_get_path (ks.model, &ks.iter);
1293         GtkTreePath *path = gtk_tree_model_get_path (ks.model, (execute_done) ? &execute_iter : &new_iter);
1294 
1295         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (ks.treeview), parent_path)) {
1296             gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), parent_path, FALSE);
1297         }
1298         // TRUE == expand recursively, this is for a new Execute action with startupnotify options, so the latter are shown.
1299         gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path, TRUE);
1300 
1301         if (!gtk_widget_get_visible (ks.change_values_label)) {
1302             gtk_tree_selection_select_path (selection, path);
1303         }
1304 
1305         // Cleanup
1306         gtk_tree_path_free (parent_path);
1307         gtk_tree_path_free (path);
1308     }
1309 
1310     if (ks.autosort_options) {
1311         // If the new row is an option of Execute, sort all options of the latter and move the selection to the new option.
1312         if (option_of_execute && gtk_tree_model_iter_n_children (ks.model, &ks.iter) > 1) {
1313              sort_execute_or_startupnotify_options_after_insertion (selection, &ks.iter, "Execute", option_of_execute);
1314         }
1315 
1316         // The same for startupnotify. new_iter always points to an "option block" == startupnotify.
1317         if (option_of_startupnotify && gtk_tree_model_iter_n_children (ks.model, &new_iter) > 1) {
1318             sort_execute_or_startupnotify_options_after_insertion (selection, &new_iter,
1319                                                                    "startupnotify", option_of_startupnotify);
1320         }
1321     }
1322 
1323     hide_action_option_grid ("action option insert");
1324 
1325     g_signal_handler_unblock (selection, ks.handler_id_row_selected);
1326     activate_change_done ();
1327     if (!gtk_widget_get_visible (ks.change_values_label)) {
1328         row_selected ();
1329     }
1330 }
1331 
1332 /*
1333 
1334     Checks for menus or pipe menus that are descendants of the currently processed row.
1335     If found, their menu ID is passed to a function that removes this ID from the list of menu IDs.
1336 
1337 */
1338 
check_for_menus(GtkTreeModel * filter_model,G_GNUC_UNUSED GtkTreePath * filter_path,GtkTreeIter * filter_iter)1339 static gboolean check_for_menus (              GtkTreeModel *filter_model,
1340                                  G_GNUC_UNUSED GtkTreePath  *filter_path,
1341                                                GtkTreeIter  *filter_iter)
1342 {
1343     gchar *type_txt_filter, *menu_id_txt_filter;
1344 
1345     gtk_tree_model_get (filter_model, filter_iter, TS_TYPE, &type_txt_filter, -1);
1346 
1347     if (streq_any (type_txt_filter, "menu", "pipe menu", NULL)) {
1348         gtk_tree_model_get (filter_model, filter_iter, TS_MENU_ID, &menu_id_txt_filter, -1);
1349 
1350         remove_menu_id (menu_id_txt_filter);
1351 
1352         // Cleanup
1353         g_free (menu_id_txt_filter);
1354     }
1355 
1356     // Cleanup
1357     g_free (type_txt_filter);
1358 
1359     return FALSE;
1360 }
1361 
1362 /*
1363 
1364     Removes a menu ID from the list of menu IDs.
1365 
1366 */
1367 
remove_menu_id(gchar * menu_id)1368 void remove_menu_id (gchar *menu_id)
1369 {
1370     GSList *to_be_removed_element = g_slist_find_custom (ks.menu_ids, menu_id, (GCompareFunc) strcmp);
1371 
1372     g_free (to_be_removed_element->data);
1373     ks.menu_ids = g_slist_remove (ks.menu_ids, to_be_removed_element->data);
1374 }
1375 
1376 /*
1377 
1378     Removes all children of a node.
1379 
1380     Children are removed from bottom to top so the paths of the other children and nodes are kept.
1381     After the removal of the children the function checks for the existence of each parent
1382     before it reselects them, because if subnodes of the selected nodes had also been selected,
1383     they can't be reselected again since they got removed.
1384 
1385 */
1386 
remove_all_children(void)1387 void remove_all_children (void)
1388 {
1389     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
1390     /*
1391         The list of rows is reverted, since if a top to bottom direction was kept and
1392         children of selected nodes was also selected, these children would be selected for removal first,
1393         but since their own children are also intended for removal, they are unselected again.
1394 
1395         Example:
1396 
1397         Selection done by user:
1398 
1399         -menu ::: selected for removal of children
1400             -menu
1401             -menu ::: selected for removal of child
1402                 -menu
1403             -menu
1404 
1405         First selection loop run:
1406 
1407         -menu 1. element whose children are selected
1408             -menu ::: selected
1409             -menu ::: selected
1410                 -menu
1411             -menu ::: selected
1412 
1413         Second selection loop run:
1414 
1415         -menu
1416             -menu ::: selected
1417             -menu -> 2. element whose children are selected, unintended unselection
1418                 -menu ::: selected
1419             -menu ::: selected
1420     */
1421     GList *selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.model));
1422 
1423     GList *selected_rows_loop;
1424     GtkTreePath *path_loop;
1425     GtkTreeIter iter_loop;
1426 
1427     // Prevents that the default check for change of selection(s) gets in the way.
1428     g_signal_handler_block (selection, ks.handler_id_row_selected);
1429 
1430     for (selected_rows_loop = selected_rows; selected_rows_loop; selected_rows_loop = selected_rows_loop->next) {
1431         path_loop = selected_rows_loop->data;
1432         // (Note: Rows are not selected if they are not visible.)
1433         gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path_loop, FALSE);
1434         gtk_tree_selection_unselect_path (selection, path_loop);
1435         gtk_tree_path_down (path_loop);
1436         gtk_tree_model_get_iter (ks.model, &iter_loop, path_loop);
1437         do {
1438             gtk_tree_selection_select_iter (selection, &iter_loop);
1439         } while (gtk_tree_model_iter_next (ks.model, &iter_loop));
1440         gtk_tree_path_up (path_loop);
1441     }
1442 
1443     remove_rows ("rm. all chldn.");
1444 
1445     g_signal_handler_unblock (selection, ks.handler_id_row_selected);
1446 
1447     for (selected_rows_loop = selected_rows; selected_rows_loop; selected_rows_loop = selected_rows_loop->next) {
1448         // Might be a former subnode that was selected and got removed.
1449         if (gtk_tree_model_get_iter (ks.model, &iter_loop, selected_rows_loop->data)) {
1450             gtk_tree_selection_select_iter (selection, &iter_loop);
1451         }
1452     }
1453 
1454     // Cleanup
1455     g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
1456 }
1457 
1458 /*
1459 
1460     Removes all currently selected rows.
1461 
1462 */
1463 
remove_rows(gchar * origin)1464 void remove_rows (gchar *origin)
1465 {
1466     GtkTreeModel *filter_model;
1467     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
1468     // The list of rows is reverted, since only with a bottom to top direction the paths will stay the same.
1469     GList *selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.model));
1470 
1471     GList *selected_rows_loop;
1472     GtkTreePath *path_loop;
1473     GtkTreeIter iter_loop;
1474 
1475     gchar *type_txt_loop, *menu_id_txt_loop;
1476 
1477     // Prevents that the default check for change of selection(s) gets in the way.
1478     g_signal_handler_block (selection, ks.handler_id_row_selected);
1479 
1480     for (selected_rows_loop = selected_rows; selected_rows_loop; selected_rows_loop = selected_rows_loop->next) {
1481         path_loop = selected_rows_loop->data;
1482         gtk_tree_model_get_iter (ks.model, &iter_loop, path_loop);
1483 
1484         if (!STREQ (origin, "dnd")) { // A drag & drop just moves rows; there are no new, deleted or changed menu IDs in this case.
1485             gtk_tree_model_get (ks.model, &iter_loop, TS_TYPE, &type_txt_loop, -1);
1486 
1487             if (streq_any (type_txt_loop, "menu", "pipe menu", NULL)) {
1488                 // Keep menu IDs in the GSList equal to the menu IDs of the treestore.
1489                 gtk_tree_model_get (ks.model, &iter_loop, TS_MENU_ID, &menu_id_txt_loop, -1);
1490                 remove_menu_id (menu_id_txt_loop);
1491 
1492                 /*
1493                     If the to be deleted row is a menu that contains other menus as children,
1494                     their menu IDs have to be deleted, too.
1495                 */
1496                 if (gtk_tree_model_iter_has_child (ks.model, &iter_loop)) { // Has to be a menu, because pipe menus don't have children.
1497                     filter_model = gtk_tree_model_filter_new (ks.model, path_loop);
1498                     gtk_tree_model_foreach (filter_model, (GtkTreeModelForeachFunc) check_for_menus, NULL);
1499 
1500                     // Cleanup
1501                     g_object_unref (filter_model);
1502                 }
1503                 // Cleanup
1504                 g_free (menu_id_txt_loop);
1505             }
1506             // Cleanup
1507             g_free (type_txt_loop);
1508         }
1509 
1510         gtk_tree_store_remove (GTK_TREE_STORE (ks.model), &iter_loop);
1511     }
1512 
1513     // If all rows have been deleted and the search functionality had been activated before, deactivate the latter.
1514     if (!gtk_tree_model_get_iter_first (ks.model, &iter_loop) && gtk_widget_get_visible (ks.find_grid)) {
1515         show_or_hide_find_grid ();
1516     }
1517 
1518     g_signal_handler_unblock (selection, ks.handler_id_row_selected);
1519 
1520     // Cleanup
1521     g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
1522 
1523     if (!streq_any (origin, "dnd", "load menu", NULL)) { // There might be further changes during Drag & Drop / loading a menu.
1524         activate_change_done ();
1525         row_selected ();
1526     }
1527 }
1528