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 
22 #include <locale.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "definitions_and_enumerations.h"
27 #include "kickshaw.h"
28 
29 ks_data ks = {
30     .options_label_txts = { " Prompt: ", " Command: ", " Startupnotify " },
31 
32     .actions = { "Execute", "Exit", "Reconfigure", "Restart", "SessionLogout" },
33 
34     .execute_displayed_txts = { "Prompt", "Command", "Startupnotify" },
35     .execute_options = { "prompt", "command", "startupnotify" },
36 
37     .startupnotify_options = { "enabled", "name", "wmclass", "icon" },
38     .startupnotify_displayed_txts = { "Enabled", "Name", "WM_CLASS", "Icon" },
39 
40     .column_header_txts = { "Menu Element", "Type", "Value", "Menu ID", "Execute", "Element Visibility" }
41 };
42 
43 static void general_initialisiation (void);
44 static void add_button_content (GtkWidget *button, gchar *label_text);
45 static void set_column_attributes (G_GNUC_UNUSED GtkTreeViewColumn *cell_column,
46                                                  GtkCellRenderer   *txt_renderer,
47                                                  GtkTreeModel      *cell_model,
48                                                  GtkTreeIter       *cell_iter,
49                                                  gpointer           column_number_pointer);
50 static void change_view_and_options (gpointer activated_menu_item_pointer);
51 void set_status_of_expand_and_collapse_buttons_and_menu_items (void);
52 static void about (void);
53 gboolean continue_despite_unsaved_changes (void);
54 void clear_global_data (void);
55 static void new_menu (void);
56 static void quit_program (void);
57 void activate_change_done (void);
58 static void write_settings (void);
59 void set_filename_and_window_title (gchar *new_filename);
60 void show_errmsg (gchar *errmsg_raw_txt);
61 void show_msg_in_statusbar (gchar *message);
62 
main(G_GNUC_UNUSED int argc,char * argv[])63 int main (G_GNUC_UNUSED int argc, char *argv[])
64 {
65     // --- Startup checks ---
66 
67 
68     // ### Display program version. ###
69 
70     if (G_UNLIKELY (STREQ (argv[1], "--version"))) {
71         g_print ("Kickshaw " KICKSHAW_VERSION
72 "\nCopyright (C) 2010-2018    Marcus Schaetzle\
73 \n\nKickshaw comes with ABSOLUTELY NO WARRANTY.\
74 \nYou may redistribute copies of Kickshaw\
75 \nunder the terms of the GNU General Public License.\
76 \nFor more information about these matters, see the file named COPYING.\n");
77         exit (EXIT_SUCCESS);
78     }
79 
80     // ### Check if a windowing system is running. ###
81 
82     if (G_UNLIKELY (!g_getenv ("DISPLAY"))) {
83         g_printerr ("No windowing system currently running, program aborted.\n");
84         exit (EXIT_FAILURE);
85     }
86 
87 
88     // --- Initialise GTK, create the GUI and set up everything else. ---
89 
90     ks.app = gtk_application_new ("savannah.nongnu.org.kickshaw", G_APPLICATION_FLAGS_NONE);
91     gint status;
92 
93     g_signal_connect_swapped (ks.app, "activate", G_CALLBACK (general_initialisiation), NULL);
94 
95     status = g_application_run (G_APPLICATION (ks.app), 0, NULL);
96 
97     g_object_unref (ks.app);
98 
99     return status;
100 }
101 
102 /*
103 
104     Creates GUI and signals, also loads settings and standard menu file, if they exist.
105 
106 */
107 
general_initialisiation(void)108 static void general_initialisiation (void)
109 {
110     GList *windows = gtk_application_get_windows (ks.app);
111 
112     if (G_UNLIKELY (windows)) {
113         gtk_window_present (GTK_WINDOW (windows->data));
114 
115         return;
116     }
117 
118     GtkWidget *main_grid;
119 
120     GtkWidget *menubar;
121     GSList *element_visibility_menu_item_group = NULL, *grid_menu_item_group = NULL;
122     GtkWidget *mb_file, *mb_find, *mb_view, *mb_help,
123               *mb_show_element_visibility_column, *mb_show_grid, *mb_about;
124     enum { FILE_MENU, EDIT_MENU, SEARCH_MENU, VIEW_MENU, OPTIONS_MENU, HELP_MENU,
125            SHOW_ELEMENT_VISIBILITY_COLUMN_MENU, SHOW_GRID_MENU, NUMBER_OF_SUBMENUS };
126     GtkWidget *submenus[NUMBER_OF_SUBMENUS];
127     GtkMenuShell *view_submenu;
128 
129     GtkAccelGroup *accel_group = NULL;
130     guint accel_key;
131     GdkModifierType accel_mod;
132 
133     gchar *file_menu_item_txts[] = { "_New", "_Open", "_Save", "Save As", "", "_Quit"};
134     gchar *file_menu_item_accelerators[] = { "<Ctl>N", "<Ctl>O", "<Ctl>S", "<Shift><Ctl>S", "", "<Ctl>Q"};
135 
136     gchar *visibility_txts[] = { "Show Element Visibility column", "Keep highlighting", "Don't keep highlighting"};
137     gchar *grid_txts[] = { "No grid lines", "Horizontal", "Vertical", "Both" };
138     guint default_checked[] = { SHOW_MENU_ID_COL, SHOW_EXECUTE_COL, SHOW_ICONS, SET_OFF_SEPARATORS, SHOW_TREE_LINES };
139     guint8 txts_cnt;
140 
141     GtkWidget *toolbar;
142     gchar *button_IDs[] = { "document-new", "document-open", "document-save", "document-save-as", "go-up",
143                             "go-down", "list-remove", "edit-find", "zoom-in", "zoom-out", "application-exit"};
144     gchar *tb_tooltips[] = { "New menu", "Open menu", "Save menu", "Save menu as...", "Move up",
145                              "Move down", "Remove", "Find", "Expand all", "Collapse all", "Quit" };
146     GtkToolItem *tb_separator;
147 
148     // Label text of the last button is set dynamically dependent on the type of the selected row.
149     gchar *bt_add_txts[] = { "_Menu", "_Pipe Menu", "_Item", "Sepa_rator", "" };
150     gchar *add_txts[] = { "menu", "pipe menu", "item", "separator" };
151 
152     GtkCssProvider *change_values_label_css_provider;
153     enum { CHANGE_VALUES_CANCEL, CHANGE_VALUES_RESET, CHANGE_VALUES_DONE, NUMBER_OF_CHANGE_VALUES_BUTTONS };
154     GtkWidget *change_values_buttons[NUMBER_OF_CHANGE_VALUES_BUTTONS];
155     gchar *change_values_button_txts[NUMBER_OF_CHANGE_VALUES_BUTTONS] = { "_Cancel", "_Reset", "_Done" };
156 
157     gchar *find_entry_buttons_imgs[] = { "window-close", "go-previous", "go-next" };
158     gchar *find_entry_buttons_tooltips[] = { "Close", "Previous", "Next" };
159     enum { ENTRY, COLUMNS_SELECTION, SPECIAL_OPTIONS, NUMBER_OF_FIND_SUBGRIDS };
160     GtkWidget *find_subgrids[NUMBER_OF_FIND_SUBGRIDS];
161 
162     GtkWidget *scrolled_window;
163     GtkTreeSelection *selection;
164 
165     const gchar *mime_types[] = { "text/plain;charset=utf-8" };
166 
167     gchar *settings_file_path;
168 
169     gchar *new_filename;
170 
171     guint8 buttons_cnt, columns_cnt, entry_fields_cnt, grids_cnt, mb_menu_items_cnt,
172            options_cnt, snotify_opts_cnt, submenus_cnt, view_and_opts_cnt, widgets_cnt;
173 
174 
175     setlocale (LC_ALL,"en_US.utf8");
176 
177 
178     // --- Creating the GUI. ---
179 
180 
181     // ### Create a new window. ###
182 
183 
184     ks.window = gtk_application_window_new (GTK_APPLICATION (ks.app));
185 
186     gtk_window_set_title (GTK_WINDOW (ks.window), "Kickshaw");
187 
188     gtk_window_set_position (GTK_WINDOW (ks.window), GTK_WIN_POS_CENTER);
189 
190     g_signal_connect_swapped (ks.window, "destroy", G_CALLBACK (g_application_quit), G_APPLICATION (ks.app));
191 
192 
193     // ### Create a new grid that will contain all elements. ###
194 
195     main_grid = gtk_grid_new ();
196     gtk_grid_set_column_homogeneous (GTK_GRID (main_grid), TRUE);
197     gtk_orientable_set_orientation (GTK_ORIENTABLE (main_grid), GTK_ORIENTATION_VERTICAL);
198     gtk_container_add (GTK_CONTAINER (ks.window), main_grid);
199 
200     // ### Create a menu bar. ###
201 
202     menubar = gtk_menu_bar_new ();
203     gtk_container_add (GTK_CONTAINER (main_grid), menubar);
204 
205     // Submenus
206     for (submenus_cnt = 0; submenus_cnt < NUMBER_OF_SUBMENUS; submenus_cnt++) {
207         submenus[submenus_cnt] = gtk_menu_new ();
208     }
209 
210     // Accelerator Group
211     accel_group = gtk_accel_group_new ();
212     gtk_window_add_accel_group (GTK_WINDOW (ks.window), accel_group);
213 
214     // File
215     mb_file = gtk_menu_item_new_with_mnemonic ("_File");
216 
217     for (mb_menu_items_cnt = MB_NEW; mb_menu_items_cnt <= MB_QUIT; mb_menu_items_cnt++) {
218         if (mb_menu_items_cnt != MB_SEPARATOR_FILE) {
219             ks.mb_file_menu_items[mb_menu_items_cnt] = gtk_menu_item_new_with_mnemonic (file_menu_item_txts[mb_menu_items_cnt]);
220             gtk_accelerator_parse (file_menu_item_accelerators[mb_menu_items_cnt], &accel_key, &accel_mod);
221             gtk_widget_add_accelerator (ks.mb_file_menu_items[mb_menu_items_cnt], "activate", accel_group,
222                                         accel_key, accel_mod, GTK_ACCEL_VISIBLE);
223         }
224         else {
225             ks.mb_file_menu_items[MB_SEPARATOR_FILE] = gtk_separator_menu_item_new ();
226         }
227     }
228 
229     for (mb_menu_items_cnt = 0; mb_menu_items_cnt < NUMBER_OF_FILE_MENU_ITEMS; mb_menu_items_cnt++) {
230         gtk_menu_shell_append (GTK_MENU_SHELL (submenus[FILE_MENU]), ks.mb_file_menu_items[mb_menu_items_cnt]);
231     }
232 
233     gtk_menu_item_set_submenu (GTK_MENU_ITEM (mb_file), submenus[FILE_MENU]);
234     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), mb_file);
235 
236     // Edit
237     ks.mb_edit = gtk_menu_item_new_with_mnemonic ("_Edit");
238 
239     ks.mb_edit_menu_items[MB_MOVE_TOP] = gtk_menu_item_new_with_label ("Move to top");
240     gtk_accelerator_parse ("<Ctl>T", &accel_key, &accel_mod);
241     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_MOVE_TOP], "activate", accel_group, accel_key, accel_mod,
242                                 GTK_ACCEL_VISIBLE);
243     ks.mb_edit_menu_items[MB_MOVE_UP] = gtk_menu_item_new_with_label ("Move up");
244     gtk_accelerator_parse ("<Ctl>minus", &accel_key, &accel_mod);
245     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_MOVE_UP], "activate", accel_group, accel_key, accel_mod,
246                                 GTK_ACCEL_VISIBLE);
247     ks.mb_edit_menu_items[MB_MOVE_DOWN] = gtk_menu_item_new_with_label ("Move down");
248     gtk_accelerator_parse ("<Ctl>plus", &accel_key, &accel_mod);
249     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_MOVE_DOWN], "activate", accel_group, accel_key, accel_mod,
250                                 GTK_ACCEL_VISIBLE);
251     ks.mb_edit_menu_items[MB_MOVE_BOTTOM] = gtk_menu_item_new_with_label ("Move to bottom");
252     gtk_accelerator_parse ("<Ctl>B", &accel_key, &accel_mod);
253     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_MOVE_BOTTOM], "activate", accel_group, accel_key, accel_mod,
254                                 GTK_ACCEL_VISIBLE);
255     ks.mb_edit_menu_items[MB_REMOVE] = gtk_menu_item_new_with_label ("Remove");
256     gtk_accelerator_parse ("<Ctl>R", &accel_key, &accel_mod);
257     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_REMOVE], "activate", accel_group, accel_key, accel_mod,
258                                 GTK_ACCEL_VISIBLE);
259     ks.mb_edit_menu_items[MB_SEPARATOR_EDIT1] = gtk_separator_menu_item_new ();
260     ks.mb_edit_menu_items[MB_REMOVE_ALL_CHILDREN] = gtk_menu_item_new_with_label ("Remove all children");
261     gtk_accelerator_parse ("<Ctl><Shift>R", &accel_key, &accel_mod);
262     gtk_widget_add_accelerator (ks.mb_edit_menu_items[MB_REMOVE_ALL_CHILDREN], "activate", accel_group, accel_key, accel_mod,
263                                 GTK_ACCEL_VISIBLE);
264     ks.mb_edit_menu_items[MB_SEPARATOR_EDIT2] = gtk_separator_menu_item_new ();
265     ks.mb_edit_menu_items[MB_VISUALISE] = gtk_menu_item_new_with_label ("Visualise");
266     ks.mb_edit_menu_items[MB_VISUALISE_RECURSIVELY] = gtk_menu_item_new_with_label ("Visualise recursively");
267 
268     for (mb_menu_items_cnt = 0; mb_menu_items_cnt < NUMBER_OF_EDIT_MENU_ITEMS; mb_menu_items_cnt++) {
269         gtk_menu_shell_append (GTK_MENU_SHELL (submenus[EDIT_MENU]), ks.mb_edit_menu_items[mb_menu_items_cnt]);
270     }
271     gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.mb_edit), submenus[EDIT_MENU]);
272     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.mb_edit);
273 
274     // Search
275     ks.mb_search = gtk_menu_item_new_with_mnemonic ("_Search");
276 
277     mb_find = gtk_menu_item_new_with_mnemonic ("_Find");
278     gtk_accelerator_parse ("<Ctl>F", &accel_key, &accel_mod);
279     gtk_widget_add_accelerator (mb_find, "activate", accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);
280 
281     gtk_menu_shell_append (GTK_MENU_SHELL (submenus[SEARCH_MENU]), mb_find);
282     gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.mb_search), submenus[SEARCH_MENU]);
283     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.mb_search);
284 
285     // View
286     mb_view = gtk_menu_item_new_with_mnemonic ("_View");
287 
288     ks.mb_expand_all_nodes = gtk_menu_item_new_with_label ("Expand all nodes");
289     gtk_accelerator_parse ("<Ctl><Shift>X", &accel_key, &accel_mod);
290     gtk_widget_add_accelerator (ks.mb_expand_all_nodes, "activate", accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);
291     ks.mb_collapse_all_nodes = gtk_menu_item_new_with_label ("Collapse all nodes");
292     gtk_accelerator_parse ("<Ctl><Shift>C", &accel_key, &accel_mod);
293     gtk_widget_add_accelerator (ks.mb_collapse_all_nodes, "activate", accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);
294     ks.mb_view_and_options[SHOW_MENU_ID_COL] = gtk_check_menu_item_new_with_label ("Show Menu ID column");
295     ks.mb_view_and_options[SHOW_EXECUTE_COL] = gtk_check_menu_item_new_with_label ("Show Execute column");
296     mb_show_element_visibility_column = gtk_menu_item_new_with_label ("Show Element Visibility column");
297     ks.mb_view_and_options[SHOW_ICONS] = gtk_check_menu_item_new_with_label ("Show icons");
298     ks.mb_view_and_options[SET_OFF_SEPARATORS] = gtk_check_menu_item_new_with_label ("Set off separators");
299     ks.mb_view_and_options[DRAW_ROWS_IN_ALT_COLOURS] =
300         gtk_check_menu_item_new_with_label ("Draw rows in altern. colours (theme dep.)");
301     ks.mb_view_and_options[SHOW_TREE_LINES] = gtk_check_menu_item_new_with_label ("Show tree lines");
302     mb_show_grid = gtk_menu_item_new_with_label ("Show grid");
303 
304     for (mb_menu_items_cnt = SHOW_ELEMENT_VISIBILITY_COL_ACTVTD, txts_cnt = 0;
305          mb_menu_items_cnt <= SHOW_ELEMENT_VISIBILITY_COL_DONT_KEEP_HIGHL;
306          mb_menu_items_cnt++, txts_cnt++) {
307         if (mb_menu_items_cnt == SHOW_ELEMENT_VISIBILITY_COL_ACTVTD) {
308             ks.mb_view_and_options[mb_menu_items_cnt] = gtk_check_menu_item_new_with_label (visibility_txts[txts_cnt]);
309         }
310         else {
311             ks.mb_view_and_options[mb_menu_items_cnt] = gtk_radio_menu_item_new_with_label (element_visibility_menu_item_group,
312                                                                                             visibility_txts[txts_cnt]);
313             element_visibility_menu_item_group =
314                 gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (ks.mb_view_and_options[mb_menu_items_cnt]));
315         }
316         gtk_menu_shell_append (GTK_MENU_SHELL (submenus[SHOW_ELEMENT_VISIBILITY_COLUMN_MENU]),
317                                ks.mb_view_and_options[mb_menu_items_cnt]);
318     }
319     gtk_menu_item_set_submenu (GTK_MENU_ITEM (mb_show_element_visibility_column),
320         submenus[SHOW_ELEMENT_VISIBILITY_COLUMN_MENU]);
321 
322     for (mb_menu_items_cnt = NO_GRID_LINES, txts_cnt = 0;
323          mb_menu_items_cnt <= BOTH;
324          mb_menu_items_cnt++, txts_cnt++) {
325         ks.mb_view_and_options[mb_menu_items_cnt] = gtk_radio_menu_item_new_with_label (grid_menu_item_group,
326                                                     grid_txts[txts_cnt]);
327         grid_menu_item_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (ks.mb_view_and_options[mb_menu_items_cnt]));
328         gtk_menu_shell_append (GTK_MENU_SHELL (submenus[SHOW_GRID_MENU]), ks.mb_view_and_options[mb_menu_items_cnt]);
329     }
330     gtk_menu_item_set_submenu (GTK_MENU_ITEM (mb_show_grid), submenus[SHOW_GRID_MENU]);
331 
332     view_submenu = GTK_MENU_SHELL (submenus[VIEW_MENU]);
333     gtk_menu_shell_append (view_submenu, ks.mb_expand_all_nodes);
334     gtk_menu_shell_append (view_submenu, ks.mb_collapse_all_nodes);
335     gtk_menu_shell_append (view_submenu, gtk_separator_menu_item_new ());
336     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[SHOW_MENU_ID_COL]);
337     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[SHOW_EXECUTE_COL]);
338     gtk_menu_shell_append (view_submenu, mb_show_element_visibility_column);
339     gtk_menu_shell_append (view_submenu, gtk_separator_menu_item_new ());
340     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[SHOW_ICONS]);
341     gtk_menu_shell_append (view_submenu, gtk_separator_menu_item_new ());
342     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[SET_OFF_SEPARATORS]);
343     gtk_menu_shell_append (view_submenu, gtk_separator_menu_item_new ());
344     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[DRAW_ROWS_IN_ALT_COLOURS]);
345     gtk_menu_shell_append (view_submenu, ks.mb_view_and_options[SHOW_TREE_LINES]);
346     gtk_menu_shell_append (view_submenu, mb_show_grid);
347     gtk_menu_item_set_submenu (GTK_MENU_ITEM (mb_view), submenus[VIEW_MENU]);
348     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), mb_view);
349 
350     // Default settings
351     for (mb_menu_items_cnt = 0; mb_menu_items_cnt < G_N_ELEMENTS (default_checked); mb_menu_items_cnt++) {
352         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[default_checked[mb_menu_items_cnt]]), TRUE);
353     }
354     gtk_widget_set_sensitive (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], FALSE);
355     gtk_widget_set_sensitive (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_DONT_KEEP_HIGHL], FALSE);
356 
357     // Options
358     ks.mb_options = gtk_menu_item_new_with_mnemonic ("_Options");
359     ks.mb_view_and_options[CREATE_BACKUP_BEFORE_OVERWRITING_MENU] =
360         gtk_check_menu_item_new_with_label ("Create backup before overwriting menu");
361     ks.mb_view_and_options[SORT_EXECUTE_AND_STARTUPN_OPTIONS] =
362         gtk_check_menu_item_new_with_label ("Sort execute/startupnotify options");
363     ks.mb_view_and_options[NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS] =
364         gtk_check_menu_item_new_with_label ("Always notify about execute opt. conversions");
365 
366     gtk_menu_shell_append (GTK_MENU_SHELL (submenus[OPTIONS_MENU]), ks.mb_view_and_options[CREATE_BACKUP_BEFORE_OVERWRITING_MENU]);
367     gtk_menu_shell_append (GTK_MENU_SHELL (submenus[OPTIONS_MENU]), ks.mb_view_and_options[SORT_EXECUTE_AND_STARTUPN_OPTIONS]);
368     gtk_menu_shell_append (GTK_MENU_SHELL (submenus[OPTIONS_MENU]), ks.mb_view_and_options[NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS]);
369     gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.mb_options), submenus[OPTIONS_MENU]);
370     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.mb_options);
371 
372     // Default settings
373     gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[CREATE_BACKUP_BEFORE_OVERWRITING_MENU]), TRUE);
374     gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS]), TRUE);
375 
376     // Help
377     mb_help = gtk_menu_item_new_with_mnemonic ("_Help");
378 
379     mb_about = gtk_menu_item_new_with_label ("About");
380 
381     gtk_menu_shell_append (GTK_MENU_SHELL (submenus[HELP_MENU]), mb_about);
382     gtk_menu_item_set_submenu (GTK_MENU_ITEM (mb_help), submenus[HELP_MENU]);
383     gtk_menu_shell_append (GTK_MENU_SHELL (menubar), mb_help);
384 
385     // ### Create toolbar. ###
386 
387     toolbar = gtk_toolbar_new ();
388     gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS);
389     gtk_container_add (GTK_CONTAINER (main_grid), toolbar);
390 
391     gtk_widget_set_margin_top (toolbar, 2);
392     gtk_widget_set_margin_bottom (toolbar, 2);
393 
394     for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_TB_BUTTONS; buttons_cnt++) {
395         switch (buttons_cnt) {
396             case TB_MOVE_UP:
397             case TB_FIND:
398             case TB_QUIT:
399                 tb_separator = gtk_separator_tool_item_new ();
400                 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tb_separator, -1);
401         }
402 
403         ks.tb[buttons_cnt] = gtk_tool_button_new (NULL, NULL);
404         gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (ks.tb[buttons_cnt]), button_IDs[buttons_cnt]);
405 
406         gtk_widget_set_tooltip_text (GTK_WIDGET (ks.tb[buttons_cnt]), tb_tooltips[buttons_cnt]);
407         gtk_toolbar_insert (GTK_TOOLBAR (toolbar), ks.tb[buttons_cnt], -1);
408     }
409 
410     // ### Create Button Bar. ###
411 
412     ks.button_grid = gtk_grid_new ();
413     gtk_container_add (GTK_CONTAINER (main_grid), ks.button_grid);
414 
415     ks.add_image = gtk_image_new_from_icon_name ("list-add");
416     gtk_container_add (GTK_CONTAINER (ks.button_grid), ks.add_image);
417 
418     // Label text is set dynamically dependent on whether it is possible to add a row or not.
419     ks.bt_bar_label = gtk_label_new (NULL);
420     gtk_container_add (GTK_CONTAINER (ks.button_grid), ks.bt_bar_label);
421 
422     // Label text is set dynamically dependent on the type of the selected row.
423     ks.bt_add_action_option_label = gtk_label_new (NULL);
424     gtk_label_set_use_underline (GTK_LABEL (ks.bt_add_action_option_label), TRUE);
425 
426     for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_ADD_BUTTONS; buttons_cnt++) {
427         ks.bt_add[buttons_cnt] = gtk_button_new ();
428         add_button_content (ks.bt_add[buttons_cnt], bt_add_txts[buttons_cnt]);
429         gtk_container_add (GTK_CONTAINER (ks.button_grid), ks.bt_add[buttons_cnt]);
430     }
431 
432 
433     ks.change_values_label = gtk_label_new ("");
434     gtk_container_add (GTK_CONTAINER (main_grid), ks.change_values_label);
435     gtk_widget_set_margin_bottom (ks.change_values_label, 12);
436     change_values_label_css_provider = gtk_css_provider_new ();
437     gtk_style_context_add_provider (gtk_widget_get_style_context (ks.change_values_label),
438                                     GTK_STYLE_PROVIDER (change_values_label_css_provider),
439                                     GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
440     gtk_css_provider_load_from_data (change_values_label_css_provider,
441                                      ".label { border-top-style:groove;"
442                                      "         border-bottom-style:groove;"
443                                      "         border-width:2px;"
444                                      "         border-color:#a8a8a8;"
445                                      "         padding:3px; }",
446                                      -1);
447 
448 
449     ks.main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
450     gtk_container_add (GTK_CONTAINER (main_grid), ks.main_box);
451 
452 
453     // ### Grid for entering the values of new rows. ###
454 
455 
456     ks.action_option_grid = gtk_grid_new ();
457     gtk_orientable_set_orientation (GTK_ORIENTABLE (ks.action_option_grid), GTK_ORIENTATION_VERTICAL);
458     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.action_option_grid);
459 
460     // List store and model for action/option combo box; the combo box isn't yet created here, only when needed.
461     ks.action_option_combo_box_liststore = gtk_list_store_new (NUMBER_OF_ACTION_OPTION_COMBO_ELEMENTS, G_TYPE_STRING);
462     ks.action_option_combo_box_model = GTK_TREE_MODEL (ks.action_option_combo_box_liststore);
463 
464     ks.new_action_option_grid = gtk_grid_new ();
465 
466     ks.new_action_option_widgets[INSIDE_MENU_LABEL] = gtk_label_new (" Inside menu ");
467 
468     ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON] = gtk_check_button_new ();
469 
470     ks.new_action_option_widgets[INCLUDING_ACTION_LABEL] = gtk_label_new (" Incl. action ");
471 
472     ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON] = gtk_check_button_new ();
473     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]), TRUE);
474 
475     ks.new_action_option_widgets[ACTION_OPTION_DONE] = gtk_button_new ();
476     add_button_content (ks.new_action_option_widgets[ACTION_OPTION_DONE], "_Done");
477 
478     ks.new_action_option_widgets[ACTION_OPTION_CANCEL] = gtk_button_new ();
479     add_button_content (ks.new_action_option_widgets[ACTION_OPTION_CANCEL], "_Cancel");
480 
481     gtk_container_add (GTK_CONTAINER (ks.action_option_grid), ks.new_action_option_grid);
482     gtk_widget_set_margin_top (ks.new_action_option_grid, 5);
483     gtk_widget_set_margin_bottom (ks.new_action_option_grid, 5);
484 
485     for (widgets_cnt = 0; widgets_cnt < NUMBER_OF_NEW_ACTION_OPTION_WIDGETS; widgets_cnt++) {
486         if (widgets_cnt != NEW_ACTION_OPTION_COMBO_BOX) {
487             gtk_grid_attach (GTK_GRID (ks.new_action_option_grid), ks.new_action_option_widgets[widgets_cnt],
488                              widgets_cnt, 0, 1, 1);
489         }
490         else {
491             // Column 5 remains empty until the action/option combo box is attached to it when needed.
492             gtk_grid_insert_column (GTK_GRID (ks.new_action_option_grid), 4);
493         }
494     }
495 
496     // Execute options
497     ks.options_grid = gtk_grid_new ();
498     gtk_container_add (GTK_CONTAINER (ks.action_option_grid), ks.options_grid);
499 
500     ks.options_fields[SN_OR_PROMPT] = gtk_check_button_new ();
501 
502     for (options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; options_cnt++) {
503         ks.options_labels[options_cnt] = gtk_label_new (ks.options_label_txts[options_cnt]);
504 
505         gtk_widget_set_halign (ks.options_labels[options_cnt], GTK_ALIGN_START);
506 
507         if (options_cnt < SN_OR_PROMPT) {
508             ks.options_fields[options_cnt] = gtk_entry_new ();
509             ks.execute_options_css_providers[options_cnt] = gtk_css_provider_new ();
510             gtk_style_context_add_provider (gtk_widget_get_style_context (ks.options_fields[options_cnt]),
511                                             GTK_STYLE_PROVIDER (ks.execute_options_css_providers[options_cnt]),
512                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
513             gtk_widget_set_hexpand (ks.options_fields[options_cnt], TRUE);
514         }
515         gtk_grid_attach (GTK_GRID (ks.options_grid), ks.options_labels[options_cnt], 0, options_cnt,
516                          (options_cnt < SN_OR_PROMPT) ? 2 : 1, 1);
517         gtk_grid_attach (GTK_GRID (ks.options_grid), ks.options_fields[options_cnt],
518                          (options_cnt < SN_OR_PROMPT) ? 2 : 1, options_cnt, 1, 1);
519     }
520 
521     ks.suboptions_grid = gtk_grid_new ();
522     gtk_grid_attach (GTK_GRID (ks.options_grid), ks.suboptions_grid, 2, 2, 1, 1);
523     gtk_widget_set_margin_start (ks.suboptions_grid, 2);
524 
525     for (snotify_opts_cnt = ENABLED; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
526         ks.suboptions_labels[snotify_opts_cnt] = gtk_label_new (NULL); // the actual label text is set later.
527 
528         gtk_widget_set_halign (ks.suboptions_labels[snotify_opts_cnt], GTK_ALIGN_START);
529 
530         gtk_grid_attach (GTK_GRID (ks.suboptions_grid), ks.suboptions_labels[snotify_opts_cnt], 0, snotify_opts_cnt, 1, 1);
531 
532         ks.suboptions_fields[snotify_opts_cnt] = (snotify_opts_cnt == ENABLED) ? gtk_check_button_new () : gtk_entry_new ();
533         if (snotify_opts_cnt > ENABLED) {
534             ks.suboptions_fields_css_providers[snotify_opts_cnt - 1] = gtk_css_provider_new ();
535             gtk_style_context_add_provider (gtk_widget_get_style_context (ks.suboptions_fields[snotify_opts_cnt]),
536                                             GTK_STYLE_PROVIDER (ks.suboptions_fields_css_providers[snotify_opts_cnt - 1]),
537                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
538         }
539         gtk_grid_attach (GTK_GRID (ks.suboptions_grid), ks.suboptions_fields[snotify_opts_cnt], 1, snotify_opts_cnt, 1, 1);
540 
541         gtk_widget_set_hexpand (ks.suboptions_fields[snotify_opts_cnt], TRUE);
542     }
543 
544     ks.mandatory = gtk_label_new (NULL);
545     gtk_label_set_markup (GTK_LABEL (ks.mandatory), "(<span foreground='darkred'>*</span>) = mandatory");
546     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.mandatory);
547 
548     ks.separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
549     gtk_widget_set_margin_top (ks.separator, 10);
550     gtk_widget_set_margin_bottom (ks.separator, 10);
551     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.separator);
552 
553     ks.change_values_buttons_grid = gtk_grid_new ();
554     gtk_grid_set_column_spacing (GTK_GRID (ks.change_values_buttons_grid), 10);
555     gtk_widget_set_margin_bottom (ks.change_values_buttons_grid, 10);
556     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.change_values_buttons_grid);
557 
558     for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_CHANGE_VALUES_BUTTONS; buttons_cnt++) {
559         change_values_buttons[buttons_cnt] = gtk_button_new_with_mnemonic (change_values_button_txts[buttons_cnt]);
560         gtk_container_add (GTK_CONTAINER (ks.change_values_buttons_grid), change_values_buttons[buttons_cnt]);
561     }
562     gtk_widget_set_halign (ks.change_values_buttons_grid, GTK_ALIGN_CENTER);
563 
564     // ### Create find grid. ###
565 
566     ks.find_grid = gtk_grid_new ();
567     gtk_orientable_set_orientation (GTK_ORIENTABLE (ks.find_grid), GTK_ORIENTATION_VERTICAL);
568     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.find_grid);
569 
570     for (grids_cnt = 0; grids_cnt < NUMBER_OF_FIND_SUBGRIDS; grids_cnt++) {
571         find_subgrids[grids_cnt] = gtk_grid_new ();
572         gtk_container_add (GTK_CONTAINER (ks.find_grid), find_subgrids[grids_cnt]);
573     }
574 
575     for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_FIND_ENTRY_BUTTONS; buttons_cnt++) {
576         ks.find_entry_buttons[buttons_cnt] = gtk_button_new_from_icon_name (find_entry_buttons_imgs[buttons_cnt]);
577         gtk_widget_set_tooltip_text (GTK_WIDGET (ks.find_entry_buttons[buttons_cnt]), find_entry_buttons_tooltips[buttons_cnt]);
578         gtk_container_add (GTK_CONTAINER (find_subgrids[ENTRY]), ks.find_entry_buttons[buttons_cnt]);
579     }
580 
581     ks.find_entry = gtk_entry_new ();
582     ks.find_entry_css_provider = gtk_css_provider_new ();
583     gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_entry),
584                                     GTK_STYLE_PROVIDER (ks.find_entry_css_provider),
585                                     GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
586 
587     gtk_widget_set_hexpand (ks.find_entry, TRUE);
588     gtk_container_add (GTK_CONTAINER (find_subgrids[ENTRY]), ks.find_entry);
589 
590     for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS - 1; columns_cnt++) {
591         ks.find_in_columns[columns_cnt] = gtk_check_button_new_with_label (ks.column_header_txts[columns_cnt]);
592         gtk_container_add (GTK_CONTAINER (find_subgrids[COLUMNS_SELECTION]), ks.find_in_columns[columns_cnt]);
593 
594         ks.find_in_columns_css_providers[columns_cnt] = gtk_css_provider_new ();
595         gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_in_columns[columns_cnt]),
596                                         GTK_STYLE_PROVIDER (ks.find_in_columns_css_providers[columns_cnt]),
597                                         GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
598     }
599 
600     ks.find_in_all_columns = gtk_check_button_new_with_label ("All columns");
601     gtk_container_add (GTK_CONTAINER (find_subgrids[COLUMNS_SELECTION]), ks.find_in_all_columns);
602     ks.find_in_all_columns_css_provider = gtk_css_provider_new ();
603     gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_in_all_columns),
604                                     GTK_STYLE_PROVIDER (ks.find_in_all_columns_css_provider),
605                                     GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
606 
607     ks.find_match_case = gtk_check_button_new_with_label ("Match case");
608     gtk_container_add (GTK_CONTAINER (find_subgrids[SPECIAL_OPTIONS]), ks.find_match_case);
609 
610     ks.find_regular_expression = gtk_check_button_new_with_label ("Regular expression (PCRE)");
611     gtk_container_add (GTK_CONTAINER (find_subgrids[SPECIAL_OPTIONS]), ks.find_regular_expression);
612 
613     // Default setting
614     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_regular_expression), TRUE);
615 
616     // ### Initialise Tree View. ###
617 
618     ks.treeview = gtk_tree_view_new ();
619     gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (ks.treeview), TRUE); // Default
620 
621     // Create columns with cell renderers and add them to the treeview.
622 
623     for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS; columns_cnt++) {
624         ks.columns[columns_cnt] = gtk_tree_view_column_new ();
625         gtk_tree_view_column_set_title (ks.columns[columns_cnt], ks.column_header_txts[columns_cnt]);
626 
627         if (columns_cnt == COL_MENU_ELEMENT) {
628             ks.renderers[PIXBUF_RENDERER] = gtk_cell_renderer_pixbuf_new ();
629             ks.renderers[EXCL_TXT_RENDERER] = gtk_cell_renderer_text_new ();
630             gtk_tree_view_column_pack_start (ks.columns[COL_MENU_ELEMENT], ks.renderers[PIXBUF_RENDERER], FALSE);
631             gtk_tree_view_column_pack_start (ks.columns[COL_MENU_ELEMENT], ks.renderers[EXCL_TXT_RENDERER], FALSE);
632             gtk_tree_view_column_set_attributes (ks.columns[COL_MENU_ELEMENT], ks.renderers[PIXBUF_RENDERER],
633                                                  "pixbuf", TS_ICON_IMG, NULL);
634         }
635         else if (columns_cnt == COL_VALUE) {
636             ks.renderers[BOOL_RENDERER] = gtk_cell_renderer_toggle_new ();
637             g_signal_connect (ks.renderers[BOOL_RENDERER], "toggled", G_CALLBACK (boolean_toggled), NULL);
638             gtk_tree_view_column_pack_start (ks.columns[COL_VALUE], ks.renderers[BOOL_RENDERER], FALSE);
639         }
640 
641         ks.renderers[TXT_RENDERER] = gtk_cell_renderer_text_new ();
642         g_signal_connect (ks.renderers[TXT_RENDERER], "edited", G_CALLBACK (cell_edited), GUINT_TO_POINTER ((guint) columns_cnt));
643         gtk_tree_view_column_pack_start (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER], FALSE);
644         // TREEVIEW_COLUMN_OFFSET is used to skip the icon related columns in the treestore.
645         gtk_tree_view_column_set_attributes (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER],
646                                              "text", columns_cnt + TREEVIEW_COLUMN_OFFSET, NULL);
647 
648         gtk_tree_view_column_set_cell_data_func (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER],
649                                                  (GtkTreeCellDataFunc) set_column_attributes,
650                                                  GUINT_TO_POINTER ((guint) columns_cnt),
651                                                  NULL); // NULL -> no destroy notify for user data.
652         gtk_tree_view_column_set_resizable (ks.columns[columns_cnt], TRUE);
653         gtk_tree_view_append_column (GTK_TREE_VIEW (ks.treeview), ks.columns[columns_cnt]);
654     }
655 
656     // Default setting
657     gtk_tree_view_column_set_visible (ks.columns[COL_ELEMENT_VISIBILITY], FALSE);
658 
659     // Set treestore and model.
660 
661     /*
662         Order: icon img, icon img status, icon img modification time, icon path,
663                menu element, type, value, menu ID, execute, element visibility
664     */
665     ks.treestore = gtk_tree_store_new (NUMBER_OF_TS_ELEMENTS, GDK_TYPE_PIXBUF, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING,
666                                        G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
667                                        G_TYPE_STRING);
668 
669     gtk_tree_view_set_model (GTK_TREE_VIEW (ks.treeview), GTK_TREE_MODEL (ks.treestore));
670     ks.model = gtk_tree_view_get_model (GTK_TREE_VIEW (ks.treeview));
671     g_object_unref (ks.treestore);
672 
673     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
674     gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
675 
676     // Set scrolled window that contains the treeview.
677     scrolled_window = gtk_scrolled_window_new (NULL, NULL); // No manual horizontal or vertical adjustment == NULL.
678     gtk_container_add (GTK_CONTAINER (ks.main_box), scrolled_window);
679 
680     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
681     gtk_container_add (GTK_CONTAINER (scrolled_window), ks.treeview);
682     gtk_widget_set_vexpand (scrolled_window, TRUE);
683 
684     // Set drag and drop destination parameters.
685     ks.content_formats = gdk_content_formats_new (mime_types, G_N_ELEMENTS (mime_types));
686     gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (ks.treeview), ks.content_formats, GDK_ACTION_MOVE);
687 
688     // ### Create entry grid. ###
689 
690     ks.entry_grid = gtk_grid_new ();
691     gtk_container_add (GTK_CONTAINER (ks.main_box), ks.entry_grid);
692 
693     // Label text is set dynamically dependent on the type of the selected row.
694     ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY] = gtk_label_new (NULL);
695     gtk_widget_set_halign (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY], GTK_ALIGN_START);
696 
697     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY], 0, 0, 1, 1);
698 
699     ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY] = gtk_entry_new ();
700     ks.menu_element_or_value_entry_css_provider = gtk_css_provider_new ();
701     gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]),
702                                     GTK_STYLE_PROVIDER (ks.menu_element_or_value_entry_css_provider),
703                                     GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
704     gtk_widget_set_hexpand (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], TRUE);
705     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], 1, 0, 1, 1);
706 
707     ks.entry_labels[ICON_PATH_ENTRY] = gtk_label_new (" Icon: ");
708     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[ICON_PATH_ENTRY], 2, 0, 1, 1);
709 
710     ks.icon_chooser = gtk_button_new_from_icon_name ("document-open");
711     gtk_widget_set_tooltip_text (GTK_WIDGET (ks.icon_chooser), "Add/Change icon");
712     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.icon_chooser, 3, 0, 1, 1);
713 
714     ks.remove_icon = gtk_button_new_from_icon_name ("list-remove");
715     gtk_widget_set_tooltip_text (GTK_WIDGET (ks.remove_icon), "Remove icon");
716     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.remove_icon, 4, 0, 1, 1);
717 
718     ks.entry_fields[ICON_PATH_ENTRY] = gtk_entry_new ();
719     ks.icon_path_entry_css_provider = gtk_css_provider_new ();
720     gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[ICON_PATH_ENTRY]),
721                                     GTK_STYLE_PROVIDER (ks.icon_path_entry_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
722     gtk_widget_set_hexpand (ks.entry_fields[ICON_PATH_ENTRY], TRUE);
723     gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[ICON_PATH_ENTRY], 5, 0, 1, 1);
724 
725     for (entry_fields_cnt = MENU_ID_ENTRY; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; entry_fields_cnt++) {
726         ks.entry_labels[entry_fields_cnt] = gtk_label_new ((entry_fields_cnt == MENU_ID_ENTRY) ? " Menu ID: " : NULL);
727         gtk_widget_set_halign (ks.entry_labels[entry_fields_cnt], GTK_ALIGN_START);
728 
729         gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[entry_fields_cnt], 0, entry_fields_cnt - 1, 1, 1);
730 
731         ks.entry_fields[entry_fields_cnt] = gtk_entry_new ();
732         if (entry_fields_cnt == EXECUTE_ENTRY) {
733             ks.execute_entry_css_provider = gtk_css_provider_new ();
734             gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[entry_fields_cnt]),
735                                             GTK_STYLE_PROVIDER (ks.execute_entry_css_provider),
736                                                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
737         }
738         gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[entry_fields_cnt], 1, entry_fields_cnt - 1, 5, 1);
739     }
740 
741     // ### Statusbar ###
742 
743     ks.statusbar = gtk_statusbar_new ();
744     gtk_container_add (GTK_CONTAINER (main_grid), ks.statusbar);
745     gtk_widget_set_margin_top (ks.statusbar, 0);
746     gtk_widget_set_margin_bottom (ks.statusbar, 0);
747     gtk_widget_set_margin_start (ks.statusbar, 0);
748     gtk_widget_set_margin_end (ks.statusbar, 0);
749 
750     // ### CSS provider for context menu
751 
752     ks.cm_css_provider = gtk_css_provider_new ();
753 
754 
755     // --- Create signals for all buttons and relevant events. ---
756 
757 
758     ks.handler_id_row_selected = g_signal_connect (selection, "changed", G_CALLBACK (row_selected), NULL);
759     g_signal_connect (ks.treeview, "row-expanded", G_CALLBACK (set_status_of_expand_and_collapse_buttons_and_menu_items),
760                       NULL);
761     g_signal_connect (ks.treeview, "row-collapsed", G_CALLBACK (set_status_of_expand_and_collapse_buttons_and_menu_items),
762                       NULL);
763     g_signal_connect (ks.treeview, "button-press-event", G_CALLBACK (mouse_pressed), NULL);
764     g_signal_connect (ks.treeview, "button-release-event", G_CALLBACK (mouse_released), NULL);
765 
766     g_signal_connect (ks.treeview, "key-press-event", G_CALLBACK (key_pressed), NULL);
767 
768     g_signal_connect (ks.treeview, "drag-motion", G_CALLBACK (drag_motion_handler), NULL);
769     g_signal_connect (ks.treeview, "drag-leave", G_CALLBACK (drag_leave_handler), NULL);
770     g_signal_connect (ks.treeview, "drag-drop", G_CALLBACK (drag_drop_handler), NULL);
771 
772     g_signal_connect (ks.mb_file_menu_items[MB_NEW], "activate", G_CALLBACK (new_menu), NULL);
773     g_signal_connect (ks.mb_file_menu_items[MB_OPEN], "activate", G_CALLBACK (open_menu), NULL);
774     g_signal_connect_swapped (ks.mb_file_menu_items[MB_SAVE], "activate", G_CALLBACK (save_menu), NULL);
775     g_signal_connect (ks.mb_file_menu_items[MB_SAVE_AS], "activate", G_CALLBACK (save_menu_as), NULL);
776 
777     g_signal_connect (ks.mb_file_menu_items[MB_QUIT], "activate", G_CALLBACK (quit_program), NULL);
778 
779     for (mb_menu_items_cnt = TOP; mb_menu_items_cnt <= BOTTOM; mb_menu_items_cnt++) {
780         g_signal_connect_swapped (ks.mb_edit_menu_items[mb_menu_items_cnt], "activate",
781                                   G_CALLBACK (move_selection), GUINT_TO_POINTER ((guint) mb_menu_items_cnt));
782     }
783     g_signal_connect_swapped (ks.mb_edit_menu_items[MB_REMOVE], "activate", G_CALLBACK (remove_rows), "menu bar");
784     g_signal_connect (ks.mb_edit_menu_items[MB_REMOVE_ALL_CHILDREN], "activate", G_CALLBACK (remove_all_children), NULL);
785     g_signal_connect_swapped (ks.mb_edit_menu_items[MB_VISUALISE], "activate",
786                               G_CALLBACK (visualise_menus_items_and_separators), GUINT_TO_POINTER (FALSE));
787     g_signal_connect_swapped (ks.mb_edit_menu_items[MB_VISUALISE_RECURSIVELY], "activate",
788                               G_CALLBACK (visualise_menus_items_and_separators), GUINT_TO_POINTER (TRUE));
789 
790     g_signal_connect (mb_find, "activate", G_CALLBACK (show_or_hide_find_grid), NULL);
791 
792     g_signal_connect_swapped (ks.mb_expand_all_nodes, "activate", G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (TRUE));
793     g_signal_connect_swapped (ks.mb_collapse_all_nodes, "activate", G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (FALSE));
794     for (mb_menu_items_cnt = 0; mb_menu_items_cnt < NUMBER_OF_VIEW_AND_OPTIONS; mb_menu_items_cnt++) {
795         g_signal_connect_swapped (ks.mb_view_and_options[mb_menu_items_cnt], "activate",
796                                   G_CALLBACK (change_view_and_options), GUINT_TO_POINTER ((guint) mb_menu_items_cnt));
797     }
798 
799     g_signal_connect (mb_about, "activate", G_CALLBACK (about), NULL);
800 
801     g_signal_connect (ks.tb[TB_NEW], "clicked", G_CALLBACK (new_menu), NULL);
802     g_signal_connect (ks.tb[TB_OPEN], "clicked", G_CALLBACK (open_menu), NULL);
803 
804     g_signal_connect_swapped (ks.tb[TB_SAVE], "clicked", G_CALLBACK (save_menu), NULL);
805     g_signal_connect (ks.tb[TB_SAVE_AS], "clicked", G_CALLBACK (save_menu_as), NULL);
806     g_signal_connect_swapped (ks.tb[TB_MOVE_UP], "clicked", G_CALLBACK (move_selection), GUINT_TO_POINTER (UP));
807     g_signal_connect_swapped (ks.tb[TB_MOVE_DOWN], "clicked", G_CALLBACK (move_selection), GUINT_TO_POINTER (DOWN));
808     g_signal_connect_swapped (ks.tb[TB_REMOVE], "clicked", G_CALLBACK (remove_rows), "toolbar");
809     g_signal_connect (ks.tb[TB_FIND], "clicked", G_CALLBACK (show_or_hide_find_grid), NULL);
810     g_signal_connect_swapped (ks.tb[TB_EXPAND_ALL], "clicked", G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (TRUE));
811     g_signal_connect_swapped (ks.tb[TB_COLLAPSE_ALL], "clicked", G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (FALSE));
812     g_signal_connect (ks.tb[TB_QUIT], "clicked", G_CALLBACK (quit_program), NULL);
813 
814     for (buttons_cnt = 0; buttons_cnt < ACTION_OR_OPTION; buttons_cnt++) {
815         g_signal_connect_swapped (ks.bt_add[buttons_cnt], "clicked", G_CALLBACK (add_new), add_txts[buttons_cnt]);
816     }
817 
818     ks.handler_id_including_action_check_button = g_signal_connect_swapped (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON],
819                                                                             "clicked",
820                                                                             G_CALLBACK (one_of_the_change_values_buttons_pressed),
821                                                                             "incl. action");
822     g_signal_connect_swapped (change_values_buttons[CHANGE_VALUES_RESET], "clicked",
823                               G_CALLBACK (one_of_the_change_values_buttons_pressed), "reset");
824     g_signal_connect_swapped (change_values_buttons[CHANGE_VALUES_DONE], "clicked",
825                               G_CALLBACK (one_of_the_change_values_buttons_pressed), "done");
826 
827     // This "simulates" a click on another row.
828     g_signal_connect (change_values_buttons[CHANGE_VALUES_CANCEL], "clicked", G_CALLBACK (row_selected), NULL);
829     g_signal_connect_swapped (ks.new_action_option_widgets[ACTION_OPTION_DONE], "clicked",
830                               G_CALLBACK (action_option_insert), "by combo box");
831     g_signal_connect_swapped (ks.new_action_option_widgets[ACTION_OPTION_CANCEL], "clicked",
832                               G_CALLBACK (hide_action_option_grid), "cancel button");
833     ks.handler_id_show_or_hide_startupnotify_options = g_signal_connect (ks.options_fields[SN_OR_PROMPT], "toggled",
834                                                                          G_CALLBACK (show_or_hide_startupnotify_options), NULL);
835 
836     g_signal_connect (ks.find_entry_buttons[CLOSE], "clicked", G_CALLBACK (show_or_hide_find_grid), NULL);
837     for (buttons_cnt = BACK; buttons_cnt <= FORWARD; buttons_cnt++) {
838         g_signal_connect_swapped (ks.find_entry_buttons[buttons_cnt], "clicked",
839                                   G_CALLBACK (jump_to_previous_or_next_occurrence),
840                                   GUINT_TO_POINTER ((buttons_cnt == FORWARD)));
841     }
842     g_signal_connect (ks.find_entry, "activate", G_CALLBACK (run_search), NULL);
843     for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS - 1; columns_cnt++) {
844         ks.handler_id_find_in_columns[columns_cnt] = g_signal_connect_swapped (ks.find_in_columns[columns_cnt], "clicked",
845                                                                                G_CALLBACK (find_buttons_management), "specif");
846     }
847     g_signal_connect_swapped (ks.find_in_all_columns, "clicked", G_CALLBACK (find_buttons_management), "all");
848     g_signal_connect_swapped (ks.find_match_case, "clicked", G_CALLBACK (find_buttons_management), NULL);
849     g_signal_connect_swapped (ks.find_regular_expression, "clicked", G_CALLBACK (find_buttons_management), NULL);
850 
851     for (entry_fields_cnt = 0; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; entry_fields_cnt++) {
852         ks.handler_id_entry_fields[entry_fields_cnt] = g_signal_connect (ks.entry_fields[entry_fields_cnt], "activate",
853                                                                          G_CALLBACK (change_row), NULL);
854     }
855     g_signal_connect (ks.icon_chooser, "clicked", G_CALLBACK (icon_choosing_by_button_or_context_menu), NULL);
856     g_signal_connect (ks.remove_icon, "clicked", G_CALLBACK (remove_icons_from_menus_or_items), NULL);
857 
858     g_signal_connect (ks.options_fields[PROMPT], "activate", G_CALLBACK (single_field_entry), NULL);
859     g_signal_connect (ks.options_fields[COMMAND], "activate", G_CALLBACK (single_field_entry), NULL);
860     for (snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; snotify_opts_cnt++) {
861         g_signal_connect (ks.suboptions_fields[snotify_opts_cnt], "activate", G_CALLBACK (single_field_entry), NULL);
862     }
863 
864     // --- Default settings ---
865 
866     g_object_get (gtk_settings_get_default (), "gtk-font-name", &ks.font_desc, NULL);
867     ks.font_size = get_font_size (); // Get the default font size ...
868     g_object_get (gtk_settings_get_default (), "gtk-icon-theme-name", &ks.icon_theme_name, NULL); // ... and the icon theme name.
869     create_invalid_icon_imgs (); // Create broken/invalid path icon images suitable for that font size.
870     ks.search_term = g_string_new (NULL);
871     // Has to be placed here because the signal to deactivate all other column check boxes has to be triggered.
872     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_in_all_columns), TRUE); // Deactivate user defined column search.
873 
874     /*
875         The height of the message label is set to be identical to the one of the buttons, so the button grid doesn't
876         shrink if the buttons are missing. This can only be done after all widgets have already been added to the grid,
877         because only then the height of the button grid (with buttons shown) can be determined correctly.
878     */
879     gtk_widget_set_size_request (ks.bt_bar_label, -1, gtk_widget_get_allocated_height (ks.bt_add[0]));
880 
881     /*
882         Usually the height of the window with all widgets shown will exceed 550 px,
883         so using gtk_window_set_default_size (GTK_WINDOW (ks.window), 650, 550) at the beginning
884         won't work as desired for a height value set to 550.
885     */
886     gtk_window_resize (GTK_WINDOW (ks.window), 650, 550);
887     gtk_window_set_gravity (GTK_WINDOW (ks.window), GDK_GRAVITY_CENTER);
888     /*
889         By calling row_selected (), settings for menu- and toolbar are adjusted.
890         The following widgets are hidden by this function:
891         * ks_change_values_label
892         * ks.mandatory
893         * ks.separator
894         * ks.change_values_buttons_grid
895         * ks.new_action_option_widgets[INSIDE_MENU_LABEL]
896         * ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]
897         * ks.new_action_option_widgets[INCLUDING_ACTION_LABEL]
898         * ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]
899     */
900     row_selected ();
901     /*
902         ks.find_grid is not hidden by row_selected (). It would be hidden later if there is a menu file,
903         but since that might not be the case, for simplicity it is done here anyway.
904     */
905     gtk_widget_hide (ks.find_grid);
906 
907 
908     // --- Load settings and standard menu file, if existent. ---
909 
910 
911     settings_file_path = g_strconcat (g_getenv ("HOME"), "/.kickshawrc", NULL);
912     if (G_LIKELY (g_file_test (settings_file_path, G_FILE_TEST_EXISTS))) {
913         GKeyFile *settings_file = g_key_file_new ();
914         GError *settings_file_error = NULL;
915 
916         if (G_LIKELY (g_key_file_load_from_file (settings_file, settings_file_path,
917                       G_KEY_FILE_KEEP_COMMENTS, &settings_file_error))) {
918             gchar *comment = NULL; // Initialization avoids compiler warning.
919             // Versions up to 0.5.7 don't have a comment in the settings file.
920             if (G_LIKELY ((comment = g_key_file_get_comment (settings_file, NULL, NULL, NULL)))) {
921                 for (view_and_opts_cnt = 0; view_and_opts_cnt < NUMBER_OF_VIEW_AND_OPTIONS; view_and_opts_cnt++) {
922                     gtk_check_menu_item_set_active
923                         (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[view_and_opts_cnt]),
924                                               g_key_file_get_boolean (settings_file,
925                                               (view_and_opts_cnt < CREATE_BACKUP_BEFORE_OVERWRITING_MENU) ? "VIEW" : "OPTIONS",
926                                               gtk_menu_item_get_label ((GtkMenuItem *) ks.mb_view_and_options[view_and_opts_cnt]),
927                                               NULL));
928                 }
929                 // Cleanup
930                 g_free (comment);
931             }
932             else {
933                 GtkWidget *dialog;
934 
935                 create_dialog (&dialog, "Settings reset due to change in settings setup", "gtk-info",
936                                "Due to a change in the setup of the settings the settings file has been rewritten "
937                                "and the settings have been reset to defaults. Use \"View\" and \"Options\" "
938                                "in the menu bar to readjust the settings.",
939                                "Close", NULL, NULL, TRUE);
940 
941                 write_settings ();
942 
943                 gtk_dialog_run (GTK_DIALOG (dialog));
944                 gtk_widget_destroy (dialog);
945             }
946         }
947         else {
948             gchar *errmsg_txt = g_strdup_printf ("<b>Could not open settings file</b>\n<tt>%s</tt>\n<b>for reading!\n\n"
949                                                  "<span foreground='darkred'>%s</span></b>",
950                                                  settings_file_path, settings_file_error->message);
951 
952             show_errmsg (errmsg_txt);
953 
954             // Cleanup
955             g_error_free (settings_file_error);
956             g_free (errmsg_txt);
957         }
958 
959         // Cleanup
960         g_key_file_free (settings_file);
961 
962     }
963     else {
964         write_settings ();
965     }
966     // Cleanup
967     g_free (settings_file_path);
968 
969 
970     // --- Load standard menu file, if existent. ---
971 
972 
973     if (G_LIKELY (g_file_test (new_filename = g_strconcat (g_getenv ("HOME"), "/.config/openbox/menu.xml", NULL),
974                   G_FILE_TEST_EXISTS))) {
975         get_menu_elements_from_file (new_filename);
976     }
977     else {
978         // Cleanup
979         g_free (new_filename);
980     }
981 
982     /*
983         This is another "convenience call" of row_selected (), this time for setting the sensivity of
984         * mb_file_menu_items[MB_NEW]
985         * tb[TB_NEW]
986     */
987     row_selected ();
988 
989     gtk_widget_grab_focus (ks.treeview);
990 }
991 
992 /*
993 
994     Creates a label for an "Add new" button, adds it to a grid with additional margin, which is added to the button.
995 
996 */
997 
add_button_content(GtkWidget * button,gchar * label_text)998 static void add_button_content (GtkWidget *button, gchar *label_text)
999 {
1000     GtkWidget *grid = gtk_grid_new ();
1001     GtkWidget *label = (*label_text) ? gtk_label_new_with_mnemonic (label_text) : ks.bt_add_action_option_label;
1002 
1003     g_object_set (grid, "margin", 2, NULL);
1004     gtk_container_add (GTK_CONTAINER (grid), label);
1005     gtk_container_add (GTK_CONTAINER (button), grid);
1006 }
1007 
1008 /*
1009 
1010     Sets attributes like foreground and background colour, visibility of cell renderers and
1011     editability of cells according to certain conditions. Also highlights search results.
1012 
1013 */
1014 
set_column_attributes(G_GNUC_UNUSED GtkTreeViewColumn * cell_column,GtkCellRenderer * txt_renderer,GtkTreeModel * cell_model,GtkTreeIter * cell_iter,gpointer column_number_pointer)1015 static void set_column_attributes (G_GNUC_UNUSED GtkTreeViewColumn *cell_column,
1016                                                  GtkCellRenderer   *txt_renderer,
1017                                                  GtkTreeModel      *cell_model,
1018                                                  GtkTreeIter       *cell_iter,
1019                                                  gpointer           column_number_pointer)
1020 {
1021     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
1022 
1023     gboolean row_is_selected = gtk_tree_selection_iter_is_selected (selection, cell_iter);
1024 
1025     guint column_number = GPOINTER_TO_UINT (column_number_pointer);
1026     GtkTreePath *cell_path = gtk_tree_model_get_path (cell_model, cell_iter);
1027     guint cell_data_icon_img_status;
1028     gchar *cell_data[NUMBER_OF_TXT_FIELDS];
1029 
1030     // Defaults
1031     gboolean visualise_txt_renderer = TRUE;
1032     gboolean visualise_bool_renderer = FALSE;
1033 
1034     // Defaults
1035     gchar *background = NULL;
1036     gboolean background_set = FALSE;
1037     GString *txt_with_markup = NULL;
1038 
1039     gboolean show_icons = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[SHOW_ICONS]));
1040     gboolean set_off_separators =
1041         gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[SET_OFF_SEPARATORS]));
1042     gboolean keep_highlighting =
1043         gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM
1044                                         (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL]));
1045 
1046     gtk_tree_model_get (cell_model, cell_iter,
1047                         TS_ICON_IMG_STATUS, &cell_data_icon_img_status,
1048                         TS_ICON_PATH, &cell_data[ICON_PATH_TXT],
1049                         TS_MENU_ELEMENT, &cell_data[MENU_ELEMENT_TXT],
1050                         TS_TYPE, &cell_data[TYPE_TXT],
1051                         TS_VALUE, &cell_data[VALUE_TXT],
1052                         TS_MENU_ID, &cell_data[MENU_ID_TXT],
1053                         TS_EXECUTE, &cell_data[EXECUTE_TXT],
1054                         TS_ELEMENT_VISIBILITY, &cell_data[ELEMENT_VISIBILITY_TXT],
1055                         -1);
1056 
1057     gboolean icon_to_be_shown = show_icons && cell_data[ICON_PATH_TXT];
1058 
1059     /*
1060         Set the cell renderer type of the "Value" column to toggle if it is a "prompt" option of a non-Execute action or
1061         an "enabled" option of a "startupnotify" option block.
1062     */
1063 
1064     if (column_number == COL_VALUE && STREQ (cell_data[TYPE_TXT], "option") &&
1065         streq_any (cell_data[MENU_ELEMENT_TXT], "prompt", "enabled", NULL)) {
1066         GtkTreeIter parent;
1067         gchar *cell_data_menu_element_parent_txt;
1068 
1069         gtk_tree_model_iter_parent (cell_model, &parent, cell_iter);
1070         gtk_tree_model_get (ks.model, &parent, TS_MENU_ELEMENT, &cell_data_menu_element_parent_txt, -1);
1071         if (!STREQ (cell_data_menu_element_parent_txt, "Execute")) {
1072             visualise_txt_renderer = FALSE;
1073             visualise_bool_renderer = TRUE;
1074             g_object_set (ks.renderers[BOOL_RENDERER], "active", STREQ (cell_data[VALUE_TXT], "yes"), NULL);
1075         }
1076         // Cleanup
1077         g_free (cell_data_menu_element_parent_txt);
1078     }
1079 
1080     g_object_set (txt_renderer, "visible", visualise_txt_renderer, NULL);
1081     g_object_set (ks.renderers[BOOL_RENDERER], "visible", visualise_bool_renderer, NULL);
1082     g_object_set (ks.renderers[PIXBUF_RENDERER], "visible", icon_to_be_shown, NULL);
1083     g_object_set (ks.renderers[EXCL_TXT_RENDERER], "visible",
1084                   G_UNLIKELY (icon_to_be_shown && cell_data_icon_img_status), NULL);
1085 
1086     /*
1087         If the icon is one of the two built-in types that indicate an invalid path or icon image,
1088         set two red exclamation marks behind it to clearly distinguish this icon from icons of valid image files.
1089     */
1090     if (G_UNLIKELY (column_number == COL_MENU_ELEMENT && cell_data_icon_img_status)) {
1091         g_object_set (ks.renderers[EXCL_TXT_RENDERER], "markup", "<span foreground='darkred'><b>!!</b></span>", NULL);
1092     }
1093 
1094     // Emphasis that a menu, pipe menu or item has no label (=invisible).
1095     if (G_UNLIKELY (column_number == COL_MENU_ELEMENT &&
1096                     streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", "item", NULL) && !cell_data[MENU_ELEMENT_TXT])) {
1097         g_object_set (txt_renderer, "text", "(No label)", NULL);
1098     }
1099 
1100     if (keep_highlighting) {
1101         guint8 visibility_of_parent = NONE_OR_VISIBLE_ANCESTOR; // Default
1102         if (G_UNLIKELY ((cell_data[ELEMENT_VISIBILITY_TXT] && !STREQ (cell_data[ELEMENT_VISIBILITY_TXT], "visible")) ||
1103                         (visibility_of_parent = check_if_invisible_ancestor_exists (cell_model, cell_path)))) {
1104             background = ((cell_data[ELEMENT_VISIBILITY_TXT] &&
1105                           g_str_has_suffix (cell_data[ELEMENT_VISIBILITY_TXT], "orphaned menu")) ||
1106                           visibility_of_parent == INVISIBLE_ORPHANED_ANCESTOR) ? "#364074" : "#656772";
1107             background_set = TRUE;
1108         }
1109     }
1110 
1111     // If a search is going on, highlight all matches.
1112     if (*ks.search_term->str && column_number < COL_ELEMENT_VISIBILITY && !gtk_widget_get_visible (ks.action_option_grid) &&
1113         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[column_number])) &&
1114         check_for_match (cell_iter, column_number)) {
1115         // cell_data starts with ICON_PATH, but this is not part of the treeview.
1116         gchar *original_cell_txt = cell_data[column_number + 1]; // Only introduced for better readability.
1117         gchar *search_term_str_escaped = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_regular_expression))) ?
1118                                          NULL : g_regex_escape_string (ks.search_term->str, -1);
1119         GRegex *regex = g_regex_new ((search_term_str_escaped) ? search_term_str_escaped : ks.search_term->str,
1120                                      (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_match_case))) ?
1121                                      0 : G_REGEX_CASELESS, 0, NULL);
1122         GMatchInfo *match_info;
1123 
1124         gchar *counter_char = original_cell_txt; // counter_char will move through all the characters of original_cell_txt.
1125         gint counter;
1126 
1127         gunichar unichar;
1128         gchar utf8_char[6]; // Six bytes is the buffer size needed later by g_unichar_to_utf8 ().
1129         gint utf8_length;
1130         gchar *utf8_escaped;
1131 
1132         enum { KS_START_POS, KS_END_POS };
1133         GArray *positions[2];
1134         positions[KS_START_POS] = g_array_new (FALSE, FALSE, sizeof (gint));
1135         positions[KS_END_POS] = g_array_new (FALSE, FALSE, sizeof (gint));
1136         gint start_position, end_position;
1137 
1138         txt_with_markup = g_string_new ((!background_set) ? "" : "<span foreground='white'>");
1139 
1140         g_regex_match (regex, original_cell_txt, 0, &match_info);
1141 
1142         while (g_match_info_matches (match_info)) {
1143             g_match_info_fetch_pos (match_info, 0, &start_position, &end_position);
1144             g_array_append_val (positions[KS_START_POS], start_position);
1145             g_array_append_val (positions[KS_END_POS], end_position);
1146             g_match_info_next (match_info, NULL);
1147         }
1148 
1149         do {
1150             unichar = g_utf8_get_char (counter_char);
1151             counter = counter_char - original_cell_txt; // pointer arithmetic
1152 
1153             if (counter == g_array_index (positions[KS_END_POS], gint, 0)) {
1154                 txt_with_markup = g_string_append (txt_with_markup, "</span>");
1155                 // It's simpler to always access the first element instead of looping through the whole array.
1156                 g_array_remove_index (positions[KS_END_POS], 0);
1157             }
1158             /*
1159                 No "else if" is used here, since if there is a search for a single character going on and
1160                 such a character appears double as 'm' in "command", between both m's a span tag has to be
1161                 closed and opened at the same position.
1162             */
1163             if (counter == g_array_index (positions[KS_START_POS], gint, 0)) {
1164                 txt_with_markup = g_string_append (txt_with_markup,
1165                                                   (row_is_selected) ?
1166                                                   "<span background='black' foreground='white'>" :
1167                                                   "<span background='yellow' foreground='black'>");
1168                 // See the comment for the similar instruction above.
1169                 g_array_remove_index (positions[KS_START_POS], 0);
1170             }
1171 
1172             utf8_length = g_unichar_to_utf8 (unichar, utf8_char);
1173             /*
1174                 Instead of using a switch statement to check whether the current character needs to be escaped,
1175                 for simplicity the character is sent to the escape function regardless of whether there will be
1176                 any escaping done by it or not.
1177             */
1178             utf8_escaped = g_markup_escape_text (utf8_char, utf8_length);
1179 
1180             txt_with_markup = g_string_append (txt_with_markup, utf8_escaped);
1181 
1182             // Cleanup
1183             g_free (utf8_escaped);
1184 
1185             counter_char = g_utf8_find_next_char (counter_char, NULL);
1186         } while (*counter_char != '\0');
1187 
1188         /*
1189             There is a '</span>' to set at the end; because the end position is one position after the string size
1190             this couldn't be done inside the preceding loop.
1191         */
1192         if (positions[KS_END_POS]->len) {
1193             g_string_append (txt_with_markup, "</span>");
1194         }
1195 
1196         if (background_set) {
1197             txt_with_markup = g_string_append (txt_with_markup, "</span>");
1198         }
1199 
1200         g_object_set (txt_renderer, "markup", txt_with_markup->str, NULL);
1201 
1202         // Cleanup
1203         g_free (search_term_str_escaped);
1204         g_regex_unref (regex);
1205         g_match_info_free (match_info);
1206         g_array_free (positions[KS_START_POS], TRUE);
1207         g_array_free (positions[KS_END_POS], TRUE);
1208     }
1209 
1210     // Set foreground and background font and cell colours. Also set editability of cells.
1211     g_object_set (txt_renderer, "weight", (set_off_separators && STREQ (cell_data[TYPE_TXT], "separator")) ? 1000 : 400,
1212 
1213                   "family", (STREQ (cell_data[TYPE_TXT], "separator") && set_off_separators) ?
1214                             "monospace, courier new, courier" : "sans, sans-serif, arial, helvetica",
1215 
1216                   "foreground", "white", "foreground-set", (row_is_selected || (background_set && !txt_with_markup)),
1217                   "background", background, "background-set", background_set,
1218 
1219                   "editable",
1220                   (((column_number == COL_MENU_ELEMENT &&
1221                   (STREQ (cell_data[TYPE_TXT], "separator") ||
1222                   (cell_data[MENU_ELEMENT_TXT] &&
1223                   streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", "item", NULL)))) ||
1224                   (column_number == COL_VALUE && STREQ (cell_data[TYPE_TXT], "option")) ||
1225                   (column_number == COL_MENU_ID && streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", NULL)) ||
1226                   (column_number == COL_EXECUTE && STREQ (cell_data[TYPE_TXT], "pipe menu")))),
1227 
1228                   NULL);
1229 
1230     for (guint8 renderer_cnt = EXCL_TXT_RENDERER; renderer_cnt < NUMBER_OF_RENDERERS; renderer_cnt++) {
1231         g_object_set (ks.renderers[renderer_cnt], "cell-background", background, "cell-background-set", background_set, NULL);
1232     }
1233 
1234     if (txt_with_markup && gtk_cell_renderer_get_visible (ks.renderers[BOOL_RENDERER])) {
1235         g_object_set (ks.renderers[BOOL_RENDERER], "cell-background", "yellow", "cell-background-set", TRUE, NULL);
1236     }
1237 
1238     // Cleanup
1239     if (txt_with_markup) {
1240         g_string_free (txt_with_markup, TRUE);
1241     }
1242     gtk_tree_path_free (cell_path);
1243     free_elements_of_static_string_array (cell_data, NUMBER_OF_TXT_FIELDS, FALSE);
1244 }
1245 
1246 /*
1247 
1248     Changes certain aspects of the tree view or misc. settings.
1249 
1250 */
1251 
change_view_and_options(gpointer activated_menu_item_pointer)1252 static void change_view_and_options (gpointer activated_menu_item_pointer)
1253 {
1254     guint8 activated_menu_item = GPOINTER_TO_UINT (activated_menu_item_pointer);
1255 
1256     gboolean menu_item_activated =
1257         (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[activated_menu_item])));
1258 
1259     if (activated_menu_item <= SHOW_ELEMENT_VISIBILITY_COL_ACTVTD) {
1260         /*
1261             SHOW_MENU_ID_COL (0)                   + 3 = COL_MENU_ID (3)
1262             SHOW_EXECUTE_COL (1)                   + 3 = COL_EXECUTE (4)
1263             SHOW_ELEMENT_VISIBILITY_COL_ACTVTD (2) + 3 = COL_ELEMENT_VISIBILITY (5)
1264         */
1265         guint8 column_position = activated_menu_item + 3;
1266 
1267         gtk_tree_view_column_set_visible (ks.columns[column_position], menu_item_activated);
1268 
1269         if (column_position == COL_ELEMENT_VISIBILITY) {
1270             if (!menu_item_activated) {
1271                 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM
1272                                                 (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL]), TRUE);
1273             }
1274             gtk_widget_set_sensitive (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], menu_item_activated);
1275             gtk_widget_set_sensitive (ks.mb_view_and_options[SHOW_ELEMENT_VISIBILITY_COL_DONT_KEEP_HIGHL], menu_item_activated);
1276         }
1277     }
1278     else if (activated_menu_item == DRAW_ROWS_IN_ALT_COLOURS) {
1279         g_object_set (ks.treeview, "rules-hint", menu_item_activated, NULL);
1280     }
1281     else if (activated_menu_item >= NO_GRID_LINES && activated_menu_item <= BOTH) {
1282         guint8 grid_settings_cnt, grid_lines_type_cnt;
1283 
1284         for (grid_settings_cnt = NO_GRID_LINES, grid_lines_type_cnt = GTK_TREE_VIEW_GRID_LINES_NONE;
1285              grid_settings_cnt <= BOTH;
1286              grid_settings_cnt++, grid_lines_type_cnt++) {
1287             if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[grid_settings_cnt]))) {
1288                 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (ks.treeview), grid_lines_type_cnt);
1289                 break;
1290             }
1291         }
1292     }
1293     else if (activated_menu_item == SHOW_TREE_LINES) {
1294         gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (ks.treeview), menu_item_activated);
1295     }
1296     else if (activated_menu_item == SORT_EXECUTE_AND_STARTUPN_OPTIONS) {
1297         if ((ks.autosort_options = menu_item_activated)) {
1298             gtk_tree_model_foreach (ks.model, (GtkTreeModelForeachFunc) sort_loop_after_sorting_activation, NULL);
1299         }
1300         /*
1301             A change of the activation status of autosorting requires
1302             (de)activation of the movement arrows of the menu- and toolbar.
1303         */
1304         row_selected ();
1305     }
1306     else {
1307         gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview)); // If icon visibility has been switched on.
1308     }
1309 
1310     write_settings ();
1311 }
1312 
1313 /*
1314 
1315     Sets the sensivity of the expand/collapse menu items and toolbar buttons
1316     according to the status of the nodes of the treeview.
1317 
1318 */
1319 
set_status_of_expand_and_collapse_buttons_and_menu_items(void)1320 void set_status_of_expand_and_collapse_buttons_and_menu_items (void)
1321 {
1322     gboolean expansion_statuses_of_nodes[NUMBER_OF_EXPANSION_STATUSES] = { FALSE };
1323 
1324     gtk_tree_model_foreach (ks.model, (GtkTreeModelForeachFunc) check_expansion_statuses_of_nodes, expansion_statuses_of_nodes);
1325 
1326     gtk_widget_set_sensitive (ks.mb_collapse_all_nodes, expansion_statuses_of_nodes[AT_LEAST_ONE_IS_EXPANDED]);
1327     gtk_widget_set_sensitive ((GtkWidget *) ks.tb[TB_COLLAPSE_ALL],
1328                               expansion_statuses_of_nodes[AT_LEAST_ONE_IS_EXPANDED]);
1329     gtk_widget_set_sensitive (ks.mb_expand_all_nodes, expansion_statuses_of_nodes[AT_LEAST_ONE_IS_COLLAPSED]);
1330     gtk_widget_set_sensitive ((GtkWidget *) ks.tb[TB_EXPAND_ALL],
1331                               expansion_statuses_of_nodes[AT_LEAST_ONE_IS_COLLAPSED]);
1332 }
1333 
1334 /*
1335 
1336     Dialog window that shows the program name and version, a short description, the website, author and license.
1337 
1338 */
1339 
about(void)1340 static void about (void)
1341 {
1342     gtk_show_about_dialog (GTK_WINDOW (ks.window),
1343                            /*
1344                                There is no icon that comes with Kickshaw; "logo-icon-name" instead of "logo" is
1345                                used to avoid a broken default icon that would be shown in the about dialog.
1346                            */
1347                            "logo-icon-name", NULL,
1348                            "program-name", "Kickshaw",
1349                            "version", KICKSHAW_VERSION,
1350                            "comments", "A menu editor for Openbox",
1351                            "website", "https://savannah.nongnu.org/projects/obladi/",
1352                            "website_label", "Project page at GNU Savannah",
1353                            "copyright", "© 2010–2018 Marcus Schätzle",
1354                            "license-type", GTK_LICENSE_GPL_2_0,
1355 
1356                            "authors", (gchar *[]) { "Marcus Schätzle", NULL },
1357                            NULL);
1358 }
1359 
1360 /*
1361 
1362     Asks whether to continue if there are unsaved changes.
1363 
1364 */
1365 
continue_despite_unsaved_changes(void)1366 gboolean continue_despite_unsaved_changes (void)
1367 {
1368     GtkWidget *dialog;
1369 
1370     gint result;
1371 
1372     #define CONTINUE_DESPITE_UNSAVED_CHANGES 2
1373 
1374     create_dialog (&dialog, "Menu has unsaved changes", "dialog-warning",
1375                    "<b>This menu has unsaved changes. Continue anyway?</b>", "_Cancel", "_Yes", NULL, TRUE);
1376     gtk_window_resize (GTK_WINDOW (dialog), 570, 1);
1377 
1378     result = gtk_dialog_run (GTK_DIALOG (dialog));
1379 
1380     gtk_widget_destroy (dialog);
1381 
1382     return (result == CONTINUE_DESPITE_UNSAVED_CHANGES);
1383 }
1384 
1385 /*
1386 
1387     Clears the data that is held throughout the running time of the program.
1388 
1389 */
1390 
clear_global_data(void)1391 void clear_global_data (void)
1392 {
1393     GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
1394 
1395     FREE_AND_REASSIGN (ks.filename, NULL);
1396     g_slist_free_full (ks.menu_ids, (GDestroyNotify) g_free);
1397     ks.menu_ids = NULL;
1398     if (ks.rows_with_icons) {
1399         stop_timeout ();
1400     }
1401     if (gtk_widget_get_visible (ks.find_grid)) {
1402         show_or_hide_find_grid ();
1403     }
1404     g_signal_handler_block (selection, ks.handler_id_row_selected);
1405     gtk_tree_store_clear (ks.treestore);
1406     g_signal_handler_unblock (selection, ks.handler_id_row_selected);
1407     ks.statusbar_msg_shown = FALSE;
1408 }
1409 
1410 /*
1411 
1412     Clears treestore and global variables, also resets window title and menu-/tool-/button bar widgets accordingly.
1413 
1414 */
1415 
new_menu(void)1416 static void new_menu (void)
1417 {
1418     if (ks.change_done && !continue_despite_unsaved_changes ()) {
1419         return;
1420     }
1421 
1422     gtk_window_set_title (GTK_WINDOW (ks.window), "Kickshaw");
1423 
1424     clear_global_data ();
1425     ks.change_done = FALSE;
1426 
1427     gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));
1428     row_selected (); // Switches the settings for menu- and toolbar to that of an empty menu.
1429 }
1430 
1431 /*
1432 
1433     Quits program, if there are unsaved changes a confirmation dialog window is shown.
1434 
1435 */
1436 
quit_program(void)1437 static void quit_program (void)
1438 {
1439     if (ks.change_done && !continue_despite_unsaved_changes ()) {
1440         return;
1441     }
1442 
1443     g_application_quit (G_APPLICATION (ks.app));
1444 }
1445 
1446 /*
1447 
1448     Activates "Save" menubar item/toolbar button (provided that there is a filename) if a change has been done.
1449     Also sets a global veriable so a program-wide check for a change is possible.
1450     If a search is still active, the list of results is recreated.
1451     A list of rows with icons is (re)created, too.
1452 
1453 */
1454 
activate_change_done(void)1455 void activate_change_done (void)
1456 {
1457     if (ks.filename) {
1458         gtk_widget_set_sensitive (ks.mb_file_menu_items[MB_SAVE], TRUE);
1459         gtk_widget_set_sensitive ((GtkWidget *) ks.tb[TB_SAVE], TRUE);
1460     }
1461 
1462     if (*ks.search_term->str) {
1463         create_list_of_rows_with_found_occurrences ();
1464     }
1465 
1466     if (ks.rows_with_icons) {
1467         stop_timeout ();
1468     }
1469     create_list_of_icon_occurrences ();
1470 
1471     ks.change_done = TRUE;
1472 }
1473 
1474 /*
1475 
1476     Writes all view and option settings into a file.
1477 
1478 */
1479 
write_settings(void)1480 static void write_settings (void)
1481 {
1482     FILE *settings_file;
1483     gchar *settings_file_path = g_strconcat (g_getenv ("HOME"), "/.kickshawrc", NULL);
1484 
1485     if (G_UNLIKELY (!(settings_file = fopen (settings_file_path, "w")))) {
1486         gchar *errmsg_txt = g_strdup_printf ("<b>Could not open settings file</b>\n<tt>%s</tt>\n<b>for writing!</b>",
1487                                              settings_file_path);
1488 
1489         show_errmsg (errmsg_txt);
1490 
1491         // Cleanup
1492         g_free (errmsg_txt);
1493         g_free (settings_file_path);
1494 
1495         return;
1496     }
1497 
1498     // Cleanup
1499     g_free (settings_file_path);
1500 
1501     fputs ("# Generated by Kickshaw " KICKSHAW_VERSION "\n\n[VIEW]\n\n", settings_file);
1502 
1503     for (guint8 view_and_opts_cnt = 0; view_and_opts_cnt < NUMBER_OF_VIEW_AND_OPTIONS; view_and_opts_cnt++) {
1504         if (view_and_opts_cnt == CREATE_BACKUP_BEFORE_OVERWRITING_MENU) {
1505             fputs ("\n[OPTIONS]\n\n", settings_file);
1506         }
1507         fprintf (settings_file, "%s=%s\n", gtk_menu_item_get_label ((GtkMenuItem *) ks.mb_view_and_options[view_and_opts_cnt]),
1508                  (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[view_and_opts_cnt]))) ?
1509                  "true" : "false");
1510     }
1511 
1512     fclose (settings_file);
1513 }
1514 
1515 /*
1516 
1517     Replaces the filename and window title.
1518 
1519 */
1520 
set_filename_and_window_title(gchar * new_filename)1521 void set_filename_and_window_title (gchar *new_filename)
1522 {
1523     gchar *basename;
1524     gchar *window_title;
1525 
1526     FREE_AND_REASSIGN (ks.filename, new_filename);
1527     basename = g_path_get_basename (ks.filename);
1528     window_title = g_strconcat ("Kickshaw - ", basename, NULL);
1529     gtk_window_set_title (GTK_WINDOW (ks.window), window_title);
1530 
1531     // Cleanup
1532     g_free (basename);
1533     g_free (window_title);
1534 }
1535 
1536 /*
1537 
1538     Shows an error message dialog.
1539 
1540 */
1541 
show_errmsg(gchar * errmsg_raw_txt)1542 void show_errmsg (gchar *errmsg_raw_txt)
1543 {
1544     GtkWidget *dialog;
1545     gchar *label_txt = g_strdup_printf ((!strstr (errmsg_raw_txt, "<b>")) ? "<b>%s</b>" : "%s", errmsg_raw_txt);
1546 
1547     create_dialog (&dialog, "Error", "dialog-error", label_txt, "_Close", NULL, NULL, TRUE);
1548 
1549     // Cleanup
1550     g_free (label_txt);
1551 
1552     gtk_dialog_run (GTK_DIALOG (dialog));
1553     gtk_widget_destroy (dialog);
1554 }
1555 
1556 /*
1557 
1558     Shows a message in the statusbar at the botton for information.
1559 
1560 */
1561 
show_msg_in_statusbar(gchar * message)1562 void show_msg_in_statusbar (gchar *message)
1563 {
1564     gtk_statusbar_remove_all (GTK_STATUSBAR (ks.statusbar), 1);
1565     gtk_statusbar_push (GTK_STATUSBAR (ks.statusbar), 1, message); // Only one context (indicated by 1) with one message.
1566 
1567     ks.statusbar_msg_shown = TRUE;
1568 }
1569