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