/*
   Kickshaw - A Menu Editor for Openbox

   Copyright (c) 2010–2025        Marcus Schätzle

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with Kickshaw. If not, see http://www.gnu.org/licenses/.
*/

#define _GNU_SOURCE
#include <gtk/gtk.h>

#include <stdlib.h> // Sometimes this might have to be included explicitly.
#include <string.h> // Sometimes this might have to be included explicitly.
#include <sys/stat.h>
#include <getopt.h>
#include <langinfo.h>
#include <locale.h>
#include <time.h>

#include "declarations_definitions_and_enumerations.h"
#include ".gresource.h"

enum { HINTS_TAB_STOPS, HINTS_BOX_BG_COLOR };

ks_data ks = {
    .grid_submenu_item_txts = { N_("No Grid Lines"), N_("Horizontal"), N_("Vertical"), N_("Both") }, // Used by change_view_and_options_GtkMenu ()
    // Used by change_view_and_options_GMenu () and write_settings ()
    .grid_states = { "no_grid_lines", "horizontal", "vertical", "both" }, 
    .edit_simple_action_txts = { "undo", "redo", "movetop", "moveup", "movedown", "movebottom", 
                                 "remove", "removeallch", "visualize", "visualizerec" }, // Used by row_selected ()

    .options_label_txts = { " Prompt: ", " Command: ", " Startupnotify " }, 

    .actions = { "Execute", "Exit", "Reconfigure", "Restart", "SessionLogout" }, 

    .execute_options = { "prompt", "command", "startupnotify" },
    .execute_displayed_txts = { "Prompt", "Command", "Startupnotify" }, 

    .startupnotify_options = { "enabled", "name", "wmclass", "icon" }, 
    .startupnotify_displayed_txts = { "Enabled", "Name", "WM_CLASS", "Icon" },

    .column_header_txts = { N_("Menu Element"), 
	                        // Translation note: This is for the type of a variable
	                        N_("Type"), 
							// Translation note: This is for the value of a variable
							N_("Value"), 
							N_("Menu ID"), 
							// Translation note: Proper word, do not translate this.
							N_("Execute"), 
							N_("Element Visibility") }, 

    .enable_list = {{ "STRING", GTK_TARGET_SAME_WIDGET, 0 }}, 
};

static void about (void);
static void add_button_content (      GtkWidget *button, 
                                const gchar     *label_text);
static void add_popup_menu_items (      GMenu      *menu, 
                                        GMenuItem **menu_items, 
                                  const gchar     **menu_item_txts, 
                                  const gchar     **simple_action_txts, 
                                  const gchar     **menu_item_accelerators, 
                                  const guint8     *section_starts, 
                                  const guint8     *section_ends, 
                                  const guint8      number_of_menu_items, 
                                  const guint8      number_of_section_elements);
static void bug_report (void);
static void change_view_and_options_GtkMenu (              GtkMenuItem *menu_item, 
                                             G_GNUC_UNUSED gpointer     user_data);
static void create_and_add_simple_actions (      GSimpleAction **simple_actions, 
                                           const gchar         **simple_action_txts, 
                                           const gchar         **menu_item_accelerators, 
                                           const guint8          number_of_menu_items);
static void expand_or_collapse_all (gpointer expand_pointer);
static void general_initialization (GString *argument);
static gboolean key_pressed (              GtkWidget   *treeview, 
                                           GdkEventKey *event, 
                             G_GNUC_UNUSED gpointer     user_data);
static gboolean mouse_pressed (              GtkTreeView    *treeview, 
                                             GdkEventButton *event, 
                               G_GNUC_UNUSED gpointer        user_data);
static gboolean mouse_released (              GtkTreeView    *treeview, 
                                G_GNUC_UNUSED GdkEventButton *event, 
                                G_GNUC_UNUSED gpointer        user_data);
static void new_menu (void);
static gboolean quit_program (void);
static void restart_app (void);
static void set_column_attributes (G_GNUC_UNUSED GtkTreeViewColumn *cell_column, 
                                                 GtkCellRenderer   *txt_renderer,
                                                 GtkTreeModel      *cell_model, 
                                                 GtkTreeIter       *cell_iter, 
                                                 gpointer           column_number_pointer);
static void show_hints_window (void);
static guint8 show_restart_dialog (const gboolean dialog_for_headerbar);
#if GTK_CHECK_VERSION (3,20,0)
static void show_shortcuts_window (void);
#endif
static void write_settings (const gboolean change_states_to_defaults);

gint main (gint   argc, 
           gchar *argv[])
{
    // ### ### Check for and initialize localization, if applicable. ### ###  
    

    setlocale (LC_ALL, "");
    bindtextdomain ("kickshaw", "/usr/share/locale/");
    textdomain ("kickshaw");

    const gchar * const *applicable_locale_names = g_get_language_names ();

    // Default
    ks.locale = "en_US";

    gchar *provided_locales[] = { "af", "am", "ar", "as", "az", "be", "bg", "bn", "bo", "br", 
                                  "ca", "cs", "cy", "da", "de_CH", "de_DE", "el", "en_GB", "eo", "es", 
                                  "et", "eu", "fa", "fi", "fil", "fo", "fr", "fur", "fy", "ga", 
                                  "gd", "gl", "gu", "he", "hi", "hr", "ht", "hu", "hy", "id", 
                                  "is", "it", "ja", "ka", "kk", "km", "kn", "ko", "ku_TR", "ky", 
                                  "lb", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mr", 
                                  "ms", "mt", "my", "nb", "ne", "nl", "nn", "or", "pa_IN", "pl", 
                                  "ps", "pt_BR", "pt_PT", "ro", "ru", "si", "sk", "sl", "sq", "sr", 
                                  "sv", "sw", "ta", "te", "tg", "th", "tk", "tn", "tr", "ug", 
                                  "uk", "ur", "uz", "vi", "xh", "zh_CN", "zh_HK", "zh_TW", "zu" };
    guint8 locales_cnt;

    gboolean exact_locale_match = FALSE;

    // Looking for an exact match of the current locale inside provided_locales
    FOREACH_IN_ARRAY(provided_locales, locales_cnt) {
        if (STREQ (applicable_locale_names[0], provided_locales[locales_cnt])) {
            exact_locale_match = TRUE;
            ks.locale = provided_locales[locales_cnt];

            break;            
        }
    }

    if (!exact_locale_match && strstr (applicable_locale_names[0], "_")) {
        g_auto(GStrv) locale_parts = g_strsplit (applicable_locale_names[0], "_", -1);
        
        // If the locale is German, but not specifically for Germany or Switzerland
        if (STREQ (locale_parts[0], "de")) {
            if (STREQ (applicable_locale_names[0], "de_LI")) {
                g_setenv ("LANGUAGE", "de_CH", TRUE);
                ks.locale = "de_CH";
            }
            else {
                g_setenv ("LANGUAGE", "de_DE", TRUE);
                ks.locale = "de_DE";
            }
        }
        // If the locale is Portuguese, but not specifically for Brazil or Portugal
        else if (STREQ (locale_parts[0], "pt") && !streq_any (applicable_locale_names[0], "pt_BR", "pt_PT", NULL)) {
            g_setenv ("LANGUAGE", "pt_PT", TRUE);
            ks.locale = "pt_PT";
        }
        /* Looking for a language match of the current locale inside provided_locales,
        e.g. if current locale is "fil_PH", "fil" will be found,
        if current_locale is "pt" without any region suffix, then "pt_BT" is chosen
        because it is alphabetically the first one inside provided_locales */
        else {
            FOREACH_IN_ARRAY(provided_locales, locales_cnt) {
                if (STREQ (locale_parts[0], provided_locales[locales_cnt])) {
                    ks.locale = provided_locales[locales_cnt];

                    break;
                }         
            }
        }
    }


    // ### ### Startup checks ### ###


    if (G_UNLIKELY (argc > 3)) {
        g_printerr (_("Kickshaw accepts only two arguments at a time.\n"));

        exit (EXIT_FAILURE);        
    }

    GString *full_path = NULL;

    if (G_UNLIKELY (argv[1])) {

        // ### Display text according to the given argument. ###

        g_autoptr(GResource) resources = resources_get_resource ();

        g_resources_register (resources);

        // ### Check if path for a filename does exist. ###

        if (strstr (argv[1], "/")) {
            if (G_UNLIKELY (argc > 2)) {
                g_printerr (_("A file path has to be the only argument.\n"));

                exit (EXIT_FAILURE);
            }

            if (G_UNLIKELY (g_file_test (argv[1], G_FILE_TEST_IS_DIR))) {
                g_printerr (_("Can't open a directory.\n"));

                exit (EXIT_FAILURE);
            }

            g_autofree gchar *dirname;
            g_autoptr(GFile) file;

            // If the entered path contains a tilde at the beginning, it is not necessary to replace it with the home directory + username.
            // This has already been done by the system.
            full_path = g_string_new (argv[1]);

            if (g_regex_match_simple ("\\./", argv[1], 0, G_REGEX_MATCH_ANCHORED)) {
                g_autofree gchar *current_dir = g_get_current_dir ();
                g_autofree gchar *basename = g_path_get_basename (argv[1]);

                g_string_printf (full_path, "%s/%s", current_dir, basename); // Destroys the old content of full_path.
            }

            dirname = g_path_get_dirname (full_path->str);
            file = g_file_new_for_path (dirname);

            if (G_UNLIKELY (!g_file_query_exists (file, NULL))) {
                g_printerr (_("The directory '%s' does not exist.\n"), dirname);

                exit (EXIT_FAILURE);
            }

            if (g_file_test (full_path->str, G_FILE_TEST_EXISTS)) {
                g_auto(GStrv) paths = g_strsplit (g_getenv ("PATH"), ":", -1);
                guint paths_idx = 0;
                struct stat sb;

                while (paths[paths_idx]) {
                    g_autofree gchar *path_with_iconv = g_strconcat (paths[paths_idx], "/iconv", NULL);

                    if (stat (path_with_iconv, &sb) == 0) {
                        // As of March 2024, there are iconv varieties around (e.g. on FreeBSD) that do not support the '-o' option to redirect output. 
                        // That is why for hiding the output of iconv, std_out_iconv is defined below and passed to g_spawn_command_line_sync().
                        // It is likely the best option to stick to this approach, because it hardly takes more effort to use the parameter 
                        // instead of the option, so there is no need to check every now and then the grade of support for the '-o' option.
                        g_autofree gchar *iconv_str = g_strconcat ("iconv -f utf8 ", full_path->str, " -t utf8", NULL);
                        g_autofree gchar *std_out_iconv = NULL, *std_err_iconv = NULL;

                        g_spawn_command_line_sync (iconv_str, &std_out_iconv, &std_err_iconv, NULL, NULL);

                        if (G_UNLIKELY (*std_err_iconv)) {
                            g_printerr (_("Can't open file; it has to be a text file that is encoded in UTF-8.\n"));

                            exit (EXIT_FAILURE);
                        }

                        break;
                    }

                    ++paths_idx;
                }
            }
        }
        else {
            gint option;
            struct option long_options[] = {
                { "help",    no_argument, NULL, 'h' },
                { "version", no_argument, NULL, 'v' },
                { NULL,      0,           NULL, 0 }
            };
            gint option_index = 0;
            guint8 options = 0;
            // Defaults
            gboolean invalid_option = FALSE;
            gboolean show_help = FALSE;
            gboolean show_version_info = FALSE;

            while ((option = getopt_long (argc, argv, "hv", long_options, &option_index)) != -1) {  
                ++options;
                switch (option) {
                    case 'h':
                        show_help = TRUE;
                        break;
                    case 'v':
                        show_version_info = TRUE;
                        break;
                    case '?':
                        invalid_option = TRUE;
                        break;
                }
            }

            if (G_UNLIKELY (invalid_option)) {
                g_autofree gchar *resource_path = g_strdup_printf ("/org/kickshaw/txts/info/%s/info_arguments_%s", ks.locale, ks.locale);
                g_autofree gchar *resource_arguments = get_txt_from_resources (resources, resource_path);

                g_printerr ("%s\n", resource_arguments);

                if (!(optind < argc)) {
                    exit (EXIT_FAILURE);
                }
            }

            if (G_UNLIKELY (optind < argc)) {
                g_autofree gchar *invalid_arg_txt = g_strdup_printf ("%s%s: %s -- %s: ", 
                                                                     (invalid_option) ? "\n" : "", argv[0], 
                                                                     _("neither file path nor option"), 
                                                                     ngettext ("invalid argument", "invalid arguments", !(argc == 3 && options == 0)));

                g_printerr ("%s", invalid_arg_txt);
                while (optind < argc) {
                    if (argv[optind][0] != '-') { 
                        g_printerr ((optind == argc - 1) ? "'%s' " : "'%s', ", argv[optind]);
                    }
                    ++optind;
                }
                g_printerr (_("\nOptions have to be preceded by either '-' or '--' for their long counterparts.\n"));

                exit (EXIT_FAILURE);
            }

            if (show_help) {
                g_autofree gchar *resource_path_hints = g_strdup_printf ("/org/kickshaw/txts/info/%s/info_hints_%s", ks.locale, ks.locale);
                g_autofree gchar *resource_path_arguments = g_strdup_printf ("/org/kickshaw/txts/info/%s/info_arguments_%s", ks.locale, ks.locale);
                g_autofree gchar *resource_hints = get_txt_from_resources (resources, resource_path_hints);
                g_autofree gchar *resource_arguments = get_txt_from_resources (resources, resource_path_arguments);

                g_print ("%s\n\n%s\n", resource_hints, resource_arguments);
            }

            if (show_version_info) {
                g_autofree gchar *resource_path = g_strdup_printf ("/org/kickshaw/txts/info/%s/info_version_%s", ks.locale, ks.locale);
                g_autofree gchar *resource = get_txt_from_resources (resources, resource_path);
                g_autofree gchar *info_version_str = g_strdup_printf (resource, KICKSHAW_VERSION, RELEASED_IN);
                    
                g_print ("%s%s\n", (show_help) ? "\n----\n\n" : "", info_version_str);
            }

            exit (EXIT_SUCCESS);
        }
    }


    // ### ### Check if a windowing system is running. ### ###


    if (G_UNLIKELY (!g_getenv ("DISPLAY"))) {
        g_printerr (_("No windowing system currently running, program aborted.\n"));

        exit (EXIT_FAILURE);
    }


    // ### ### Initialize GTK, create the GUI and set up everything else. ### ###


#if GLIB_CHECK_VERSION(2,74,0)
    ks.app = gtk_application_new ("savannah.nongnu.org.kickshaw", G_APPLICATION_DEFAULT_FLAGS);
#else
    ks.app = gtk_application_new ("savannah.nongnu.org.kickshaw", G_APPLICATION_FLAGS_NONE);
#endif
    gint status;

    g_signal_connect_swapped (ks.app, "activate", G_CALLBACK (general_initialization), full_path);

    status = g_application_run (G_APPLICATION (ks.app), 0, NULL);

    g_object_unref (ks.app);

    return status;
}

/* 

    Creates GUI and signals, also loads settings and standard menu file, if they exist.

*/

static void general_initialization (GString *argument)
{
    GList *windows = gtk_application_get_windows (ks.app);

    if (G_UNLIKELY (windows)) {
        gtk_window_present (GTK_WINDOW (windows->data));

        return;
    }


    GtkWidget *toolbar;

    enum { SHOW_ELEMENT_VIS_COL_SUBMENU, SHOW_GRID_SUBMENU, NUMBER_OF_SUBMENUS };

    const gchar *menu_labels[] = { N_("File"), 
	                               // Translation note: Like the German "Bearbeiten". For a menu item.
	                               N_("Edit"), 
								   N_("Search"), 
								   // Translation note: Like the German "Ansicht". For a menu item.
								   N_("View"), 
								   N_("Options"), 
								   N_("Help") };

    const gchar *file_menu_item_txts[] = { N_("New"),
	                                       // Translation note: The verb "Open"
	                                       N_("Open"), 
	                                       // Translation note: For a menu item in a "File" menu
										   N_("Save"), 
										   // Translation note: For a menu item in a "File" menu
										   N_("Save As…"), 
										   N_("Quit") };
    const gchar *file_menu_item_accelerators[] = { N_("<Ctl>N"), N_("<Ctl>O"), N_("<Ctl>S"), N_("<Shift><Ctl>S"), N_("<Ctl>Q") };

    const gchar *edit_menu_item_txts[] = { N_("Undo"),
	                                       N_("Redo"),
										   // Translation note: This is for a context menu for moving a node in a tree view
										   N_("Move to Top"),
										   // Translation note: This is for a context menu for moving a node in a tree view
										   N_("Move Up"),
										   // Translation note: This is for a context menu for moving a node in a tree view
										   N_("Move Down"),
										   // Translation note: This is for a context menu for moving a node in a tree view
										   N_("Move to Bottom"),   
                                           N_("Remove"),
										   N_("Remove All Children"),
										   // Translation note: "Visualize" in the sense of making something visible
										   N_("Visualize"),
										   // Translation note: "Visualize" in the sense of making something visible
										   N_("Visualize Recursively") };
    const gchar *edit_menu_item_accelerators[] = { N_("<Ctl>Z"), N_("<Ctl>Y"), N_("<Ctl>T"), N_("<Ctl>minus"), N_("<Ctl>plus"), N_("<Ctl>B"), 
                                                   N_("<Ctl>R"), N_("<Shift><Ctl>R"), N_("<Ctl>E"), N_("<Shift><Ctl>E")};

    /* The search menu items texts are written out since there are only two of them; 
       there is no loop to create the corresponding menu items. */
    const gchar *search_menu_item_accelerators[] = { N_("<Ctl>F"), N_("<Ctl>H") };

    const gchar *view_menu_item_txts[] = { N_("Expand All Nodes"),
	                                       N_("Collapse All Nodes"), 
										   N_("Show Menu ID Column"), 
										   // Translation note: Do not translate "Execute"
                                           N_("Show Execute Column"), 
										   N_("Show Element Visibility Column"), 
										   N_("Show Icons"), 
                                           N_("Highlight Separators"), 
										   N_("Show Tree Lines"), 
										   N_("Show Grid") };
    const gchar *view_menu_item_accelerators[] = { N_("<Shift><Ctl>X"), N_("<Shift><Ctl>C"), "", "", "", "", "", "", "" };

    const gchar *options_menu_item_txts[] = { N_("Create Backup Before Overwriting Menu File"), 
	                                          N_("Use Tabs for Indentations in Saved Menu File"), 
                                              N_("Keep Root Menu Separate in Saved Menu File"), 
											  // Translation note: Do not translate "Execute" and "Startupnotify"
											  N_("Sort Execute/Startupnotify Options"), 
											  // Translation note: Do not translate "Execute". Translate "Opt." as "Option" here
                                              N_("Always Notify About Execute Opt. Conversions"), 
											  // Translation note: "Cient-side decorations" in the context of window managers
											  N_("Use Client-Side Decorations"), 
                                              N_("Show Menu Button Instead of Menubar") };
    const gchar *options_menu_item_accelerators[] = { "", "", "", "", "", "", "" };                                 

    const gchar *help_menu_item_txts[] = { N_("Hints"), N_("Shortcuts"), N_("Report Issue"), N_("About") };
    const gchar *help_menu_item_accelerators[] = { N_("F1"), N_("<Ctl>L"), N_("<Ctl>I"), N_("<Shift><Ctl>A") };

    const gchar *button_IDs[] = { "document-new", "document-open", "document-save", "document-save-as", 
                                  "edit-undo", "edit-redo", "edit-find", "zoom-in", "zoom-out" };
    const gchar *tb_tooltips[] = { N_("New Menu"), 
                                   // Translation note: the verb "Open"
                                   N_("Open Menu"), 
                                   // Translation note: For a tooltip
                                   N_("Save Menu"), 
                                   // Translation note: For a tooltip
                                   N_("Save Menu As…"), 
                                   N_("Undo"), 
                                   N_("Redo"), 
                                   N_("Find"), 
                                   N_("Expand All Nodes"), 
                                   N_("Collapse All Nodes") };

    GtkWidget *main_box;

    // Label text of the last button is set dynamically depending on the type of the selected row.
    const gchar *bt_add_txts[] = { N_("_Menu"), N_("_Pipe Menu"), N_("_Item"), N_("Separato_r") };
    const gchar *add_txts[] = { "menu", "pipe menu", "item", "separator" };
    GtkWidget *separator;

    ks.label_field_txt = g_string_new (NULL);
    ks.menu_ID_field_txt = g_string_new (NULL);
    ks.execute_field_txt = g_string_new (NULL);
    ks.prompt_field_txt = g_string_new (NULL);
    ks.command_field_txt = g_string_new (NULL);
    ks.mandatory_label_txt = g_string_new (NULL);
    ks.mandatory_empty_string_txt = g_string_new (NULL);
    ks.double_menu_id_label_txt = g_string_new (NULL);

    GtkCssProvider *button_provider;

    GtkWidget *menu_id_box;
    g_autoptr(GtkCssProvider) enter_values_label_css_provider;
    enum { ENTER_VALUES_CANCEL, ENTER_VALUES_RESET, ENTER_VALUES_DONE, NUMBER_OF_ENTER_VALUES_BUTTONS };
    GtkWidget *enter_values_buttons[NUMBER_OF_ENTER_VALUES_BUTTONS];
    const gchar *enter_values_button_txts[NUMBER_OF_ENTER_VALUES_BUTTONS] = { NC_("Cancel|No File Dialogue", "_Cancel"), N_("_Reset"), N_("_Done") };

    const gchar *find_entry_buttons_imgs[] = { "window-close", "go-previous", "go-next" };
    const gchar *find_entry_buttons_tooltips[] = { // Translation note: The verb "Close"
                                                   N_("Close"), 
                                                   N_("Previous"),
                                                   N_("Next") };
    enum { ENTRY, COLUMNS_SELECTION, SPECIAL_OPTIONS, NUMBER_OF_FIND_SUBGRIDS };
    GtkWidget *find_subgrids[NUMBER_OF_FIND_SUBGRIDS];
    const gchar *special_options_txts[] = { // This is for a search functionality. A translation of "case sensitive" or "distinguish between uppercase and lowercase letters" would also work.
	                                        N_("Match Case"),
	                                        N_("Regular Expression (PCRE)"),
											N_("Whole Word") };

    GtkWidget *scrolled_window;
    GtkTreeSelection *selection;

    GtkEntryCompletion *entry_completion;

    g_autofree gchar *settings_file_path;
    g_autoptr(GFile) settings_file;
    g_autoptr(GKeyFile) settings_key_file = NULL;
    enum { PARSABLE, PARSING_ERROR, NO_OR_UNPARSABLE_COMMENT_IN_SETTINGS_FILE, 
           CHANGE_IN_SETTINGS_FILE_SETUP, UNABLE_TO_OPEN_SETTINGS_FILE, NO_SETTINGS_FILE };
    gboolean settings_file_status;

    const gchar tmp_dir_template[] = "/kickshaw.XXXXXX";
    gchar *tmp_path;

    guint8 buttons_cnt, columns_cnt, entry_fields_cnt, menu_items_cnt, menus_cnt, snotify_opts_cnt;


    // ### ### Creating the GUI. ### ###


    // ### Create a new window. ###


    ks.window = gtk_application_window_new (GTK_APPLICATION (ks.app));
    // Enforce a widget layout according to the script direction. There has been
    // a test case on one system where the layout direction was not set properly. 
    if (streq_any (ks.locale, "ar", "fa", "he", "ps", "ug", "ur", NULL)) {
    	gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL);
    	ks.options_label_txts[PROMPT] = "&#8207; Prompt: ";
    	ks.options_label_txts[COMMAND] = "&#8207; Command: ";
    }
    else {
    	gtk_widget_set_default_direction (GTK_TEXT_DIR_LTR);
    }
    // To capture if an entry field has lost its focus.
    gtk_widget_add_events (ks.window, GDK_FOCUS_CHANGE_MASK);
    determine_theme_type ();


    /* ### Check the settings file (if it exists) if the window shall ###
       ### - contain a header bar or not                              ###
       ### - show a menu button or a menubar.                         ### */


    ks.settings.show_menu_button = TRUE; // Default

    ks.home_dir = g_get_home_dir ();
    settings_file_path = g_build_filename (ks.home_dir, ".kickshawrc", NULL);
    settings_file = g_file_new_for_path (settings_file_path);

    if (G_LIKELY (g_file_query_exists (settings_file, NULL))) {
        settings_key_file = g_key_file_new ();
        g_autoptr(GError) settings_file_error = NULL;

        if (G_LIKELY (g_key_file_load_from_file (settings_key_file, settings_file_path, 
                                                 G_KEY_FILE_KEEP_COMMENTS, &settings_file_error))) {
            const gchar *settings_file_reset_txt = _("The program will now try to reset the settings file to "
                                                     "its defaults. If no further error message regarding this "
                                                     "comment is shown, the writing was successful.");
            g_autofree gchar *comment = g_key_file_get_comment (settings_key_file, NULL, NULL, &settings_file_error);

            if (G_UNLIKELY (settings_file_error)) {
                g_autofree gchar *err_msg = g_strdup_printf (_("<b>Unable to look up the comment inside the settings file</b>\n"
                                                               "<tt>%s</tt>\n<b>!\n\n"
                                                               "<span foreground='%s'>%s</span></b>\n\n"
                                                               "%s"), 
                                                             settings_file_path, ks.red_hue, 
                                                             settings_file_error->message, settings_file_reset_txt);

                show_errmsg (err_msg);

                settings_file_status = PARSING_ERROR;
            }
            // Versions up to 0.5.7 don't have a comment in the settings file.
            else if (G_UNLIKELY (comment[0] == '\n' || !(strstr (comment, "Generated by Kickshaw")))) { 
                g_autofree gchar *err_msg = g_strdup_printf (_("<b>Unable to find a comment with the version information "
                                                               "inside the settings file</b>\n"
                                                               "<tt>%s</tt>\n\n"
                                                               "%s"), 
                                                             settings_file_path, settings_file_reset_txt);

                show_errmsg (err_msg);

                settings_file_status = NO_OR_UNPARSABLE_COMMENT_IN_SETTINGS_FILE;
            }
            else {
                gchar *version_number = NULL;

                settings_file_status = PARSABLE;

                version_number = extract_substring_via_regex (comment, "([0-9][0-9.]*)$");

#if __GLIBC__
                if (G_LIKELY (strverscmp (version_number, "1.0.47") >= 0)) { 
#else
                if (G_LIKELY (strverscmp_nonglibc (version_number, "1.0.47") >= 0)) { 
#endif
                    for (guint8 keys_cnt = 0; keys_cnt < 2; ++keys_cnt) {
                        const gchar *key = (keys_cnt) ? "sf-showmenubutton" : "sf-useheaderbar";
                        const gboolean key_value = g_key_file_get_boolean (settings_key_file, "OPTIONS", key, &settings_file_error);

                        set_settings ((keys_cnt) ? M_SHOW_MENU_BUTTON : M_USE_HEADER_BAR, key_value, -1);

                        if (G_UNLIKELY (settings_file_error)) {
                            g_autofree gchar *dialog_txt = g_strdup_printf (_("<b>Unable to parse the key</b> <tt>%s</tt>.\n"
                                                                              "<b><span foreground='%s'>Error: %s</span></b>\n\n"
                                                                              "The program will now try to reset the settings file to "
                                                                              "its default state. If no further error message regarding this "
                                                                              "key is shown, the writing was successful."), 
                                                                            key, ks.red_hue, settings_file_error->message);

                            show_errmsg (dialog_txt);

                            settings_file_status = PARSING_ERROR;

                            // Reset to default value.
                            set_settings ((keys_cnt) ? M_SHOW_MENU_BUTTON : M_USE_HEADER_BAR, TRUE, -1);

                            break;
                        }
                    }
                }
                else {
                    settings_file_status = CHANGE_IN_SETTINGS_FILE_SETUP;
                }
            }
        }
        else {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Unable to open the settings file</b>\n<tt>%s</tt>\n<b>for reading!\n\n"
                                                           "<span foreground='%s'>%s</span></b>\n\n"
                                                           "The program will now try to reset the settings file to "
                                                           "its defaults. If no further error message is shown, "
                                                           "the writing was successful."), 
                                                         settings_file_path, ks.red_hue, settings_file_error->message);

            show_errmsg (err_msg);

            settings_file_status = UNABLE_TO_OPEN_SETTINGS_FILE;
        }
    }
    else {
        settings_file_status = NO_SETTINGS_FILE;
    }


    // ### Create Header Bar (if client side decorations are activated). ###


    if (ks.settings.use_header_bar) {
        GdkScreen *screen = gdk_screen_get_default ();
        g_autoptr(GtkCssProvider) global_provider = gtk_css_provider_new ();
        GtkWidget *title_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);

        gtk_css_provider_load_from_data (global_provider, 
                                         "headerbar { min-height:0px;"
                                         "            padding-top:3px;"
                                         "            padding-bottom:3px;"
                                         "            padding-left:3px;"
                                         "            padding-right:7px; }"
                                         "headerbar * { margin-top:0px;"
                                         "              margin-bottom:0px; }"                    
                                         "headerbar label { padding:0px; }"
                                         "headerbar button.titlebutton { margin:-3px; }",
                                         -1, NULL);
        gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (global_provider), 
                                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

        ks.header_bar = gtk_header_bar_new ();
        gtk_window_set_titlebar (GTK_WINDOW (ks.window), ks.header_bar);

        gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (ks.header_bar), TRUE);

        gtk_widget_set_hexpand (title_box, TRUE);
        gtk_widget_set_size_request (title_box, 100, -1); // When the window is minimized, leave some space for dragging it.
        ks.basename_label = gtk_label_new (NULL);
        g_object_set (ks.basename_label, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
        ks.subtitle_label = gtk_label_new (NULL);
        g_object_set (ks.subtitle_label, "ellipsize", PANGO_ELLIPSIZE_END, NULL);

        gtk_style_context_add_class (gtk_widget_get_style_context (ks.basename_label), "title");
        g_autoptr(GtkCssProvider) title_provider = gtk_css_provider_new ();
        gtk_css_provider_load_from_data (title_provider, ".title { font-size:13px; }", -1, NULL);
        gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (title_provider), 
                                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

        gtk_style_context_add_class (gtk_widget_get_style_context (ks.subtitle_label), "subtitle");
        g_autoptr(GtkCssProvider) subtitle_provider = gtk_css_provider_new ();
        gtk_css_provider_load_from_data (subtitle_provider, ".subtitle { font-size:11px; }", -1, NULL);
        gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (subtitle_provider), 
                                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

        gtk_container_add (GTK_CONTAINER (title_box), ks.basename_label);
        gtk_container_add (GTK_CONTAINER (title_box), ks.subtitle_label);

        gtk_header_bar_set_custom_title (GTK_HEADER_BAR (ks.header_bar), title_box);
    }


    // ### Main Box ###


    main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add (GTK_CONTAINER (ks.window), main_box);


    // ### Create toolbar. ###


    toolbar = gtk_grid_new ();


    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_TB_BUTTONS; ++buttons_cnt) {
        switch (buttons_cnt) {
            case TB_BUTTON_NEW:
            case TB_BUTTON_UNDO:
            case TB_BUTTON_FIND:
                if (!ks.settings.use_header_bar && !(!ks.settings.show_menu_button && buttons_cnt == TB_BUTTON_NEW)) {
                    separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
                    g_object_set (separator, "margin-left", 7, "margin-right", 7, NULL);
                    gtk_container_add (GTK_CONTAINER (toolbar), separator);
                }
        }

        ks.tb_buttons[buttons_cnt] = gtk_button_new_from_icon_name (button_IDs[buttons_cnt], GTK_ICON_SIZE_LARGE_TOOLBAR);
        if (ks.settings.use_header_bar) {
            switch (buttons_cnt) {
                case TB_BUTTON_NEW:
                case TB_BUTTON_UNDO:
                case TB_BUTTON_FIND:
                    if (!(!ks.settings.show_menu_button && buttons_cnt == TB_BUTTON_NEW)) {
                        gtk_widget_set_margin_start (ks.tb_buttons[buttons_cnt], 5);
                    }
            }
        }
        gtk_button_set_relief (GTK_BUTTON (ks.tb_buttons[buttons_cnt]), 
                               (!ks.settings.use_header_bar) ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL);
        gtk_widget_set_tooltip_text (ks.tb_buttons[buttons_cnt], _(tb_tooltips[buttons_cnt]));
        gtk_container_add (GTK_CONTAINER (toolbar), ks.tb_buttons[buttons_cnt]);
    }

    if (!ks.settings.use_header_bar) {
        separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
        g_object_set (separator, "margin-left", 7, "margin-right", 7, NULL);
        gtk_container_add (GTK_CONTAINER (toolbar), separator);

        ks.tb_button_quit = gtk_button_new_from_icon_name ("application-exit", GTK_ICON_SIZE_LARGE_TOOLBAR);
        gtk_button_set_relief (GTK_BUTTON (ks.tb_button_quit), GTK_RELIEF_NONE);
        gtk_widget_set_tooltip_text (ks.tb_button_quit, _("Quit Application"));
        gtk_container_add (GTK_CONTAINER (toolbar), ks.tb_button_quit);
    }


    // ### Create popup menu or menubar ###


    if (ks.settings.show_menu_button) {
        GSimpleAction *search_actions[NUMBER_OF_SEARCH_MENU_ITEMS];
        GSimpleAction *help_actions[NUMBER_OF_HELP_MENU_ITEMS];
        GSimpleAction *view_action_grid;

        const gchar *file_simple_action_txts[] = { "new", "open", "save", "saveas", "quit" };
        const gchar *search_simple_action_txts[] = { "find", "findandreplace" };
        // No actions texts for "Show Element Visibility column" and "Show grid"
        const gchar *view_simple_action_txts[] = { "expand", "collapse", "sf-showmenuidcol", "sf-showexecutecol", 
                                                   "",  "sf-showicons", "sf-highlightsep", "sf-showtreelines", "" };
        const gchar *options_simple_action_txts[] = { "sf-createbackup", "sf-usetabs", "sf-separaterootmenu", 
                                                      "sf-sortoptions", "sf-notifyaboutconversions", 
                                                      "sf-useheaderbar", "sf-showmenubutton" };
        const gchar *help_simple_action_txts[] = { "hints", "shortcuts", "reportissue", "about" };

        const guint8 file_section_starts[] = { M_NEW, M_QUIT };
        const guint8 file_section_ends[] = { M_SAVE_AS, M_QUIT };

        const guint8 edit_section_starts[] = { M_UNDO, M_MOVE_TOP, M_REMOVE, M_VISUALIZE };
        const guint8 edit_section_ends[] = { M_REDO, M_MOVE_BOTTOM, M_REMOVE_ALL_CHILDREN, M_VISUALIZE_RECURSIVELY };

        const guint8 options_section_starts[] = { M_CREATE_BACKUP_BEFORE_OVERWRITING_MENU, M_SORT_EXECUTE_AND_STARTUPN_OPTIONS, 
                                                  M_USE_HEADER_BAR };
        const guint8 options_section_ends[] = { M_KEEP_ROOT_MENU_SEPARATE, M_NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS, M_SHOW_MENU_BUTTON };

        const guint8 help_section_starts[] = { M_HINTS, M_ABOUT };
        const guint8 help_section_ends[] = { M_REPORT_ISSUE, M_ABOUT };

        GMenu *MBut_menus[NUMBER_OF_MENUS];
        GMenu *MBut_submenus[NUMBER_OF_SUBMENUS];

        GMenuItem *MBut_file_menu_items[NUMBER_OF_FILE_MENU_ITEMS];
        GMenuItem *MBut_edit_menu_items[NUMBER_OF_EDIT_MENU_ITEMS];
        GMenuItem *MBut_search_menu_items[NUMBER_OF_SEARCH_MENU_ITEMS];
        GMenuItem *MBut_view_menu_items[NUMBER_OF_VIEW_MENU_ITEMS];
        GMenuItem *MBut_view_menu_visibility_items[NUMBER_OF_VIEW_MENU_VISIBILITY_ITEMS];
        GMenuItem *MBut_view_menu_grid_items[NUMBER_OF_VIEW_MENU_GRID_ITEMS];
        GMenuItem *MBut_help_menu_items[NUMBER_OF_HELP_MENU_ITEMS];

        g_autoptr(GMenu) element_visibility_section, show_grid_section;
        GMenu *view_section = NULL; // Initialization avoids compiler warning

        GtkWidget *tb_menu_button;
        g_autoptr(GList) menu_button_children;

        g_autoptr(GVariantType) type_string = g_variant_type_new ("s");

        create_and_add_simple_actions (ks.file_actions, file_simple_action_txts, 
                                       file_menu_item_accelerators, NUMBER_OF_FILE_MENU_ITEMS);
        create_and_add_simple_actions (ks.edit_actions, ks.edit_simple_action_txts, 
                                       edit_menu_item_accelerators, NUMBER_OF_EDIT_MENU_ITEMS);
        create_and_add_simple_actions (search_actions, search_simple_action_txts, 
                                       search_menu_item_accelerators, NUMBER_OF_SEARCH_MENU_ITEMS);
        create_and_add_simple_actions (ks.view_actions, view_simple_action_txts, 
                                       view_menu_item_accelerators, NUMBER_OF_VIEW_MENU_ITEMS);
        create_and_add_simple_actions (ks.options_actions, options_simple_action_txts, 
                                       options_menu_item_accelerators, NUMBER_OF_OPTIONS_MENU_ITEMS);
        create_and_add_simple_actions (help_actions, help_simple_action_txts, 
                                       help_menu_item_accelerators, NUMBER_OF_HELP_MENU_ITEMS);

        ks.popup_menu = g_menu_new ();

        for (menus_cnt = 0; menus_cnt < NUMBER_OF_MENUS; ++menus_cnt) {
            MBut_menus[menus_cnt] = g_menu_new ();
            ks.MBut_menu_items[menus_cnt] = g_menu_item_new_submenu (_(menu_labels[menus_cnt]), G_MENU_MODEL (MBut_menus[menus_cnt]));
            g_menu_append_item (ks.popup_menu, ks.MBut_menu_items[menus_cnt]);
        }

        // File

        add_popup_menu_items (MBut_menus[FILE_MENU], MBut_file_menu_items, file_menu_item_txts, 
                              file_simple_action_txts, file_menu_item_accelerators, 
                              file_section_starts, file_section_ends, 
                              NUMBER_OF_FILE_MENU_ITEMS, sizeof (file_section_starts) / sizeof (guint8));

        // Edit

        add_popup_menu_items (MBut_menus[EDIT_MENU], MBut_edit_menu_items, edit_menu_item_txts, 
                              ks.edit_simple_action_txts, edit_menu_item_accelerators, 
                              edit_section_starts, edit_section_ends, 
                              NUMBER_OF_EDIT_MENU_ITEMS, sizeof (edit_section_starts) / sizeof (guint8));

        // Search

        MBut_search_menu_items[M_FIND] = g_menu_item_new (_("Find"), NULL);
        g_menu_item_set_attribute (MBut_search_menu_items[M_FIND], "action", "s", "app.find");
        g_menu_item_set_attribute (MBut_search_menu_items[M_FIND], "accel", "s", _(search_menu_item_accelerators[M_FIND]));
        gtk_application_set_accels_for_action (ks.app, "app.find", (const gchar*[]) { _(search_menu_item_accelerators[M_FIND]), NULL });
        g_menu_append_item (MBut_menus[SEARCH_MENU], MBut_search_menu_items[M_FIND]);
        MBut_search_menu_items[M_FIND_AND_REPLACE] = g_menu_item_new (_("Find and Replace"), NULL);
        g_menu_item_set_attribute (MBut_search_menu_items[M_FIND_AND_REPLACE], "action", "s", "app.findandreplace");
        g_menu_item_set_attribute (MBut_search_menu_items[M_FIND_AND_REPLACE], "accel", "s",
                                   _(search_menu_item_accelerators[M_FIND_AND_REPLACE]));
        gtk_application_set_accels_for_action (ks.app, "app.findandreplace",
                                   (const gchar*[]) { _(search_menu_item_accelerators[M_FIND_AND_REPLACE]), NULL });
        g_menu_append_item (MBut_menus[SEARCH_MENU], MBut_search_menu_items[M_FIND_AND_REPLACE]);

        // View

        for (guint8 menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_VIEW_MENU_ITEMS; ++menu_items_cnt) {    
            switch (menu_items_cnt) {
                case M_EXPAND_ALL_NODES:
                case M_SHOW_MENU_ID_COL:
                case M_SHOW_ICONS:
                case M_HIGHLIGHT_SEPARATORS:
                case M_SHOW_TREE_LINES:
                    view_section = g_menu_new ();   
            }

            if (menu_items_cnt != M_SHOW_ELEMENT_VIS_COL && menu_items_cnt != M_SHOW_GRID) {
                MBut_view_menu_items[menu_items_cnt] = g_menu_item_new (_(view_menu_item_txts[menu_items_cnt]), NULL);

                g_autofree gchar *action_str = g_strconcat ("app.", view_simple_action_txts[menu_items_cnt], NULL);

                g_menu_item_set_attribute (MBut_view_menu_items[menu_items_cnt], "action", "s", action_str);

                if (menu_items_cnt <= M_COLLAPSE_ALL_NODES) {            
                    g_menu_item_set_attribute (MBut_view_menu_items[menu_items_cnt], "accel", "s", 
                                               _(view_menu_item_accelerators[menu_items_cnt]));
                }
            }
            else {
                guint8 menu_idx = (menu_items_cnt == M_SHOW_GRID); // SHOW_ELEMENT_VIS_COL_SUBMENU = 0, SHOW_GRID_SUBMENU = 1
                MBut_submenus[menu_idx] = g_menu_new ();
                MBut_view_menu_items[menu_items_cnt] = g_menu_item_new_submenu (_(view_menu_item_txts[menu_items_cnt]), 
                                                                                G_MENU_MODEL (MBut_submenus[menu_idx]));
            }

            g_menu_append_item (view_section, MBut_view_menu_items[menu_items_cnt]);

            switch (menu_items_cnt) {
                case M_COLLAPSE_ALL_NODES:
                case M_SHOW_ELEMENT_VIS_COL:
                case M_SHOW_ICONS:
                case M_HIGHLIGHT_SEPARATORS:
                case M_SHOW_GRID:
                    g_menu_append_section (MBut_menus[VIEW_MENU], NULL, G_MENU_MODEL (view_section));

                    // Cleanup
                    g_object_unref (view_section);
            }
        }

        // View submenu items: Show Element Visibility column

        ks.view_action_visibility = g_simple_action_new_stateful ("sf-elmviscol", NULL, g_variant_new_boolean (FALSE));
        g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_action_visibility));
        ks.view_action_highlighting = g_simple_action_new_stateful ("sf-highlightingstate", NULL, g_variant_new_boolean (TRUE));
        // By default, the action is not added to the action map.

        element_visibility_section = g_menu_new ();

        MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD] = g_menu_item_new (_("Show Element Visibility Column"), 
                                                                                                 NULL);
        g_menu_item_set_attribute (MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD], 
                                   "action", "s", "app.sf-elmviscol");
        g_menu_append_item (element_visibility_section, MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD]);
		// Translation note: The word "Highlighting" in the sense of letting text stand out by marking it
        MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL] = g_menu_item_new (_("Keep Highlighting"), NULL);
        g_menu_item_set_attribute (MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], 
                                   "action", "s", "app.sf-highlightingstate");
        g_menu_append_item (element_visibility_section, MBut_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL]);
        g_menu_append_section (MBut_submenus[SHOW_ELEMENT_VIS_COL_SUBMENU], NULL, G_MENU_MODEL (element_visibility_section));

        // View submenu items: Show grid

        show_grid_section = g_menu_new ();

        view_action_grid = g_simple_action_new_stateful ("sf-gridstate", type_string, g_variant_new_string ("no_grid_lines"));
        g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (view_action_grid));

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_VIEW_MENU_GRID_ITEMS; ++menu_items_cnt) {
            MBut_view_menu_grid_items[menu_items_cnt] = g_menu_item_new (_(ks.grid_submenu_item_txts[menu_items_cnt]), NULL);
            g_menu_item_set_attribute (MBut_view_menu_grid_items[menu_items_cnt], "action", "s", "app.sf-gridstate");
            g_menu_item_set_attribute (MBut_view_menu_grid_items[menu_items_cnt], "target", "s", ks.grid_states[menu_items_cnt]);
            g_menu_append_item (show_grid_section, MBut_view_menu_grid_items[menu_items_cnt]);
        }

        g_menu_append_section (MBut_submenus[SHOW_GRID_SUBMENU], NULL, G_MENU_MODEL (show_grid_section));

        // Options

        add_popup_menu_items (MBut_menus[OPTIONS_MENU], ks.MBut_options_menu_items, options_menu_item_txts, 
                              options_simple_action_txts, options_menu_item_accelerators, 
                              options_section_starts, options_section_ends, 
                              NUMBER_OF_OPTIONS_MENU_ITEMS, sizeof (options_section_starts) / sizeof (guint8));

        // Help

        add_popup_menu_items (MBut_menus[HELP_MENU], MBut_help_menu_items, help_menu_item_txts, 
                              help_simple_action_txts, help_menu_item_accelerators, 
                              help_section_starts, help_section_ends, 
                              NUMBER_OF_HELP_MENU_ITEMS, sizeof (help_section_starts) / sizeof (guint8));

        // Create a menu button, connect the menu to it and attach it as new first element of the toolbar.

        tb_menu_button = gtk_menu_button_new ();
        gtk_button_set_relief (GTK_BUTTON (tb_menu_button), (!ks.settings.use_header_bar) ? GTK_RELIEF_NONE : GTK_RELIEF_NORMAL);
        menu_button_children = gtk_container_get_children (GTK_CONTAINER (tb_menu_button));
        gtk_widget_destroy (menu_button_children->data);
        GtkWidget *menu_image = gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON);
        gtk_container_add (GTK_CONTAINER (tb_menu_button), menu_image);
        gtk_widget_set_tooltip_text (GTK_WIDGET (tb_menu_button), _("Show Application Menu"));

        gtk_grid_insert_column (GTK_GRID (toolbar), 0);
        gtk_grid_attach (GTK_GRID (toolbar), tb_menu_button, 0, 0, 1, 1);

        gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (tb_menu_button), G_MENU_MODEL (ks.popup_menu));
        gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (tb_menu_button), FALSE);

        // Signals

        g_signal_connect_swapped (ks.file_actions[M_NEW], "activate", G_CALLBACK (new_menu), NULL);
        g_signal_connect_swapped (ks.file_actions[M_OPEN], "activate", G_CALLBACK (open_menu), NULL);
        g_signal_connect_swapped (ks.file_actions[M_SAVE], "activate", G_CALLBACK (save_menu), NULL);
        g_signal_connect_swapped (ks.file_actions[M_SAVE_AS], "activate", G_CALLBACK (save_menu_as), NULL);
        g_signal_connect_swapped (ks.file_actions[M_QUIT], "activate", G_CALLBACK (quit_program), NULL);

        g_signal_connect_swapped (ks.edit_actions[M_UNDO], "activate", G_CALLBACK (undo_redo_restore), "undo");
        g_signal_connect_swapped (ks.edit_actions[M_REDO], "activate", G_CALLBACK (undo_redo_restore), "redo");
        g_signal_connect_swapped (ks.edit_actions[M_MOVE_TOP], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_TOP));
        g_signal_connect_swapped (ks.edit_actions[M_MOVE_UP], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_UP));
        g_signal_connect_swapped (ks.edit_actions[M_MOVE_DOWN], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_DOWN));
        g_signal_connect_swapped (ks.edit_actions[M_MOVE_BOTTOM], "activate", G_CALLBACK (move_selection), 
                                  GUINT_TO_POINTER (MOVE_BOTTOM));
        g_signal_connect_swapped (ks.edit_actions[M_REMOVE], "activate", G_CALLBACK (remove_rows), "popup menu");
        g_signal_connect (ks.edit_actions[M_REMOVE_ALL_CHILDREN], "activate", G_CALLBACK (remove_all_children), NULL);
        g_signal_connect_swapped (ks.edit_actions[M_VISUALIZE], "activate", 
                                  G_CALLBACK (visualize_menus_items_and_separators), GUINT_TO_POINTER (FALSE));
        g_signal_connect_swapped (ks.edit_actions[M_VISUALIZE_RECURSIVELY], "activate", 
                                  G_CALLBACK (visualize_menus_items_and_separators), GUINT_TO_POINTER (TRUE));

        g_signal_connect (search_actions[M_FIND], "activate", G_CALLBACK (show_or_hide_find_grid), NULL);
        g_signal_connect (search_actions[M_FIND_AND_REPLACE], "activate", G_CALLBACK (show_find_and_replace_dialog), NULL);

        g_signal_connect_swapped (ks.view_actions[M_EXPAND_ALL_NODES], "activate", G_CALLBACK (expand_or_collapse_all), 
                                  GUINT_TO_POINTER (TRUE));
        g_signal_connect_swapped (ks.view_actions[M_COLLAPSE_ALL_NODES], "activate", G_CALLBACK (expand_or_collapse_all), 
                                  GUINT_TO_POINTER (FALSE));
        for (menu_items_cnt = M_SHOW_MENU_ID_COL; menu_items_cnt < NUMBER_OF_VIEW_MENU_ITEMS; ++menu_items_cnt) {
            if (menu_items_cnt != M_SHOW_ELEMENT_VIS_COL && menu_items_cnt != M_SHOW_GRID) {
                g_signal_connect (ks.view_actions[menu_items_cnt], "activate", G_CALLBACK (change_view_and_options_GMenu), NULL);
            }
        }
        g_signal_connect (ks.view_action_visibility, "activate", G_CALLBACK (change_view_and_options_GMenu), NULL);
        g_signal_connect (ks.view_action_highlighting, "activate", G_CALLBACK (change_view_and_options_GMenu), NULL);
        g_signal_connect (view_action_grid, "activate", G_CALLBACK (change_view_and_options_GMenu), NULL);

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_OPTIONS_MENU_ITEMS; ++menu_items_cnt) {
            if (menu_items_cnt != M_SHOW_MENU_BUTTON) {
                g_signal_connect (ks.options_actions[menu_items_cnt], "activate", G_CALLBACK (change_view_and_options_GMenu), NULL);
            }
            else {
                ks.handler_id_show_menu_button = g_signal_connect (ks.options_actions[M_SHOW_MENU_BUTTON], "activate", 
                                                                   G_CALLBACK (change_view_and_options_GMenu), NULL);
            }
        }

        g_signal_connect (help_actions[M_HINTS], "activate", G_CALLBACK (show_hints_window), NULL);
#if GTK_CHECK_VERSION(3,20,0)
        g_signal_connect (help_actions[M_SHORTCUTS], "activate", G_CALLBACK (show_shortcuts_window), NULL);
#endif
        g_signal_connect (help_actions[M_REPORT_ISSUE], "activate", G_CALLBACK (bug_report), NULL);
        g_signal_connect (help_actions[M_ABOUT], "activate", G_CALLBACK (about), NULL);
    }
    else {
        GtkWidget *menubar = gtk_menu_bar_new ();

        GtkWidget *MBar_submenus[NUMBER_OF_SUBMENUS];

        GtkWidget *MBar_search_menu_items[NUMBER_OF_SEARCH_MENU_ITEMS];
        GtkWidget *MBar_help_menu_items[NUMBER_OF_HELP_MENU_ITEMS];

        GtkAccelGroup *accel_group = gtk_accel_group_new ();
        guint accel_key;
        GdkModifierType accel_mod;
        GSList *grid_menu_item_group = NULL;

        const gchar *MBar_menu_labels[] = { N_("_File"), 
		                                    // Translation note: Like the German "Bearbeiten". For a menu item.
	                                        N_("_Edit"), 
                                            N_("_Search"), 
											// Translation note: Like the German "Ansicht". For a menu item.
											N_("_View"),
											N_("_Options"),
											N_("_Help") };

        gtk_window_add_accel_group (GTK_WINDOW (ks.window), accel_group);

        const guint8 default_checked[] = { M_SHOW_MENU_ID_COL, M_SHOW_EXECUTE_COL, M_SHOW_ICONS, 
                                           M_HIGHLIGHT_SEPARATORS, M_SHOW_TREE_LINES };

        for (menus_cnt = 0; menus_cnt < NUMBER_OF_MENUS; ++menus_cnt) {
            ks.MBar_menus[menus_cnt] = gtk_menu_new ();
            ks.MBar_menu_items[menus_cnt] = gtk_menu_item_new_with_mnemonic (_(MBar_menu_labels[menus_cnt]));
        }

        // File

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_FILE_MENU_ITEMS; ++menu_items_cnt) {
            ks.MBar_file_menu_items[menu_items_cnt] = gtk_menu_item_new_with_mnemonic (_(file_menu_item_txts[menu_items_cnt]));

            gtk_accelerator_parse (_(file_menu_item_accelerators[menu_items_cnt]), &accel_key, &accel_mod);

            gtk_widget_add_accelerator (ks.MBar_file_menu_items[menu_items_cnt], "activate", accel_group, 
                                        accel_key, accel_mod, GTK_ACCEL_VISIBLE);

            gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[FILE_MENU]), ks.MBar_file_menu_items[menu_items_cnt]);

            if (menu_items_cnt == M_SAVE_AS) {
                gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[FILE_MENU]), gtk_separator_menu_item_new ());
            }
        }

        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[FILE_MENU]), ks.MBar_menus[FILE_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[FILE_MENU]);

        // Edit

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_EDIT_MENU_ITEMS; ++menu_items_cnt) {
            if (edit_menu_item_txts[menu_items_cnt][0] != '_') { // Only "Remove" has a mnemonic
                ks.MBar_edit_menu_items[menu_items_cnt] = gtk_menu_item_new_with_label (_(edit_menu_item_txts[menu_items_cnt]));
            }
            else {
                ks.MBar_edit_menu_items[menu_items_cnt] = gtk_menu_item_new_with_mnemonic (_(edit_menu_item_txts[menu_items_cnt]));
            }

            if (*edit_menu_item_accelerators[menu_items_cnt]) {
                gtk_accelerator_parse (_(edit_menu_item_accelerators[menu_items_cnt]), &accel_key, &accel_mod);
                gtk_widget_add_accelerator (ks.MBar_edit_menu_items[menu_items_cnt], "activate", accel_group, 
                                            accel_key, accel_mod, GTK_ACCEL_VISIBLE);
            }

            gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[EDIT_MENU]), ks.MBar_edit_menu_items[menu_items_cnt]);

            switch (menu_items_cnt) {
                case M_REDO:
                case M_MOVE_BOTTOM:
                case M_REMOVE_ALL_CHILDREN:
                    gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[EDIT_MENU]), gtk_separator_menu_item_new ());
            }
        }

        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[EDIT_MENU]), ks.MBar_menus[EDIT_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[EDIT_MENU]);

        // Search

        MBar_search_menu_items[M_FIND] = gtk_menu_item_new_with_mnemonic (_("Find"));
        gtk_accelerator_parse (_(search_menu_item_accelerators[M_FIND]), &accel_key, &accel_mod);
        gtk_widget_add_accelerator (MBar_search_menu_items[M_FIND], "activate", accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);

        MBar_search_menu_items[M_FIND_AND_REPLACE] = gtk_menu_item_new_with_label (_("Find and Replace"));
        gtk_accelerator_parse (_(search_menu_item_accelerators[M_FIND_AND_REPLACE]), &accel_key, &accel_mod);
        gtk_widget_add_accelerator (MBar_search_menu_items[M_FIND_AND_REPLACE], "activate", accel_group, 
                                    accel_key, accel_mod, GTK_ACCEL_VISIBLE);

        gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[SEARCH_MENU]), MBar_search_menu_items[M_FIND]);
        gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[SEARCH_MENU]), MBar_search_menu_items[M_FIND_AND_REPLACE]);
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[SEARCH_MENU]), ks.MBar_menus[SEARCH_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[SEARCH_MENU]);

        // View

        ks.MBar_view_menu_items[M_EXPAND_ALL_NODES] = gtk_menu_item_new_with_label (_("Expand All Nodes"));
        gtk_accelerator_parse (_(view_menu_item_accelerators[M_EXPAND_ALL_NODES]), &accel_key, &accel_mod);
        gtk_widget_add_accelerator (ks.MBar_view_menu_items[M_EXPAND_ALL_NODES], "activate", 
                                    accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);
        ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES] = gtk_menu_item_new_with_label (_("Collapse All Nodes"));
        gtk_accelerator_parse (_(view_menu_item_accelerators[M_COLLAPSE_ALL_NODES]), &accel_key, &accel_mod);
        gtk_widget_add_accelerator (ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES], "activate", 
                                    accel_group, accel_key, accel_mod, GTK_ACCEL_VISIBLE);
        ks.MBar_view_menu_items[M_SHOW_MENU_ID_COL] = gtk_check_menu_item_new_with_label (_("Show Menu ID Column"));
		// Translation note: Do not translate "Execute"
        ks.MBar_view_menu_items[M_SHOW_EXECUTE_COL] = gtk_check_menu_item_new_with_label (_("Show Execute Column"));
        ks.MBar_view_menu_items[M_SHOW_ELEMENT_VIS_COL] = gtk_menu_item_new_with_label (_("Show Element Visibility Column"));
        ks.MBar_view_menu_items[M_SHOW_ICONS] = gtk_check_menu_item_new_with_label (_("Show Icons"));
		// Translation note: The verb "highlight", in this context meaning to make a separator stand out optically
        ks.MBar_view_menu_items[M_HIGHLIGHT_SEPARATORS] = gtk_check_menu_item_new_with_label (_("Highlight Separators"));
        ks.MBar_view_menu_items[M_SHOW_TREE_LINES] = gtk_check_menu_item_new_with_label (_("Show Tree Lines")); 
        ks.MBar_view_menu_items[M_SHOW_GRID] = gtk_menu_item_new_with_label (_("Show Grid"));

        for (menus_cnt = 0; menus_cnt < NUMBER_OF_VIEW_MENU_ITEMS; ++menus_cnt) {
            switch (menus_cnt) {
                case M_SHOW_MENU_ID_COL:
                case M_SHOW_ICONS:
                case M_HIGHLIGHT_SEPARATORS:
                case M_SHOW_TREE_LINES:
                    gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[VIEW_MENU]), gtk_separator_menu_item_new ());
            }
            gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[VIEW_MENU]), ks.MBar_view_menu_items[menus_cnt]);
        }
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[VIEW_MENU]), ks.MBar_menus[VIEW_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[VIEW_MENU]);

        // Visibility Submenu
        ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD] = 
            gtk_check_menu_item_new_with_label (_("Show Element Visibility Column"));
        ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL] = 
            gtk_check_menu_item_new_with_label (_("Keep Highlighting"));

        MBar_submenus[SHOW_ELEMENT_VIS_COL_SUBMENU] = gtk_menu_new ();
        for (menus_cnt = 0; menus_cnt < NUMBER_OF_VIEW_MENU_VISIBILITY_ITEMS; ++menus_cnt) {
            gtk_menu_shell_append (GTK_MENU_SHELL (MBar_submenus[SHOW_ELEMENT_VIS_COL_SUBMENU]), 
                                   ks.MBar_view_menu_visibility_items[menus_cnt]);
        }
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_view_menu_items[M_SHOW_ELEMENT_VIS_COL]), 
                                   MBar_submenus[SHOW_ELEMENT_VIS_COL_SUBMENU]);

        // Default settings for View items
        for (menu_items_cnt = 0; menu_items_cnt < G_N_ELEMENTS (default_checked); ++menu_items_cnt) {
            gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_items[default_checked[menu_items_cnt]]), TRUE);
        }
        gtk_widget_set_sensitive (ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], FALSE);

        // Grid Submenu
        MBar_submenus[SHOW_GRID_SUBMENU] = gtk_menu_new ();
        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_VIEW_MENU_GRID_ITEMS; ++menu_items_cnt) {
            ks.MBar_view_menu_grid_items[menu_items_cnt] = gtk_radio_menu_item_new_with_label (grid_menu_item_group, 
                                                                                               _(ks.grid_submenu_item_txts[menu_items_cnt]));
            grid_menu_item_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (ks.MBar_view_menu_grid_items[menu_items_cnt]));
            gtk_menu_shell_append (GTK_MENU_SHELL (MBar_submenus[SHOW_GRID_SUBMENU]), ks.MBar_view_menu_grid_items[menu_items_cnt]);
        }
        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_view_menu_items[M_SHOW_GRID]), MBar_submenus[SHOW_GRID_SUBMENU]);

        // Options

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_OPTIONS_MENU_ITEMS; ++menu_items_cnt) {
            ks.MBar_options_menu_items[menu_items_cnt] = gtk_check_menu_item_new_with_label (_(options_menu_item_txts[menu_items_cnt]));
            gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[OPTIONS_MENU]), ks.MBar_options_menu_items[menu_items_cnt]);

            if (menu_items_cnt == M_KEEP_ROOT_MENU_SEPARATE || menu_items_cnt == M_NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS) {
                gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[OPTIONS_MENU]), gtk_separator_menu_item_new ());
            }
        }

        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[OPTIONS_MENU]), ks.MBar_menus[OPTIONS_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[OPTIONS_MENU]);

        /* To prevent that the dialog for a decorations type change is triggered when the key file is parsed, the menu item has to 
           be set to the value that has been already read into ks.settings.use_header_bar BEFORE the signals are connected. When the 
           key file is parsed later again completely, the menu item will already be set according to the value found in the key file. 
           Since gtk_check_menu_item_set_active will only trigger the callback function if the new status is different from the current one, 
           the callback function is not called and thus the dialog not shown. */
        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.MBar_options_menu_items[M_USE_HEADER_BAR]), ks.settings.use_header_bar);

        // Help

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_HELP_MENU_ITEMS; ++menu_items_cnt) {
#if !(GTK_CHECK_VERSION(3,20,0))
            if (menu_items_cnt == M_SHORTCUTS) {
                continue;
            }
#endif
            MBar_help_menu_items[menu_items_cnt] = gtk_menu_item_new_with_mnemonic (_(help_menu_item_txts[menu_items_cnt]));
            gtk_accelerator_parse (_(help_menu_item_accelerators[menu_items_cnt]), &accel_key, &accel_mod);
            gtk_widget_add_accelerator (MBar_help_menu_items[menu_items_cnt], "activate", accel_group, 
                                        accel_key, accel_mod, GTK_ACCEL_VISIBLE);

            gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[HELP_MENU]), MBar_help_menu_items[menu_items_cnt]);

            if (menu_items_cnt == M_REPORT_ISSUE) {
                gtk_menu_shell_append (GTK_MENU_SHELL (ks.MBar_menus[HELP_MENU]), gtk_separator_menu_item_new ());
            }
        }

        gtk_menu_item_set_submenu (GTK_MENU_ITEM (ks.MBar_menu_items[HELP_MENU]), ks.MBar_menus[HELP_MENU]);
        gtk_menu_shell_append (GTK_MENU_SHELL (menubar), ks.MBar_menu_items[HELP_MENU]);

        gtk_container_add (GTK_CONTAINER (main_box), menubar);

        // Signals

        g_signal_connect_swapped (ks.MBar_file_menu_items[M_NEW], "activate", G_CALLBACK (new_menu), NULL);
        g_signal_connect_swapped (ks.MBar_file_menu_items[M_OPEN], "activate", G_CALLBACK (open_menu), NULL);
        g_signal_connect_swapped (ks.MBar_file_menu_items[M_SAVE], "activate", G_CALLBACK (save_menu), NULL);
        g_signal_connect_swapped (ks.MBar_file_menu_items[M_SAVE_AS], "activate", G_CALLBACK (save_menu_as), NULL);
        g_signal_connect_swapped (ks.MBar_file_menu_items[M_QUIT], "activate", G_CALLBACK (quit_program), NULL);

        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_UNDO], "activate", G_CALLBACK (undo_redo_restore), "undo");
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_REDO], "activate", G_CALLBACK (undo_redo_restore), "redo");
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_MOVE_TOP], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_TOP));
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_MOVE_UP], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_UP));
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_MOVE_DOWN], "activate", 
                                  G_CALLBACK (move_selection), GUINT_TO_POINTER (MOVE_DOWN));
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_MOVE_BOTTOM], "activate", G_CALLBACK (move_selection), 
                                  GUINT_TO_POINTER (MOVE_BOTTOM));
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_REMOVE], "activate", G_CALLBACK (remove_rows), "popup menu");
        g_signal_connect (ks.MBar_edit_menu_items[M_REMOVE_ALL_CHILDREN], "activate", G_CALLBACK (remove_all_children), NULL);
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_VISUALIZE], "activate", 
                                  G_CALLBACK (visualize_menus_items_and_separators), GUINT_TO_POINTER (FALSE));
        g_signal_connect_swapped (ks.MBar_edit_menu_items[M_VISUALIZE_RECURSIVELY], "activate", 
                                  G_CALLBACK (visualize_menus_items_and_separators), GUINT_TO_POINTER (TRUE));

        g_signal_connect (MBar_search_menu_items[M_FIND], "activate", G_CALLBACK (show_or_hide_find_grid), NULL);
        g_signal_connect (MBar_search_menu_items[M_FIND_AND_REPLACE], "activate", G_CALLBACK (show_find_and_replace_dialog), NULL);

        g_signal_connect_swapped (ks.MBar_view_menu_items[M_EXPAND_ALL_NODES], "activate", 
                                  G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (TRUE));
        g_signal_connect_swapped (ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES], "activate", 
                                  G_CALLBACK (expand_or_collapse_all), GUINT_TO_POINTER (FALSE));

        for (menu_items_cnt = M_SHOW_MENU_ID_COL; menu_items_cnt < NUMBER_OF_VIEW_MENU_ITEMS; ++menu_items_cnt) {
            if (menu_items_cnt != M_SHOW_ELEMENT_VIS_COL && menu_items_cnt != M_SHOW_GRID) {
                g_signal_connect (ks.MBar_view_menu_items[menu_items_cnt], "activate", 
                                  G_CALLBACK (change_view_and_options_GtkMenu), NULL);
            }
        }

        for (menu_items_cnt = M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD; 
             menu_items_cnt < NUMBER_OF_VIEW_MENU_VISIBILITY_ITEMS; 
             ++menu_items_cnt) {
            g_signal_connect (ks.MBar_view_menu_visibility_items[menu_items_cnt], "activate", 
                              G_CALLBACK (change_view_and_options_GtkMenu), NULL);
        }

        for (menu_items_cnt = M_NO_GRID_LINES; menu_items_cnt < NUMBER_OF_VIEW_MENU_GRID_ITEMS; ++menu_items_cnt) {
            g_signal_connect (ks.MBar_view_menu_grid_items[menu_items_cnt], "activate", 
                              G_CALLBACK (change_view_and_options_GtkMenu), NULL);
        }

        for (menu_items_cnt = 0; menu_items_cnt < NUMBER_OF_OPTIONS_MENU_ITEMS; ++menu_items_cnt) {
            if (menu_items_cnt < M_USE_HEADER_BAR) {
                g_signal_connect (ks.MBar_options_menu_items[menu_items_cnt], "activate", 
                                  G_CALLBACK (change_view_and_options_GtkMenu), NULL);
            }
            else if (menu_items_cnt == M_USE_HEADER_BAR) {
                ks.handler_id_use_header_bar = g_signal_connect (ks.MBar_options_menu_items[menu_items_cnt], "activate", 
                                                                 G_CALLBACK (change_view_and_options_GtkMenu), NULL);
            }
            else {
                ks.handler_id_show_menu_button = g_signal_connect (ks.MBar_options_menu_items[menu_items_cnt], "activate", 
                                                                   G_CALLBACK (change_view_and_options_GtkMenu), NULL);
            }
        }


        g_signal_connect (MBar_help_menu_items[M_HINTS], "activate", G_CALLBACK (show_hints_window), NULL);
#if GTK_CHECK_VERSION(3,20,0)
        g_signal_connect (MBar_help_menu_items[M_SHORTCUTS], "activate", G_CALLBACK (show_shortcuts_window), NULL);
#endif
        g_signal_connect (MBar_help_menu_items[M_REPORT_ISSUE], "activate", G_CALLBACK (bug_report), NULL);
        g_signal_connect (MBar_help_menu_items[M_ABOUT], "activate", G_CALLBACK (about), NULL);
    }


    // ### Add toolbar to the header bar or to the main box. ###


    if (ks.settings.use_header_bar) {
        gtk_header_bar_pack_start (GTK_HEADER_BAR (ks.header_bar), toolbar);
    }
    else {
        gtk_container_set_border_width (GTK_CONTAINER (toolbar), 7);
        gtk_container_add (GTK_CONTAINER (main_box), toolbar);
    }


    // ### Create Button Bar. ###


    ks.button_grid = gtk_grid_new ();
    gtk_container_add (GTK_CONTAINER (main_box), ks.button_grid);

    GtkWidget *button_add_grid = gtk_grid_new ();
    gtk_container_add (GTK_CONTAINER (ks.button_grid), button_add_grid);

    ks.add_image = gtk_image_new_from_icon_name ("list-add", GTK_ICON_SIZE_BUTTON);
    g_object_set (ks.add_image, "margin-start", 5, "margin-end", 5, NULL);
    gtk_container_add (GTK_CONTAINER (button_add_grid), ks.add_image);

    // Label text is set dynamically dependent on whether it is possible to add a row or not.
    ks.bt_bar_label = gtk_label_new (_("Add New:  "));
    gtk_container_add (GTK_CONTAINER (button_add_grid), ks.bt_bar_label);

    /* Label text is set dynamically dependent on the type of the selected row, 
       but it is set here for an initial size request for the button_add_grid. */
    ks.bt_add_action_option_label = gtk_label_new ("Action");
    gtk_label_set_use_underline (GTK_LABEL (ks.bt_add_action_option_label), TRUE);

    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_ADD_BUTTONS; ++buttons_cnt) {
        ks.bt_add[buttons_cnt] = gtk_button_new ();
        add_button_content (ks.bt_add[buttons_cnt], (buttons_cnt < ACTION_OR_OPTION) ? _(bt_add_txts[buttons_cnt]) : "");
        gtk_container_add (GTK_CONTAINER (button_add_grid), ks.bt_add[buttons_cnt]);
    }

    separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
    g_object_set (separator, "margin-start", 7, "margin-end", 7, NULL);
    gtk_container_add (GTK_CONTAINER (ks.button_grid), separator);

    ks.bt_remove = gtk_button_new_from_icon_name ("list-remove", GTK_ICON_SIZE_BUTTON);
    gtk_widget_set_tooltip_text (ks.bt_remove, _("Remove"));
    gtk_widget_set_name (ks.bt_remove, "list-remove");
    button_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.bt_remove), 
                                    GTK_STYLE_PROVIDER (button_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_css_provider_load_from_data (button_provider, "#list-remove { padding-left:8px; padding-right:8px; }", -1, NULL);    
    gtk_container_add (GTK_CONTAINER (ks.button_grid), ks.bt_remove);
    // Cleanup
    g_object_unref (button_provider);

    const gchar *bt_movement_txts[] = { "go-top", "go-up", "go-down", "go-bottom" };
    const gchar *bt_movement_tooltips[] = { // Translation note: This is for a context menu for moving a node in a tree view
	                                        N_("Move to Top"),
											// Translation note: This is for a context menu for moving a node in a tree view
	                                        N_("Move Up"),
											// Translation note: This is for a context menu for moving a node in a tree view
											N_("Move Down"),
											// Translation note: This is for a context menu for moving a node in a tree view
											N_("Move to Bottom") };
    FOREACH_IN_ARRAY (bt_movement_txts, buttons_cnt) {
        g_autofree gchar *css_str;

        ks.bt_movements[buttons_cnt] = gtk_button_new_from_icon_name (bt_movement_txts[buttons_cnt], GTK_ICON_SIZE_BUTTON);
        gtk_widget_set_tooltip_text (ks.bt_movements[buttons_cnt], _(bt_movement_tooltips[buttons_cnt]));
        gtk_widget_set_name (ks.bt_remove, bt_movement_txts[buttons_cnt]);
        button_provider = gtk_css_provider_new ();
        gtk_style_context_add_provider (gtk_widget_get_style_context (ks.bt_remove), 
                                        GTK_STYLE_PROVIDER (button_provider), 
                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        css_str = g_strdup_printf ("#%s { padding-left:8px; padding-right:8px; }", bt_movement_txts[buttons_cnt]);
        gtk_css_provider_load_from_data (button_provider, css_str, -1, NULL);
        gtk_container_add (GTK_CONTAINER (ks.button_grid), ks.bt_movements[buttons_cnt]);
        // Cleanup
        g_object_unref (button_provider);
    }

    gtk_widget_set_margin_start (ks.bt_movements[MOVE_TOP], 5);


    // ### Enter Values Box ###


    ks.enter_values_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_widget_set_halign (ks.enter_values_box, GTK_ALIGN_CENTER);
    ks.enter_values_label = gtk_label_new (NULL);
    gtk_box_pack_start (GTK_BOX (ks.enter_values_box), ks.enter_values_label, FALSE, TRUE, 0);
    gtk_widget_set_name (ks.enter_values_label, "enter_values_label");
    gtk_container_add (GTK_CONTAINER (main_box), ks.enter_values_box);
    g_object_set (ks.enter_values_label, "margin-top", 3, "margin-bottom", 12, NULL);
    enter_values_label_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.enter_values_label), 
                                    GTK_STYLE_PROVIDER (enter_values_label_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_css_provider_load_from_data (enter_values_label_css_provider, 
                                     "#enter_values_label { border-style:solid;"
                                     "                      border-width:2px;"
                                     "                      padding-top:4px;"
                                     "                      padding-bottom:4px;"
                                     "                      padding-left:6px;"
                                     "                      padding-right:6px; }", 
                                     -1, NULL);
    // No unreferencing of the css provider here, as it used elsewhere in the program.


    gtk_window_set_position (GTK_WINDOW (ks.window), GTK_WIN_POS_CENTER_ALWAYS);


    // ### Create a new sub box that will contain the entry grids and the tree view. ###


    ks.sub_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add (GTK_CONTAINER (main_box), ks.sub_box);


    // ### Grid for entering the values of new rows. ###


    ks.action_option_grid = gtk_grid_new ();
    gtk_orientable_set_orientation (GTK_ORIENTABLE (ks.action_option_grid), GTK_ORIENTATION_VERTICAL);
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.action_option_grid);

    // List store and model for action/option combo box; the combo box isn't yet created here, only when needed.
    ks.action_option_combo_box_liststore = gtk_list_store_new (NUMBER_OF_ACTION_OPTION_COMBO_ELEMENTS, G_TYPE_STRING);
    ks.action_option_combo_box_model = GTK_TREE_MODEL (ks.action_option_combo_box_liststore);

    /*
        The "new action/option" combo box is created and destroyed dynamically, since it does not shrink 
        back down when being repopulated with new shorter items, resulting in a clunky look in that case. 
        The code for adding and deleting the combo box is not part of this function or module, but part of 
        adding_and_deleting.c.
    */
    ks.new_action_option_grid_or_table = gtk_grid_new ();

    ks.new_action_option_widgets[INSIDE_MENU_LABEL] = gtk_label_new (_(" Inside Menu "));

    ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON] = gtk_check_button_new ();

    ks.new_action_option_widgets[INCLUDING_ACTION_LABEL] = gtk_label_new (_(" Incl. Action "));

    ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON] = gtk_check_button_new ();
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]), TRUE);

    ks.new_action_option_widgets[ACTION_OPTION_DONE] = gtk_button_new ();
    add_button_content (ks.new_action_option_widgets[ACTION_OPTION_DONE], _("_Done"));

    ks.new_action_option_widgets[ACTION_OPTION_CANCEL] = gtk_button_new ();
    add_button_content (ks.new_action_option_widgets[ACTION_OPTION_CANCEL], C_("Cancel|No File Dialogue", "_Cancel"));

    gtk_container_add (GTK_CONTAINER (ks.action_option_grid), ks.new_action_option_grid_or_table);
    g_object_set (ks.new_action_option_grid_or_table, "margin-top", 5, "margin-bottom", 5, NULL);

    for (guint8 widgets_cnt = 0; widgets_cnt < NUMBER_OF_NEW_ACTION_OPTION_WIDGETS; ++widgets_cnt) {
        if (widgets_cnt != NEW_ACTION_OPTION_COMBO_BOX) {
            gtk_grid_attach (GTK_GRID (ks.new_action_option_grid_or_table), ks.new_action_option_widgets[widgets_cnt], 
                             widgets_cnt, 0, 1, 1);
        }
    }

    // Execute options
    ks.options_grid = gtk_grid_new ();
    gtk_container_add (GTK_CONTAINER (ks.action_option_grid), ks.options_grid);

    ks.options_fields[SN_OR_PROMPT] = gtk_check_button_new ();

    for (guint8 options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; ++options_cnt) {
        ks.options_labels[options_cnt] = gtk_label_new (ks.options_label_txts[options_cnt]);

        gtk_widget_set_halign (ks.options_labels[options_cnt], GTK_ALIGN_START);

        if (options_cnt < SN_OR_PROMPT) {
            ks.options_fields[options_cnt] = gtk_entry_new ();
            ks.execute_options_css_providers[options_cnt] = gtk_css_provider_new ();
            gtk_style_context_add_provider (gtk_widget_get_style_context (ks.options_fields[options_cnt]), 
                                            GTK_STYLE_PROVIDER (ks.execute_options_css_providers[options_cnt]), 
                                            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
            gtk_widget_set_hexpand (ks.options_fields[options_cnt], TRUE);
        }
        gtk_grid_attach (GTK_GRID (ks.options_grid), ks.options_labels[options_cnt], 0, options_cnt, 
                         (options_cnt < SN_OR_PROMPT) ? 2 : 1, 1);
        gtk_grid_attach (GTK_GRID (ks.options_grid), ks.options_fields[options_cnt], 
                         (options_cnt < SN_OR_PROMPT) ? 2 : 1, options_cnt, 1, 1);
    }

    ks.suboptions_grid = gtk_grid_new ();
    gtk_grid_attach (GTK_GRID (ks.options_grid), ks.suboptions_grid, 2, 2, 1, 1);
    gtk_widget_set_margin_start (ks.suboptions_grid, 2);

    for (snotify_opts_cnt = ENABLED; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
        ks.suboptions_labels[snotify_opts_cnt] = gtk_label_new (NULL); // the actual label text is set later.

        gtk_widget_set_halign (ks.suboptions_labels[snotify_opts_cnt], GTK_ALIGN_START);

        gtk_grid_attach (GTK_GRID (ks.suboptions_grid), ks.suboptions_labels[snotify_opts_cnt], 0, snotify_opts_cnt, 1, 1);

        ks.suboptions_fields[snotify_opts_cnt] = (snotify_opts_cnt == ENABLED) ? gtk_check_button_new () : gtk_entry_new ();
        if (snotify_opts_cnt > ENABLED) {
            ks.suboptions_fields_css_providers[snotify_opts_cnt - 1] = gtk_css_provider_new ();
            gtk_style_context_add_provider (gtk_widget_get_style_context (ks.suboptions_fields[snotify_opts_cnt]), 
                                            GTK_STYLE_PROVIDER (ks.suboptions_fields_css_providers[snotify_opts_cnt - 1]), 
                                            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        }
        gtk_grid_attach (GTK_GRID (ks.suboptions_grid), ks.suboptions_fields[snotify_opts_cnt], 1, snotify_opts_cnt, 1, 1);

        gtk_widget_set_hexpand (ks.suboptions_fields[snotify_opts_cnt], TRUE);
    }

    ks.mandatory = gtk_label_new (NULL);
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.mandatory);

    ks.separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
    g_object_set (ks.separator, "margin-top", 10, "margin-bottom", 10, NULL);
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.separator);

    ks.enter_values_buttons_grid = gtk_grid_new ();
    gtk_grid_set_column_spacing (GTK_GRID (ks.enter_values_buttons_grid), 10);
    gtk_widget_set_margin_bottom (ks.enter_values_buttons_grid, 10);
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.enter_values_buttons_grid);

    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_ENTER_VALUES_BUTTONS; ++buttons_cnt) {
        enter_values_buttons[buttons_cnt] = gtk_button_new ();
        add_button_content (enter_values_buttons[buttons_cnt], (buttons_cnt != CANCEL_NEW_XY) ? _(enter_values_button_txts[buttons_cnt]) : g_dpgettext2 (NULL, "Cancel|No File Dialogue", "_Cancel"));
        gtk_container_add (GTK_CONTAINER (ks.enter_values_buttons_grid), enter_values_buttons[buttons_cnt]);  
    }
    gtk_widget_set_halign (ks.enter_values_buttons_grid, GTK_ALIGN_CENTER);


    // ### Create find grid. ###


    ks.find_grid = gtk_grid_new ();
    gtk_orientable_set_orientation (GTK_ORIENTABLE (ks.find_grid), GTK_ORIENTATION_VERTICAL);
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.find_grid);

    for (guint8 grids_cnt = 0; grids_cnt < NUMBER_OF_FIND_SUBGRIDS; ++grids_cnt) {
        find_subgrids[grids_cnt] = gtk_grid_new ();
        gtk_container_add (GTK_CONTAINER (ks.find_grid), find_subgrids[grids_cnt]);
    }

    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_FIND_ENTRY_BUTTONS; ++buttons_cnt) {
        ks.find_entry_buttons[buttons_cnt] = gtk_button_new_from_icon_name (find_entry_buttons_imgs[buttons_cnt], 
                                                                            GTK_ICON_SIZE_BUTTON);
        gtk_widget_set_tooltip_text (GTK_WIDGET (ks.find_entry_buttons[buttons_cnt]), _(find_entry_buttons_tooltips[buttons_cnt]));
        gtk_container_add (GTK_CONTAINER (find_subgrids[ENTRY]), ks.find_entry_buttons[buttons_cnt]);
    }

    ks.find_entry = gtk_search_entry_new ();
    ks.find_entry_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_entry), 
                                    GTK_STYLE_PROVIDER (ks.find_entry_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    gtk_widget_set_hexpand (ks.find_entry, TRUE);
    gtk_container_add (GTK_CONTAINER (find_subgrids[ENTRY]), ks.find_entry);

    for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS - 1; ++columns_cnt) {
        ks.find_in_columns[columns_cnt] = gtk_check_button_new_with_label (_(ks.column_header_txts[columns_cnt]));
        gtk_container_add (GTK_CONTAINER (find_subgrids[COLUMNS_SELECTION]), ks.find_in_columns[columns_cnt]);

        ks.find_in_columns_css_providers[columns_cnt] = gtk_css_provider_new ();
        gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_in_columns[columns_cnt]), 
                                        GTK_STYLE_PROVIDER (ks.find_in_columns_css_providers[columns_cnt]), 
                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }

    ks.find_in_all_columns = gtk_check_button_new_with_label (_("All Columns"));
    gtk_container_add (GTK_CONTAINER (find_subgrids[COLUMNS_SELECTION]), ks.find_in_all_columns);
    ks.find_in_all_columns_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.find_in_all_columns), 
                                    GTK_STYLE_PROVIDER (ks.find_in_all_columns_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_SPECIAL_OPTIONS; ++buttons_cnt) {
        ks.special_options[buttons_cnt] = gtk_check_button_new_with_label (_(special_options_txts[buttons_cnt]));
        gtk_container_add (GTK_CONTAINER (find_subgrids[SPECIAL_OPTIONS]), ks.special_options[buttons_cnt]);
    }

    // Default setting
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.special_options[REGULAR_EXPRESSION]), TRUE);


    // ### Initialize Tree View. ###


    ks.treeview = gtk_tree_view_new ();
    gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (ks.treeview), TRUE); // Default
    gtk_tree_view_set_enable_search (GTK_TREE_VIEW (ks.treeview), FALSE); // Kickshaw has its own search functionality.

    // Create columns with cell renderers and add them to the treeview.

    for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS; ++columns_cnt) {
        ks.columns[columns_cnt] = gtk_tree_view_column_new ();
        gtk_tree_view_column_set_title (ks.columns[columns_cnt], _(ks.column_header_txts[columns_cnt]));

        if (columns_cnt == COL_MENU_ELEMENT) {
            ks.renderers[PIXBUF_RENDERER] = gtk_cell_renderer_pixbuf_new ();
            ks.renderers[EXCL_TXT_RENDERER] = gtk_cell_renderer_text_new ();
            gtk_tree_view_column_pack_start (ks.columns[COL_MENU_ELEMENT], ks.renderers[PIXBUF_RENDERER], FALSE);
            gtk_tree_view_column_pack_start (ks.columns[COL_MENU_ELEMENT], ks.renderers[EXCL_TXT_RENDERER], FALSE);
            gtk_tree_view_column_set_attributes (ks.columns[COL_MENU_ELEMENT], ks.renderers[PIXBUF_RENDERER], 
                                                 "pixbuf", TS_ICON_IMG, NULL);
        }
        else if (columns_cnt == COL_VALUE) {
            ks.renderers[BOOL_RENDERER] = gtk_cell_renderer_toggle_new ();
            g_signal_connect (ks.renderers[BOOL_RENDERER], "toggled", G_CALLBACK (boolean_toggled), NULL);
            gtk_tree_view_column_pack_start (ks.columns[COL_VALUE], ks.renderers[BOOL_RENDERER], FALSE);
        }

        ks.renderers[TXT_RENDERER] = gtk_cell_renderer_text_new ();
        g_signal_connect (ks.renderers[TXT_RENDERER], "edited", G_CALLBACK (cell_edited), GUINT_TO_POINTER ((guint) columns_cnt));
        gtk_tree_view_column_pack_start (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER], FALSE);
        // TREEVIEW_COLUMN_OFFSET is used to skip the icon related columns in the treestore. 
        gtk_tree_view_column_set_attributes (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER], 
                                             "text", columns_cnt + TREEVIEW_COLUMN_OFFSET, NULL);

        gtk_tree_view_column_set_cell_data_func (ks.columns[columns_cnt], ks.renderers[TXT_RENDERER], 
                                                 (GtkTreeCellDataFunc) set_column_attributes, 
                                                 GUINT_TO_POINTER ((guint) columns_cnt), 
                                                 NULL); // NULL -> no destroy notify for user data.
        gtk_tree_view_column_set_resizable (ks.columns[columns_cnt], TRUE);
        gtk_tree_view_append_column (GTK_TREE_VIEW (ks.treeview), ks.columns[columns_cnt]);
    }

    // Default setting
    gtk_tree_view_column_set_visible (ks.columns[COL_ELEMENT_VISIBILITY], FALSE);

    // Set treestore and model.

    /*
        Order: icon img, icon img status, icon img modification time, icon path, 
               menu element, type, value, menu ID, execute, element visibility
    */
    ks.treestore = gtk_tree_store_new (NUMBER_OF_TS_ELEMENTS, GDK_TYPE_PIXBUF, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, 
                                       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 
                                       G_TYPE_STRING);

    gtk_tree_view_set_model (GTK_TREE_VIEW (ks.treeview), GTK_TREE_MODEL (ks.treestore));
    ks.ts_model = gtk_tree_view_get_model (GTK_TREE_VIEW (ks.treeview));
    g_object_unref (ks.treestore);

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

    // Set scrolled window that contains the treeview.
    scrolled_window = gtk_scrolled_window_new (NULL, NULL); // No manual horizontal or vertical adjustment = NULL.
    gtk_container_add (GTK_CONTAINER (ks.sub_box), scrolled_window);

    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add (GTK_CONTAINER (scrolled_window), ks.treeview);
    gtk_widget_set_vexpand (scrolled_window, TRUE);

    // Set drag and drop destination parameters.
    gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (ks.treeview), ks.enable_list, 1, GDK_ACTION_MOVE);


    // ### Create entry grid. ###


    ks.entry_grid = gtk_grid_new ();
    gtk_container_add (GTK_CONTAINER (ks.sub_box), ks.entry_grid);

    ks.liststore = gtk_list_store_new (1, G_TYPE_STRING);
    ks.ls_model = GTK_TREE_MODEL (ks.liststore);

    // Label text is set dynamically dependent on the type of the selected row.
    ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY] = gtk_label_new (NULL);
    gtk_widget_set_halign (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY], GTK_ALIGN_START);

    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY], 0, 0, 1, 1);

    ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY] = gtk_entry_new ();
    ks.menu_element_or_value_entry_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), 
                                    GTK_STYLE_PROVIDER (ks.menu_element_or_value_entry_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_widget_set_hexpand (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], TRUE);
    entry_completion = gtk_entry_completion_new ();
    gtk_entry_completion_set_model (entry_completion, GTK_TREE_MODEL (ks.liststore));
    gtk_entry_completion_set_text_column (entry_completion, 0);
    gtk_entry_completion_set_match_func (entry_completion, (GtkEntryCompletionMatchFunc) entry_completion_func, NULL, NULL);
    gtk_entry_set_completion (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), entry_completion);    
    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], 1, 0, 1, 1);

    ks.entry_labels[ICON_PATH_ENTRY] = gtk_label_new (_(" Icon: "));
    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[ICON_PATH_ENTRY], 2, 0, 1, 1);

    ks.icon_chooser = gtk_button_new_from_icon_name ("document-open", GTK_ICON_SIZE_BUTTON);
    gtk_widget_set_tooltip_text (GTK_WIDGET (ks.icon_chooser), _("Add/Change Icon"));
    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.icon_chooser, 3, 0, 1, 1);

    ks.remove_icon = gtk_button_new_from_icon_name ("list-remove", GTK_ICON_SIZE_BUTTON);
    gtk_widget_set_tooltip_text (GTK_WIDGET (ks.remove_icon), _("Remove Icon"));
    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.remove_icon, 4, 0, 1, 1);

    ks.entry_fields[ICON_PATH_ENTRY] = gtk_entry_new ();
    ks.icon_path_entry_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[ICON_PATH_ENTRY]), 
                                    GTK_STYLE_PROVIDER (ks.icon_path_entry_css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    gtk_widget_set_hexpand (ks.entry_fields[ICON_PATH_ENTRY], TRUE);
    entry_completion = gtk_entry_completion_new ();
    gtk_entry_completion_set_model (entry_completion, GTK_TREE_MODEL (ks.liststore));
    gtk_entry_completion_set_text_column (entry_completion, 0);
    gtk_entry_completion_set_match_func (entry_completion, (GtkEntryCompletionMatchFunc) entry_completion_func, NULL, NULL);
    gtk_entry_set_completion (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), entry_completion);
    gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[ICON_PATH_ENTRY], 5, 0, 1, 1);

    for (entry_fields_cnt = MENU_ID_ENTRY; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_fields_cnt) {
        ks.entry_labels[entry_fields_cnt] = gtk_label_new ((entry_fields_cnt == MENU_ID_ENTRY) ? _(" Menu ID: ") : NULL);
        gtk_widget_set_halign (ks.entry_labels[entry_fields_cnt], GTK_ALIGN_START);

        gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_labels[entry_fields_cnt], 0, entry_fields_cnt - 1, 1, 1);

        ks.entry_fields[entry_fields_cnt] = gtk_entry_new ();
        if (entry_fields_cnt == MENU_ID_ENTRY) {
            menu_id_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
            gtk_grid_attach (GTK_GRID (ks.entry_grid), menu_id_box, 1, entry_fields_cnt - 1, 5, 1);
            gtk_box_pack_start (GTK_BOX (menu_id_box), ks.entry_fields[MENU_ID_ENTRY], TRUE, TRUE, 0);
            ks.double_menu_id_label = gtk_label_new (NULL);
            gtk_label_set_markup (GTK_LABEL (ks.double_menu_id_label), ks.double_menu_id_label_txt->str);
            gtk_container_add (GTK_CONTAINER (menu_id_box), ks.double_menu_id_label);
        }
        else {
            ks.execute_entry_css_provider = gtk_css_provider_new ();
            gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[entry_fields_cnt]), 
                                            GTK_STYLE_PROVIDER (ks.execute_entry_css_provider), 
                                                                GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
                                                                
            gtk_grid_attach (GTK_GRID (ks.entry_grid), ks.entry_fields[EXECUTE_ENTRY], 1, entry_fields_cnt - 1, 5, 1);
        }
        entry_completion = gtk_entry_completion_new ();
        gtk_entry_completion_set_model (entry_completion, GTK_TREE_MODEL (ks.liststore));
        gtk_entry_completion_set_text_column (entry_completion, 0);
        gtk_entry_completion_set_match_func (entry_completion, (GtkEntryCompletionMatchFunc) entry_completion_func, NULL, NULL);
        gtk_entry_set_completion (GTK_ENTRY (ks.entry_fields[entry_fields_cnt]), entry_completion);
    }

    ks.menu_id_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.entry_fields[MENU_ID_ENTRY]), 
                                    GTK_STYLE_PROVIDER (ks.menu_id_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);


    // ### Statusbar ###


    ks.statusbar = gtk_statusbar_new ();
    gtk_container_add (GTK_CONTAINER (main_box), ks.statusbar);
    g_object_set (ks.statusbar, "margin", 0, NULL);


    // ### Signals not related to the application menu ###


    g_signal_connect (ks.window, "delete-event", G_CALLBACK (quit_program), NULL);

    ks.handler_id_row_selected = g_signal_connect (selection, "changed", G_CALLBACK (row_selected), NULL);
    g_signal_connect (ks.treeview, "row-expanded", G_CALLBACK (set_status_of_expand_and_collapse_buttons_and_menu_items),
                      NULL);
    g_signal_connect (ks.treeview, "row-collapsed", G_CALLBACK (set_status_of_expand_and_collapse_buttons_and_menu_items), 
                      NULL);

    g_signal_connect (ks.treeview, "button-press-event", G_CALLBACK (mouse_pressed), NULL);
    g_signal_connect (ks.treeview, "button-release-event", G_CALLBACK (mouse_released), NULL);

    g_signal_connect (ks.treeview, "key-press-event", G_CALLBACK (key_pressed), NULL);

    g_signal_connect (ks.treeview, "drag-motion", G_CALLBACK (drag_motion_handler), NULL);
    g_signal_connect (ks.treeview, "drag-leave", G_CALLBACK (drag_leave_handler), NULL);
    g_signal_connect (ks.treeview, "drag-drop", G_CALLBACK (drag_drop_handler), NULL);

    g_signal_connect (ks.tb_buttons[TB_BUTTON_NEW], "clicked", G_CALLBACK (new_menu), NULL);
    g_signal_connect (ks.tb_buttons[TB_BUTTON_OPEN], "clicked", G_CALLBACK (open_menu), NULL);
    g_signal_connect_swapped (ks.tb_buttons[TB_BUTTON_SAVE], "clicked", G_CALLBACK (save_menu), NULL);
    g_signal_connect (ks.tb_buttons[TB_BUTTON_SAVE_AS], "clicked", G_CALLBACK (save_menu_as), NULL);
    g_signal_connect_swapped (ks.tb_buttons[TB_BUTTON_UNDO], "clicked", G_CALLBACK (undo_redo_restore), "undo");
    g_signal_connect_swapped (ks.tb_buttons[TB_BUTTON_REDO], "clicked", G_CALLBACK (undo_redo_restore), "redo");
    g_signal_connect (ks.tb_buttons[TB_BUTTON_FIND], "clicked", G_CALLBACK (show_or_hide_find_grid), NULL);
    g_signal_connect_swapped (ks.tb_buttons[TB_BUTTON_EXPAND_ALL_NODES], "clicked", G_CALLBACK (expand_or_collapse_all), 
                              GUINT_TO_POINTER (TRUE));
    g_signal_connect_swapped (ks.tb_buttons[TB_BUTTON_COLLAPSE_ALL_NODES], "clicked", G_CALLBACK (expand_or_collapse_all), 
                              GUINT_TO_POINTER (FALSE));
    if (!ks.settings.use_header_bar) {
        g_signal_connect (ks.tb_button_quit, "clicked", G_CALLBACK (quit_program), NULL);
    }

    for (buttons_cnt = 0; buttons_cnt < ACTION_OR_OPTION; ++buttons_cnt) {
        g_signal_connect_swapped (ks.bt_add[buttons_cnt], "clicked", G_CALLBACK (add_new), (gpointer) add_txts[buttons_cnt]);
    }
    g_signal_connect_swapped (ks.bt_remove, "clicked", G_CALLBACK (remove_rows), "button bar");
    FOREACH_IN_ARRAY (ks.bt_movements, buttons_cnt) {
        g_signal_connect_swapped (ks.bt_movements[buttons_cnt], "clicked", G_CALLBACK (move_selection), 
                                  GUINT_TO_POINTER (buttons_cnt));
    }    

    ks.handler_id_including_action_check_button = g_signal_connect_swapped (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON], 
                                                                            "clicked", 
                                                                            G_CALLBACK (one_of_the_enter_values_buttons_pressed), 
                                                                            "incl. action");
    g_signal_connect_swapped (enter_values_buttons[ENTER_VALUES_RESET], "clicked", 
                              G_CALLBACK (one_of_the_enter_values_buttons_pressed), "reset");
    g_signal_connect_swapped (enter_values_buttons[ENTER_VALUES_DONE], "clicked", 
                              G_CALLBACK (one_of_the_enter_values_buttons_pressed), "done");
    // This "simulates" a click on another row.
    g_signal_connect (enter_values_buttons[ENTER_VALUES_CANCEL], "clicked", G_CALLBACK (row_selected), NULL);

    g_signal_connect_swapped (ks.new_action_option_widgets[ACTION_OPTION_DONE], "clicked", 
                              G_CALLBACK (action_option_insert), "by combo box");
    g_signal_connect_swapped (ks.new_action_option_widgets[ACTION_OPTION_CANCEL], "clicked", 
                              G_CALLBACK (hide_action_option_grid), "cancel button");
    ks.handler_id_show_or_hide_startupnotify_options = g_signal_connect (ks.options_fields[SN_OR_PROMPT], "toggled", 
                                                                         G_CALLBACK (show_or_hide_startupnotify_options), NULL);

    g_signal_connect (ks.find_entry_buttons[CLOSE], "clicked", G_CALLBACK (show_or_hide_find_grid), NULL);
    for (buttons_cnt = BACK; buttons_cnt <= FORWARD; ++buttons_cnt) {
        g_signal_connect_swapped (ks.find_entry_buttons[buttons_cnt], "clicked", 
                                  G_CALLBACK (jump_to_previous_or_next_occurrence),
                                  GUINT_TO_POINTER ((buttons_cnt == FORWARD)));
    }
    g_signal_connect (ks.find_entry, "activate", G_CALLBACK (run_search), NULL);
    for (columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS - 1; ++columns_cnt) {
        ks.handler_id_find_in_columns[columns_cnt] = g_signal_connect_swapped (ks.find_in_columns[columns_cnt], "clicked", 
                                                                               G_CALLBACK (find_buttons_management), "specif");
    }
    g_signal_connect_swapped (ks.find_in_all_columns, "clicked", G_CALLBACK (find_buttons_management), "all");
    for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_SPECIAL_OPTIONS; ++buttons_cnt) {
        g_signal_connect_swapped (ks.special_options[buttons_cnt], "clicked", G_CALLBACK (find_buttons_management), NULL);
    }

    for (entry_fields_cnt = 0; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_fields_cnt) {
        ks.handler_id_entry_fields[entry_fields_cnt] = g_signal_connect_swapped (ks.entry_fields[entry_fields_cnt], 
                                                                                 "activate", 
                                                                                 G_CALLBACK (change_row), 
                                                                                 GUINT_TO_POINTER (entry_fields_cnt));

        guint8 treestore_pos;

        switch (entry_fields_cnt) {
            case MENU_ELEMENT_OR_VALUE_ENTRY:
                treestore_pos = TS_MENU_ELEMENT;
                break;
            case ICON_PATH_ENTRY:
                treestore_pos = TS_ICON_PATH;
                break;
            case MENU_ID_ENTRY:
                treestore_pos = TS_MENU_ID;
                break;
            default: // EXECUTE_ENTRY
                treestore_pos = TS_EXECUTE;
        }

        if (entry_fields_cnt == MENU_ELEMENT_OR_VALUE_ENTRY) {
            ks.handler_id_entry_field_changed = g_signal_connect_swapped (ks.entry_fields[entry_fields_cnt], "changed", 
                                                                          G_CALLBACK (build_entry_liststore), GUINT_TO_POINTER (treestore_pos));
        }
        else {
            g_signal_connect_swapped (ks.entry_fields[entry_fields_cnt], "changed", 
                                      G_CALLBACK (build_entry_liststore), GUINT_TO_POINTER (treestore_pos));
        }
        g_signal_connect_swapped (ks.entry_fields[entry_fields_cnt], "focus-out-event", G_CALLBACK (focus_no_longer_on_entry_field), NULL);
    }

    g_signal_connect (ks.icon_chooser, "clicked", G_CALLBACK (icon_choosing_by_button_or_context_menu), NULL);
    g_signal_connect (ks.remove_icon, "clicked", G_CALLBACK (remove_icons_from_menus_or_items), GUINT_TO_POINTER (TRUE));

    g_signal_connect (ks.options_fields[PROMPT], "activate", G_CALLBACK (single_field_entry), NULL);
    g_signal_connect (ks.options_fields[COMMAND], "activate", G_CALLBACK (single_field_entry), NULL);
    for (snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
        g_signal_connect (ks.suboptions_fields[snotify_opts_cnt], "activate", G_CALLBACK (single_field_entry), NULL);
    }


    // ### Default settings ###


    g_object_get (gtk_settings_get_default (), "gtk-font-name", &ks.font_desc, NULL);
    ks.font_size = get_font_size (); // Get the default font size ...
    g_object_get (gtk_settings_get_default (), "gtk-icon-theme-name", &ks.icon_theme_name, NULL); // ... and the icon theme name.
    create_invalid_icon_imgs (); // Create broken/invalid path icon images suitable for that font size.
    ks.search_term = g_string_new (NULL);
    // Has to be placed here because the signal to deactivate all other column check boxes has to be triggered.
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_in_all_columns), TRUE); // Deactivate user defined column search.
    ks.cm_css_provider = gtk_css_provider_new ();

    gtk_widget_show_all (ks.window);

    /*
        The height of the message label is set to be identical to the one of the buttons, so the button grid doesn't 
        shrink if the buttons are missing. This can only be done after all widgets have already been added to the grid, 
        because only then the height of the button grid (with buttons shown) can be determined correctly.
    */
    gtk_widget_set_size_request (ks.bt_bar_label, -1, gtk_widget_get_allocated_height (ks.bt_add[0]));
    // Set the minimum size for button_add_grid, because it should not shrink when ks.bt_add[ACTION_OR_OPTION] is not shown.
    gtk_widget_set_size_request (button_add_grid, gtk_widget_get_allocated_width (button_add_grid), -1);

    gtk_window_resize (GTK_WINDOW (ks.window), 700, MIN (gtk_widget_get_allocated_height (ks.window), 525));
    gtk_window_set_gravity (GTK_WINDOW (ks.window), GDK_GRAVITY_CENTER);
    gtk_window_move (GTK_WINDOW (ks.window), 0, 0);

    /*
        By calling row_selected (), the following widgets are hidden by this function:
        * ks.bt_add[ACTION_OR_OPTION]
        * ks.enter_values_label
        * ks.mandatory
        * ks.separator
        * ks.enter_values_buttons_grid
        * ks.new_action_option_widgets[INSIDE_MENU_LABEL]
        * ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]
        * ks.new_action_option_widgets[INCLUDING_ACTION_LABEL]
        * ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]

        This is done before the settings file is loaded. If there is an issue with the settings file, 
        a dialog window will be shown and the elements above are supposed to be already hidden.        
    */
    row_selected ();

    /*
        ks.find_grid is not hidden by row_selected (). It would be hidden later if there is a menu file, 
        but since that might not be the case, for simplicity it is done here anyway.
    */
    gtk_widget_hide (ks.find_grid);


    // ### Parse settings file. ###


    // sf - settings file
    ks.settings_file_actions = "sf-showmenuidcol|sf-showexecutecol|sf-showicons|sf-highlightsep|sf-showtreelines|"
                               "sf-elmviscol|sf-highlightingstate|sf-gridstate|sf-createbackup|sf-usetabs|sf-separaterootmenu|"
                               "sf-sortoptions|sf-notifyaboutconversions|sf-useheaderbar|sf-showmenubutton";

    if (!ks.settings.show_menu_button) {
        ks.settings_menu_items = g_ptr_array_new ();

        add_elements_to_ptr_array (ks.settings_menu_items, 
                                   ks.MBar_view_menu_items[M_SHOW_MENU_ID_COL], 
                                   ks.MBar_view_menu_items[M_SHOW_EXECUTE_COL], 
                                   ks.MBar_view_menu_items[M_SHOW_ICONS], 
                                   ks.MBar_view_menu_items[M_HIGHLIGHT_SEPARATORS], 
                                   ks.MBar_view_menu_items[M_SHOW_TREE_LINES], 
                                   ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD], 
                                   ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], 
                                   // The following element is a filler, because NULL can't be used since it acts as a delimiter.
                                   ks.MBar_view_menu_visibility_items[M_NO_GRID_LINES], 
                                   ks.MBar_options_menu_items[M_CREATE_BACKUP_BEFORE_OVERWRITING_MENU], 
                                   ks.MBar_options_menu_items[M_USE_TABS_FOR_INDENTATIONS], 
                                   ks.MBar_options_menu_items[M_KEEP_ROOT_MENU_SEPARATE], 
                                   ks.MBar_options_menu_items[M_SORT_EXECUTE_AND_STARTUPN_OPTIONS], 
                                   ks.MBar_options_menu_items[M_NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS], 
                                   ks.MBar_options_menu_items[M_USE_HEADER_BAR], 
                                   ks.MBar_options_menu_items[M_SHOW_MENU_BUTTON], 
                                   NULL);
    }

    if (G_LIKELY (settings_file_status == PARSABLE)) {
        const gchar *group = "VIEW"; // Start with this group.
        gboolean visibility_column_shown = FALSE; // Default

        g_auto(GStrv) settings_file_actions = g_strsplit (ks.settings_file_actions, "|", -1);
        guint8 action_idx = 0;

        while (settings_file_actions[action_idx]) {
            gchar *action_str = settings_file_actions[action_idx];
            gboolean settings_file_has_wrong_value = FALSE; // Default
            g_autoptr(GError) settings_file_error = NULL;

            if (STREQ (action_str, "sf-createbackup")) {
                group = "OPTIONS";
            }

            g_autofree gchar *key_str = g_key_file_get_string (settings_key_file, group, action_str, &settings_file_error);

            if ((!STREQ (action_str, "sf-gridstate") && 
                !streq_any (key_str, "true", "false", NULL)) || 
               (STREQ (action_str, "sf-gridstate") && 
                !streq_any (key_str, "no_grid_lines", "vertical", "horizontal", "both", NULL))) {
                settings_file_has_wrong_value = TRUE;
            }

            if (G_UNLIKELY (settings_file_error || settings_file_has_wrong_value)) {
                g_autofree gchar *dialog_txt = g_strdup_printf (_("<b>Unable to parse the key</b> <tt>%s</tt>.\n"
                                                                  "<b><span foreground='%s'>Error: %s%s%s%s</span></b>\n\n"
                                                                  "The program will now try to reset the settings file to "
                                                                  "its default state. If no further error message regarding this "
                                                                  "key is shown, the writing was successful."), 
                                                                action_str, ks.red_hue, 
                                                                (settings_file_error) ? settings_file_error->message : _("Wrong value "), 
                                                                (settings_file_error) ? "" : "\"", 
                                                                (settings_file_error) ? "" : key_str, 
                                                                (settings_file_error) ? "" : "\"");
                g_autoptr(GError) error = NULL;

                show_errmsg (dialog_txt);

                write_settings (CHANGE_STATES_TO_DEFAULT);

                g_application_quit (G_APPLICATION (ks.app));
                g_spawn_command_line_async (installed_or_local (), &error);

                if (G_UNLIKELY (error)) {
                    g_printerr ("%s\n", error->message);
                }

                exit (EXIT_FAILURE);
            }

            if (ks.settings.show_menu_button) {
                GVariant *new_variant = NULL; // Default

                if (STREQ (action_str, "sf-gridstate")) {
                    new_variant = g_variant_new_string (key_str);
                }

                if (STREQ (action_str, "sf-elmviscol") && STREQ (key_str, "true")) {
                    visibility_column_shown = TRUE;
                }

                if (!(STREQ (action_str, "sf-highlightingstate") && 
                      !visibility_column_shown)) {
                    /* If action_str != "sf-gridstate", new_variant is NULL (default value)
                       and ignored by change_view_and_options ().
                       If new_variant != NULL, key_str is ignored by change_view_and_options (). */
                    change_view_and_options_GMenu (G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (ks.app), action_str)), 
                                                   new_variant, key_str);
                }
            }
            else {
                if (!STREQ (action_str, "sf-gridstate")) {
                    GtkWidget *menu_item = g_ptr_array_index (ks.settings_menu_items, action_idx);

                    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), STREQ (key_str, "true"));
                }
                else {
                    FOREACH_IN_ARRAY (ks.grid_submenu_item_txts, menu_items_cnt) {
                        if (STREQ (key_str, ks.grid_states[menu_items_cnt])) {
                            gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_grid_items[menu_items_cnt]), TRUE);
                            break;
                        }
                    }
                }
            }

            ++action_idx;
        }
    }
    else {
        write_settings (CHANGE_STATES_TO_DEFAULT);

        if (settings_file_status == CHANGE_IN_SETTINGS_FILE_SETUP) {
            show_message_dialog (_("Settings Reset Due to Change in Settings Setup"), "gtk-info", 
                                 _("Due to a change in the setup of the settings the settings file has been rewritten "
                                   "and the settings have been reset to the defaults. If so desired, "
                                   "\"View\" and \"Options\" can be used in the application menu to readjust the settings."), 
								 // Translation note: the verb "Close"
                                 _("_Close"));
        }
    }


    /* ### Create a new tmp folder used for undo/redo,                                                                         ###
       ### or retrieve an existing one if the program has been restarted or started again because of an irregular termination. ### */                          


    g_autofree gchar *symlink_tmp_folder_path = g_build_filename (g_get_tmp_dir (), "kickshaw_tmp_folder_symlink", NULL);
    ks.symlink_tmp_folder = g_file_new_for_path (symlink_tmp_folder_path);
    gboolean symlink_tmp_folder_exists = FALSE; // Default

    if (!(g_file_query_exists (ks.symlink_tmp_folder, NULL))) {
        tmp_path = g_strconcat (g_get_tmp_dir (), tmp_dir_template, NULL);

        if (G_UNLIKELY (!(ks.tmp_path = g_mkdtemp (tmp_path)))) {
            show_errmsg (_("Could not create temporary undo/redo folder! Program is aborted."));

            exit (EXIT_FAILURE);
        }
    }
    else {
        symlink_tmp_folder_exists = TRUE;
        g_autoptr(GError) error = NULL;
        g_autoptr(GFileInfo) file_info = g_file_query_info (ks.symlink_tmp_folder, 
                                                            G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, G_FILE_QUERY_INFO_NONE, 
                                                            NULL, &error);

        if (G_UNLIKELY (error)) {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not retrieve file information for the symbolic link</b> <tt>%s</tt> <b>!\n"
                                                         "Error:</b> %s"), symlink_tmp_folder_path, error->message);

            show_errmsg (err_msg);

            exit (EXIT_FAILURE);
        }

        ks.tmp_path = g_strdup (g_file_info_get_symlink_target (file_info)); // g_file_info_get_symlink_target () returns a const gchar *
    }


    /* ### Check if there is a symbolic link to the Kickshaw tmp folder present.   ###
       ### If a restart indicator file is present, the program has been restarted. ### 
       ### Other than that, the program has likely been irregularly terminated.    ### */


    g_autofree gchar *autosave_path = g_build_filename (ks.home_dir, ".kickshaw_autosave", NULL);
    g_autoptr(GFile) autosave = g_file_new_for_path (autosave_path);
    const gboolean autosave_exists = g_file_query_exists (autosave, NULL);
    gboolean autosave_restored = FALSE; // Default;

    if (symlink_tmp_folder_exists || autosave_exists) {
        g_autofree gchar *restart_indicator_path = g_build_filename (ks.tmp_path, "kickshaw_restart_indicator", NULL);
        g_autoptr(GFile) restart_indicator = g_file_new_for_path (restart_indicator_path);

        if (g_file_query_exists (restart_indicator, NULL)) { // Program restarted
            undo_redo_restore ("restore");
            autosave_restored = TRUE;
 
            g_autoptr(GError) error = NULL;

            if (G_UNLIKELY (!(g_file_delete (restart_indicator, NULL, &error)))) {
                g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not delete restart indicator file</b> <tt>%s</tt> <b>!\n"
                                                               "Error:</b> %s"), restart_indicator_path, error->message);

                show_errmsg (err_msg);

                exit (EXIT_FAILURE);
            }
        }
        else if (G_UNLIKELY (autosave_exists)) { // Irregular program termination
            GtkWidget *dialog;

            gint result;

            enum { RESTORE_AUTOSAVE = 1, DELETE_AUTOSAVE };

            g_autoptr(GString) dialog_txt = g_string_new (_("There is an autosave present, "
                                                            "presumably because Kickshaw has been terminated irregularly. "
                                                            "<b>Restore the menu from the autosave or delete the autosave</b>?\n"));

            g_string_append (dialog_txt, (symlink_tmp_folder_exists) ? 
                                         _("The editing history (for undo/redo) is still accessible and will be restored as well.") :
                                         _("The editing history (for undo/redo) is no longer accessible and therefore can't be reconstructed. "
                                           "It is stored inside a folder of a temporary directory (e.g. /tmp) "
                                           "and has probably been removed by the system in the meantime."));

            create_dialog (&dialog, 
			               // "Autosave present" means "An autosave exists" here
			               _("Autosave Present"), "dialog-question", dialog_txt->str, 
                           _("_Restore Menu"), _("_Delete Autosave"), NULL, SHOW_IMMEDIATELY);

            result = gtk_dialog_run (GTK_DIALOG (dialog));
            gtk_widget_destroy (dialog);
            switch (result) {
                case RESTORE_AUTOSAVE:
                    undo_redo_restore ("restore");
                    autosave_restored = TRUE;

                    break;
                case DELETE_AUTOSAVE:
                case GTK_RESPONSE_DELETE_EVENT:
                    delete_autosave ();
            }          
        }
    }


    // ### If no autosave has been restored, load menu, if exists. ###


    if (G_LIKELY (!autosave_restored)) {
        g_autoptr(GFile) menu_file;

        if (G_LIKELY (!argument)) {
            gchar *new_filename = g_build_filename (ks.home_dir, ".config/openbox/menu.xml", NULL);
            menu_file = g_file_new_for_path (new_filename);
            gboolean not_unsaved_menu_as_label;

            if (G_LIKELY (g_file_query_exists (menu_file, NULL))) {
                not_unsaved_menu_as_label = get_menu_elements_from_file (new_filename);
            }
            else {
                not_unsaved_menu_as_label = FALSE;
 
                // Cleanup
                g_free (new_filename);
            }

            if (!not_unsaved_menu_as_label) {
                if (!ks.settings.use_header_bar) {
                    gtk_window_set_title (GTK_WINDOW (ks.window), _("Unsaved Menu"));
                }
                else {
                    gtk_label_set_label (GTK_LABEL (ks.basename_label), _("Unsaved Menu"));
                    gtk_widget_hide (ks.subtitle_label);
                }
            }
        }
        else {
            menu_file = g_file_new_for_path (argument->str);

            if (G_LIKELY (g_file_query_exists (menu_file, NULL))) {
                get_menu_elements_from_file (g_strdup (argument->str));
            }
            else {
                set_filename_and_window_title (g_strdup (argument->str));
                ks.last_save_pos = 1;
            }

            // Cleanup
            g_string_free (argument, TRUE);
        }
    }


    /* ### Either create a first undo stack item if the program is not restarted or has been irregularly terminated or ###
       ### restore the stack pointers with the help of the existing symlinks                                           ### */


    // Not a restart of the program or the tmp folder is no longer present that could be used by the program after an irregular termination
    if (!symlink_tmp_folder_exists) { 
        // Push original state on the stack. This undo item is never removed during run time, unless a new menu is created or loaded.
        push_new_item_on_undo_stack (NOT_AFTER_SAVE);

        g_autoptr(GError) error = NULL;

        if (G_UNLIKELY (!(g_file_make_symbolic_link (ks.symlink_tmp_folder, ks.tmp_path, NULL, &error)))) {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not create symbolic link to tmp folder</b> <tt>%s</tt> <b>!\n"
                                                           "Error:</b> %s"), ks.tmp_path, error->message);

            show_errmsg (err_msg);

            exit (EXIT_FAILURE);
        }
    }
    else {
        g_autoptr(GError) error = NULL;
        g_autofree gchar *symlink_pos_inside_undo_stack_path = g_build_filename (ks.tmp_path, "pos_inside_undo_stack_symlink", NULL);
        g_autoptr(GFile) symlink_pos_inside_undo_stack = g_file_new_for_path (symlink_pos_inside_undo_stack_path);
        g_autoptr(GFileInfo) file_info = g_file_query_info (symlink_pos_inside_undo_stack, 
                                                            G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, G_FILE_QUERY_INFO_NONE, 
                                                            NULL, &error);

        if (G_UNLIKELY (error)) {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not retrieve file information for the symbolic link</b> <tt>%s</tt> <b>!\n"
                                                           "Error:</b> %s"), symlink_pos_inside_undo_stack_path, error->message);

            show_errmsg (err_msg);

            exit (EXIT_FAILURE);
        }

        const      gchar *undo_file_path = g_file_info_get_symlink_target (file_info);
        g_autofree gchar *undo_file_str  = extract_substring_via_regex (undo_file_path, "[0-9]*$");

        ks.pos_inside_undo_stack = atoi (undo_file_str);
        ks.number_of_undo_stack_items = ks.pos_inside_undo_stack;

        while (TRUE) {
            g_autofree gchar *number_of_undo_stack_items_str = g_strdup_printf ("%i", ks.number_of_undo_stack_items);
            g_autofree gchar *last_undo_file_path = g_build_filename (ks.tmp_path, number_of_undo_stack_items_str, NULL);
            g_autoptr(GFile) last_undo_file = g_file_new_for_path (last_undo_file_path);

            if (!(g_file_query_exists (last_undo_file, NULL))) {
                --ks.number_of_undo_stack_items;

                break;
            }

            ++ks.number_of_undo_stack_items;
        }
    }

    /*
        With this second call of row_selected (), 
        the visibility of MBut_file_menu_items[M_NEW]/MBar_file_menu_items[M_NEW] and 
        sensitivity of tb_buttons[TB_NEW] are also set, depending on whether a menu file has been loaded or not.

        Other than the previous call of row_selected (), this has naturally to be done after the 
        loading procedure of the menu file.

        If there is a settings file, row_selected () has already been called from inside change_view_and_options ().
    */
    row_selected ();

    // If a menu has been loaded, the timeout is already active.
    if (!ks.timeout_id) {
        ks.timeout_id = g_timeout_add_seconds (1, (GSourceFunc) check_for_external_file_and_settings_changes, "timeout");
    }

    gtk_widget_grab_focus (ks.treeview);
}

/*

    Create and add simple actions for the popup menu.

*/

static void create_and_add_simple_actions (      GSimpleAction **simple_actions, 
                                           const gchar         **simple_action_txts, 
                                           const gchar         **menu_item_accelerators, 
                                           const guint8          number_of_menu_items)
{
    for (guint8 menu_items_cnt = 0; menu_items_cnt < number_of_menu_items; ++menu_items_cnt) {
        if (!(*simple_action_txts[menu_items_cnt])) {
            continue;
        }

#if !(GTK_CHECK_VERSION(3,20,0))
        if (STREQ (simple_action_txts[menu_items_cnt], "shortcuts")) {
            continue;
        }
#endif

        if (strstr (simple_action_txts[menu_items_cnt], "sf-")) {
            simple_actions[menu_items_cnt] = g_simple_action_new_stateful (simple_action_txts[menu_items_cnt], 
                                                                           NULL, g_variant_new_boolean (FALSE));
        }
        else {
            simple_actions[menu_items_cnt] = g_simple_action_new (simple_action_txts[menu_items_cnt], NULL);
        }

        if (*menu_item_accelerators[menu_items_cnt]) {
            g_autofree gchar *full_action_str = g_strdup_printf ("app.%s", simple_action_txts[menu_items_cnt]);

            gtk_application_set_accels_for_action (ks.app, full_action_str, 
                                                   (const gchar*[]) { _(menu_item_accelerators[menu_items_cnt]), NULL });
        }

        g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (simple_actions[menu_items_cnt]));
    }
}

/*

    Add menu items to the popup menu.

*/

static void add_popup_menu_items (      GMenu      *menu, 
                                        GMenuItem **menu_items, 
                                  const gchar     **menu_item_txts, 
                                  const gchar     **simple_action_txts, 
                                  const gchar     **menu_item_accelerators, 
                                  const guint8     *section_starts, 
                                  const guint8     *section_ends, 
                                  const guint8      number_of_menu_items, 
                                  const guint8      number_of_section_elements)
{
    GMenu *section = NULL; // Initialization avoids compiler warning

    for (guint8 menu_items_cnt = 0; menu_items_cnt < number_of_menu_items; ++menu_items_cnt) {
        guint8 section_idx_cnt;

        for (section_idx_cnt = 0; section_idx_cnt < number_of_section_elements; ++section_idx_cnt) {
            if (section_starts[section_idx_cnt] == menu_items_cnt) {
                section = g_menu_new ();
                break;
            }
        }

#if !(GTK_CHECK_VERSION(3,20,0))
        if (STREQ (menu_item_txts[menu_items_cnt], _("Shortcuts"))) {
            continue;
        }
#endif

        menu_items[menu_items_cnt] = g_menu_item_new (_(menu_item_txts[menu_items_cnt]), NULL);

        g_autofree gchar *action_str = g_strconcat ("app.", simple_action_txts[menu_items_cnt], NULL);

        g_menu_item_set_attribute (menu_items[menu_items_cnt], "action", "s", action_str);

        if (*menu_item_accelerators[menu_items_cnt]) {            
            g_menu_item_set_attribute (menu_items[menu_items_cnt], "accel", "s", 
                                       _(menu_item_accelerators[menu_items_cnt]));
        }

        g_menu_append_item (section, menu_items[menu_items_cnt]);

        for (section_idx_cnt = 0; section_idx_cnt < number_of_section_elements; ++section_idx_cnt) {
            if (section_ends[section_idx_cnt] == menu_items_cnt) {
                g_menu_append_section (menu, NULL, G_MENU_MODEL (section));

                // Cleanup
                g_object_unref (section);
            }
        }
    }
}

/* 

    Creates a label for an "Add new" button, adds it to a grid with additional margin, which is added to the button.

*/

static void add_button_content (      GtkWidget *button, 
                                const gchar     *label_text)
{
    GtkWidget *grid = gtk_grid_new ();
    GtkWidget *label = (*label_text) ? gtk_label_new_with_mnemonic (label_text) : ks.bt_add_action_option_label;

    gtk_container_set_border_width (GTK_CONTAINER (grid), 2);
    gtk_container_add (GTK_CONTAINER (grid), label);
    gtk_container_add (GTK_CONTAINER (button), grid);
}

/*

    Closes a window after the activation of a shortcut or pressing escape.

*/

gboolean close_window (GtkWidget   *window, 
                       GdkEventKey *event_key, 
                       gpointer     keys)
{
    const gchar *key_str[] = { "F1", "<ctrl>H", "<ctrl>L", "ESC" };
    const guint keyvals[] = { GDK_KEY_F1, GDK_KEY_h, GDK_KEY_l, GDK_KEY_Escape };
    gboolean window_closed = FALSE; // Default

    guint8 key_idx;

    g_auto(GStrv) tokens = g_strsplit (keys, "|", -1);

    for (guint8 tokens_cnt = 0; tokens_cnt < g_strv_length (tokens); ++tokens_cnt) {
        FOREACH_IN_ARRAY (key_str, key_idx) {
            if (STREQ (tokens[tokens_cnt], key_str[key_idx])) {
                break;
            }
        }

        if ((strstr (tokens[tokens_cnt], "<ctrl>") && 
            (event_key->state & (GDK_CONTROL_MASK)) && event_key->keyval == keyvals[key_idx]) || 
            (streq_any (tokens[tokens_cnt], "F1", "ESC", NULL) && event_key->keyval == keyvals[key_idx])) {
            gtk_widget_destroy (GTK_WIDGET (window));   
            window_closed = TRUE;

            break;
        }
    }

    return (window_closed); // TRUE = Return without propagating the event further.
}

/*

    Creates, sets and returns a PangoTabArray. Returned tab array must be freed after use.

*/

PangoTabArray *create_and_set_tab_array (void)
{
    PangoTabArray *tab_array = pango_tab_array_new (10, FALSE);
    const gchar *space = " ";
    g_autoptr(PangoFontDescription) font_description = pango_font_description_new ();
    // Only the layout for the first text view is needed, as the others are equivalent.
    g_autoptr(PangoLayout) pango_layout = gtk_widget_create_pango_layout (ks.text_views[MENU_FILE_HINTS], space);
    gint pango_size_width;

    pango_font_description_set_family (font_description, "monospace");
    pango_layout_set_font_description (pango_layout, font_description);
    pango_layout_get_size (pango_layout, &pango_size_width, NULL);

    // Setting the first ten tab positions should be enough.
    for (guint8 tabs_cnt = 0; tabs_cnt < 10; ++tabs_cnt) {
        pango_tab_array_set_tab (tab_array, tabs_cnt, PANGO_TAB_LEFT, tabs_cnt * pango_size_width * 4);
    }

    return tab_array;
}

/*

    Shows a window with the hints for the usage of this program.

*/

static void show_hints_window (void)
{
    GtkWidget *notebook;
    const gchar *label_txts[] = { _("The Menu _File"), 
                                  // Translation note: The translation should use the infinitive. Don't use the gerund or imperative. In case of absence of an infinitive in the target language, use the closest equivalent form.
                                  _("_Working With Kickshaw"), 
                                  _("_Miscellaneous") };
    g_autoptr(GResource) resources = resources_get_resource ();

    ks.hints_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    gtk_window_set_modal (GTK_WINDOW (ks.hints_window), TRUE);
    gtk_window_set_resizable (GTK_WINDOW (ks.hints_window), TRUE);
    gtk_window_set_position (GTK_WINDOW (ks.hints_window), GTK_WIN_POS_CENTER);
    gtk_window_set_title (GTK_WINDOW (ks.hints_window), _("Hints"));

    gtk_window_set_transient_for (GTK_WINDOW (ks.hints_window), GTK_WINDOW (ks.window));

    notebook = gtk_notebook_new ();

    gtk_container_add (GTK_CONTAINER (ks.hints_window), notebook);

    g_resources_register (resources);

    for (guint8 hint_txts_cnt = 0; hint_txts_cnt < NUMBER_OF_HINT_TXTS; ++hint_txts_cnt) {
        GtkWidget *scrolled_window;
        GtkWidget *text_view_label;
        GtkTextBuffer *text_buffer;
        GtkTextIter text_iter;
        g_autoptr(PangoTabArray) tab_array;
#if GTK_CHECK_VERSION(3,20,0)
        g_autofree gchar *hints_resource_path = g_strdup_printf ("/org/kickshaw/txts/hints/%s/hints%i_%s", ks.locale, hint_txts_cnt + 1, ks.locale);
#else
        g_autofree gchar *hints_resource_path = g_strdup_printf ("/org/kickshaw/txts/hints/%s/hints%i%s_%s", ks.locale, hint_txts_cnt + 1, 
                                                                 (hint_txts_cnt != 1) ? "" : "_pre_gtk3_20", ks.locale);
#endif
        g_autofree gchar *hints_txt = get_txt_from_resources (resources, hints_resource_path);

        ks.text_views[hint_txts_cnt] = gtk_text_view_new ();

        tab_array = create_and_set_tab_array ();
        gtk_text_view_set_tabs (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), tab_array);

        gtk_text_view_set_left_margin (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), 5);
        gtk_text_view_set_right_margin (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), 5);
        gtk_text_view_set_top_margin (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), 3);
        gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), 5);

        gtk_text_view_set_editable (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), FALSE);
        gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), FALSE);
        gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]), GTK_WRAP_WORD);

        text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ks.text_views[hint_txts_cnt]));
        gtk_text_buffer_get_start_iter (text_buffer, &text_iter);
        gtk_text_buffer_insert_markup (text_buffer, &text_iter, hints_txt, -1);

        scrolled_window = gtk_scrolled_window_new (NULL, NULL);
        gtk_widget_set_size_request (scrolled_window, 650, 400);
        gtk_container_add (GTK_CONTAINER (scrolled_window), ks.text_views[hint_txts_cnt]);

        text_view_label = gtk_label_new (NULL);
        gtk_label_set_markup_with_mnemonic (GTK_LABEL (text_view_label), _(label_txts[hint_txts_cnt]));

        gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled_window, text_view_label);
    }

    g_signal_connect (G_OBJECT (ks.hints_window), "key-press-event", 
                      G_CALLBACK (close_window), "F1|ESC");
    g_signal_connect (ks.hints_window, "destroy", G_CALLBACK (gtk_widget_destroyed), &ks.hints_window);

    gtk_widget_show_all (GTK_WIDGET (ks.hints_window));
}

/*

    Shows a window with the program's shortcuts.

*/

#if GTK_CHECK_VERSION(3,20,0)
static void show_shortcuts_window (void)
{
    GtkShortcutsWindow *shortcuts_window;
    GtkShortcutsSection *shortcuts_section;
    // Initializing shortcuts_group to NULL will avoid a warning from a static analyzer that an uninitialized value is used.
    GtkShortcutsGroup *shortcuts_group = NULL;

    g_autoptr(GResource) resources = resources_get_resource ();
    g_resources_register (resources);

    g_autofree gchar *shortcuts_path = g_strdup_printf ("/org/kickshaw/txts/list_of_shortcuts/%s", ks.locale);
    g_autofree gchar *sc_win_resource_txt = get_txt_from_resources (resources, shortcuts_path);
    g_auto(GStrv) sc_win_elm = g_strsplit (sc_win_resource_txt, "\n", -1);

    guint8 sc_win_elm_cnt; // shortcut_window_element_counter

    // General

    shortcuts_window = g_object_new (GTK_TYPE_SHORTCUTS_WINDOW, "modal", TRUE, 
                                                                "resizable", TRUE, 
                                                                "gravity", GDK_GRAVITY_CENTER, 
                                                                "transient-for", ks.window, 
                                                                NULL);

    shortcuts_section = g_object_new (GTK_TYPE_SHORTCUTS_SECTION, "visible", TRUE, "max-height", 11, NULL);
    /* Manually show()ing at least one section is mandatory, BEFORE adding to the window,
       because otherwise the window opens on search results with no way out.
       GtkShortcutsWindow's show_all() does not suffice, as it excludes sections. */
    gtk_widget_show (GTK_WIDGET (shortcuts_section));

    // Add shortcut groups and shortcuts.

    sc_win_elm_cnt = 0;

    while (sc_win_elm[sc_win_elm_cnt]) {
        if (!(strstr (sc_win_elm[sc_win_elm_cnt], "|"))) { 
            /* Creating a new object for the same variable in a loop does not create a memory leak here, 
               as shortcuts_group is attached to the shortcuts window. When the window gets destroyed, all 
               its child objects will be erased as well. */
            shortcuts_group = g_object_new (GTK_TYPE_SHORTCUTS_GROUP, "title", sc_win_elm[sc_win_elm_cnt], NULL);
            gtk_container_add (GTK_CONTAINER (shortcuts_section), GTK_WIDGET (shortcuts_group));
        }
        else {
            enum { ACCEL, ACCEL_TXT };
            g_auto(GStrv) tokens = g_strsplit (sc_win_elm[sc_win_elm_cnt], " | ", -1);
            g_autofree gchar *compressed_accel_txt_token = g_strcompress (tokens[ACCEL_TXT]); // Replaces \n with the one byte equivalent.

            gtk_container_add (GTK_CONTAINER (shortcuts_group), GTK_WIDGET (g_object_new (GTK_TYPE_SHORTCUTS_SHORTCUT, 
                                                                                          "accelerator", g_strchug (tokens[ACCEL]), 
                                                                                          "title", compressed_accel_txt_token, 
                                                                                          NULL)));
        }

        ++sc_win_elm_cnt;
    }

    // Finalizing

    gtk_container_add (GTK_CONTAINER (shortcuts_window), GTK_WIDGET (shortcuts_section)); 

    g_signal_connect (G_OBJECT (shortcuts_window), "key-press-event", G_CALLBACK (close_window), "<ctrl>L");

    gtk_widget_show_all (GTK_WIDGET (shortcuts_window));
}
#endif

/*

    Opens a browser window with the Submit Item page of the Kickshaw Bug Tracker.    

*/

static void bug_report (void) 
{
    enum { BR_CANCEL = 1, BR_OK };

    GtkWidget *dialog;

    gint result;

    create_dialog (&dialog, _("Report Issue"), "dialog-info", 
                   _("A new browser window will now be opened with the Item Submission Page of the Kickshaw Bug Tracker. "
                     "Reporting an issue requires an account, which can be created on the website."), 
                   C_("Cancel|No File Dialogue", "_Cancel"), _("_OK"), NULL, SHOW_IMMEDIATELY);

    result = gtk_dialog_run (GTK_DIALOG (dialog));

    gtk_widget_destroy (dialog);

    if (result == BR_OK) {
        g_app_info_launch_default_for_uri ("https://savannah.nongnu.org/bugs/?group=obladi&func=additem", NULL, NULL);
    }
}

/*

    If there is a connection to the download server, this function tries to get the version string file and read the version string from it.

*/

void get_latest_version_str_from_server_callback (GObject *source_object, GAsyncResult *result, G_GNUC_UNUSED gpointer user_data)
{
    g_autoptr(GError) error = NULL;

    g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error);

    if (!error) {
        g_autoptr(GFile) latest = g_file_new_for_uri ("http://download.savannah.gnu.org/releases/obladi/latest");
        g_autofree gchar *latest_local_str = g_strdup_printf ("%s/latest", ks.tmp_path);
        g_autoptr(GFile) latest_local = g_file_new_for_path (latest_local_str);

        g_autoptr(GError) error = NULL;

        if (G_LIKELY (g_file_copy (latest, latest_local, G_FILE_COPY_ALL_METADATA, NULL, NULL, NULL, &error))) {
            g_autoptr(GFileInputStream) latest_local_input_stream = NULL;

            if (G_LIKELY ((latest_local_input_stream = g_file_read (latest_local, NULL, &error)))) {
                g_autoptr(GDataInputStream) latest_local_data_input_stream = g_data_input_stream_new (G_INPUT_STREAM (latest_local_input_stream));
                g_autofree gchar *latest_local_check_version;

                if (G_UNLIKELY (!(latest_local_check_version = g_data_input_stream_read_line (latest_local_data_input_stream, 
                                                                                              NULL, NULL, &error)))) {
                    g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not obtain the version from the version check file</b> "
                                                                   "<tt>latest</tt> <b>located inside the tmp folder</b> "
                                                                   "<tt>%s</tt> <b>!\n" 
                                                                   "Error:</b> %s"), ks.tmp_path, error->message);

                    show_errmsg (err_msg);
                }

                if (G_UNLIKELY (!(g_file_delete (latest_local, NULL, &error)))) {
                    g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not remove version check file</b> <tt>latest</tt> "
                                                                   "<b>from tmp folder</b> <tt>%s</tt> <b>!</b>"), ks.tmp_path);

                    show_errmsg (err_msg);
                }

#if __GLIBC__
                if (strverscmp (latest_local_check_version, KICKSHAW_VERSION) > 0) {
#else
                if (strverscmp_nonglibc (latest_local_check_version, KICKSHAW_VERSION) > 0) {
#endif
                    // Translation note: %s will be replaced by a string that represents a version number, e.g. 1.2.26
                    FREE_AND_REASSIGN (ks.version_info, g_strdup_printf (_("%s\nNewer version %s is available."), KICKSHAW_VERSION, latest_local_check_version));                    
                }
                else {
                    FREE_AND_REASSIGN (ks.version_info, g_strdup_printf (_("%s\nThis is the latest version."), KICKSHAW_VERSION));
                }
            }
            else {
                g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not open version check file</b> "
                                                               "<tt>latest</tt> <b>inside</b> <tt>%s</tt> <b>!\n"
                                                               "Error:</b> %s"), ks.tmp_path, error->message);

                show_errmsg (err_msg);
            }
        }
        /*
            Ignore if the version check file hasn't been found.
            The file might have been renamed on the server or the whole version check functionality have been rewritten 
            in the meantime. In this case, the version check is likely to work again with a newer version of Kickshaw.
        */
        else if (!(g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))) {
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not copy version check file</b> "
                                                           "<tt>latest</tt> <b>to</b> <tt>%s</tt> <b>!\n"
                                                           "Error:</b> %s"), ks.tmp_path, error->message); 

            show_errmsg (err_msg);
        }
    }
    else { // No internet connection or download server is down
        FREE_AND_REASSIGN (ks.version_info, g_strdup_printf ("%s\n%s", KICKSHAW_VERSION, _("Check for newer version requires internet connection.")));
    }

    ks.stopped_trying_to_connect_to_server = TRUE;
}

/* 

    Dialog window that shows the program name and version, a short description, the website, author, and license.

*/

static void about (void) 
{
    g_autoptr(GSocketClient) socket_client = g_socket_client_new ();

    ks.version_info = g_strdup (KICKSHAW_VERSION);

    g_socket_client_connect_to_uri_async (socket_client, "http://download.savannah.gnu.org", 80, NULL, (GAsyncReadyCallback) get_latest_version_str_from_server_callback, NULL);

    g_autoptr(GResource) resources = resources_get_resource ();

    g_resources_register (resources);

    g_autoptr(GBytes) data = g_resource_lookup_data (resources, "/org/kickshaw/imgs/logo_about.jpg", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
    g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (data);
    g_autoptr(GdkPixbuf) logo = gdk_pixbuf_new_from_stream (input_stream, NULL, NULL);

    g_autofree gchar *credits = get_txt_from_resources (resources, "/org/kickshaw/txts/credits");

    g_autoptr(GRand) rand = g_rand_new ();

    #define NUMBER_OF_SHOWN_QUOTES 3

    g_auto(GStrv) quotes = g_resource_enumerate_children (resources, "/org/kickshaw/txts/quotes", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
    /* For the last quote, there are four resource files with material for this quote, 
       so these have to be deducted from the size of the character array. */
    const guint8 number_of_available_quotes = g_strv_length (quotes) - 4;

    g_autoptr(GString) credits_and_quotes = g_string_new (credits);
    g_autoptr(GString) quote35_processed  = g_string_new (NULL); // Will only be filled if quote35 is chosen as a random quote.
    guint8 random_numbers[NUMBER_OF_SHOWN_QUOTES]; 

    for (guint8 quotes_cnt = 0; quotes_cnt < NUMBER_OF_SHOWN_QUOTES; ++quotes_cnt) {
        while (TRUE) {
            random_numbers[quotes_cnt] = (guint8) g_rand_int_range (rand, 1, number_of_available_quotes + 1);

            guint8 inner_quotes_cnt = 0;

            if (quotes_cnt > 0) {
                for (; inner_quotes_cnt < quotes_cnt && random_numbers[quotes_cnt] != random_numbers[inner_quotes_cnt]; ++inner_quotes_cnt);
            }

            if (inner_quotes_cnt == quotes_cnt) { // Quote isn't already in array
                break;
            }
        }

        g_autofree gchar *quote_pos = g_strdup_printf("/org/kickshaw/txts/quotes/quote%i", random_numbers[quotes_cnt]);
        g_autofree gchar *quote = get_txt_from_resources (resources, quote_pos);
        g_string_append (credits_and_quotes, "\n\n");

        if (random_numbers[quotes_cnt] == number_of_available_quotes) {
            g_autoptr(GRand) rand = g_rand_new ();

            g_autofree gchar *kickshaw_forks_str = get_txt_from_resources (resources, "/org/kickshaw/txts/quotes/quote35_kickshaw_forks");
            g_auto(GStrv) kickshaw_forks = g_strsplit (kickshaw_forks_str, "|", -1);
            const guint8 kickshaw_fork_idx = (guint8) g_rand_int_range (rand, 0, g_strv_length (kickshaw_forks));

            const gchar *architectures[] = { "Alpha", "ARM", "Cell", "Itanium", "MIPS", "OpenRISC", "PowerPC", "SPARC" };

            const guint8 number_of_archs = G_N_ELEMENTS (architectures);
                  guint8 arch_idx[2];
                  

            arch_idx[0] = (guint8) g_rand_int_range (rand, 0, number_of_archs);

            while (TRUE) {
                arch_idx[1] = (guint8) g_rand_int_range (rand, 0, number_of_archs);
                if (arch_idx[1] != arch_idx[0]) {
                    break;
                }
            }

            const gchar *unnamed_things1[] = { "doodah", "doohickey", "gubbins", "whatsis", "wossname" };
            const gchar *unnamed_things2[] = { "gizmo", "hootmalalie", "kajigger", "oojamaflip", "thingamabob" };

            const guint8 unnamed_thing1_idx = (guint8) g_rand_int_range (rand, 0, G_N_ELEMENTS (unnamed_things1));
            const guint8 unnamed_thing2_idx = (guint8) g_rand_int_range (rand, 0, G_N_ELEMENTS (unnamed_things2));


            gchar current_year_str[5];
            gchar current_month_str[3];
            gchar current_day_str[3];

            gchar *months[12];

			setlocale(LC_TIME, "C"); // Get the month names in English
			for (guint8 months_cnt = 0; months_cnt < 12; ++months_cnt) {
        		months[months_cnt] = nl_langinfo (MON_1 + months_cnt);
    		}
    		setlocale(LC_TIME, ""); // Reset

			time_t timer;
            struct tm *tm_info;

            time (&timer);
            tm_info = localtime (&timer);

            strftime (current_year_str, 5, "%Y", tm_info);
            strftime (current_month_str, 3, "%m", tm_info);
            strftime (current_day_str, 3, "%d", tm_info);

            const guint8 past_years = (guint8) g_rand_int_range (rand, 0, 6);
            const guint current_year = atoi (current_year_str);
            const guint year = current_year - past_years;

            const guint8 current_month = atoi (current_month_str);
            const guint8 current_day = atoi (current_day_str);     
                  guint8 month;
                  guint8 day;

            while (TRUE) {
                month = (guint8) g_rand_int_range (rand, 1, 13);
                if (year < current_year || month <= current_month) {
                    break;
                }
            }

            while (TRUE) {
                day = (guint8) g_rand_int_range (rand, 1, g_date_get_days_in_month (month, (GDateYear) year) + 1);
                if (!(month == current_month && day > current_day)) {
                    break;
                }
            }

            const gchar *expressions_of_doubt[] = { "doubtful", "???", "dafuq?", "um, yes, sure…", "if you say so…", 
                                                    "uh huh, yeah…", "in your dreams…", "you wish" };

            const guint8 expr_of_doubt_idx = (guint8) g_rand_int_range (rand, 0, G_N_ELEMENTS (expressions_of_doubt));

            const gchar *resource_suffixes[] = { "cities", "objects", "locations" };
            enum { Q_CITY, Q_OBJECT, Q_LOCATION, Q_EXPR_OF_DOUBT };
            g_autoptr(GPtrArray) random_quote_elements = g_ptr_array_new ();

            g_ptr_array_set_free_func (random_quote_elements, (GDestroyNotify) g_free);

            for (guint8 txt_cnt = 0; txt_cnt < G_N_ELEMENTS(resource_suffixes); ++txt_cnt) {
                g_autofree gchar *resource_path = g_strdup_printf ("/org/kickshaw/txts/quotes/quote%i_%s", 
                                                                   number_of_available_quotes, resource_suffixes[txt_cnt]);
                g_autofree gchar *resource_str = get_txt_from_resources (resources, resource_path);

                g_auto(GStrv) resource_elements = g_strsplit (resource_str, "|", -1);

                const guint8 random_idx = (guint8) g_rand_int_range (rand, 0, g_strv_length (resource_elements));

                g_ptr_array_add (random_quote_elements, g_strdup (resource_elements[random_idx]));
            }

            g_autofree gchar *compressed_quote = g_strcompress (quote); // Replaces \n with the one byte equivalent.

            /* Not using the ellipsis UTF-8 character here, as this screws up the calculation of the line break positions.  
               The loop below moves from one UTF8 character to the next one, but an ellipsis takes the space of three ASCII 
               characters on the screen. A line that includes an ellipsis thus takes too much space if it uses the UTF8 character. */
            g_autofree gchar *quote35_unprocessed = g_strdup_printf (compressed_quote, 
                                                                     kickshaw_forks[kickshaw_fork_idx], 
                                                                     unnamed_things1[unnamed_thing1_idx], 
                                                                     architectures[arch_idx[0]], architectures[arch_idx[1]], 
                                                                     unnamed_things2[unnamed_thing2_idx], 
                                                                     (gchar *) g_ptr_array_index (random_quote_elements, Q_CITY), 
                                                                     /* The month obtained by strftime is in the range from 1 - 12, 
                                                                        and the variable month is set to the same range as current_month.
                                                                        Because the array indexes are from 0 - 11, -1 has to be deducted 
                                                                        from month if month is used as an index. */
                                                                     months[month - 1], day, year, 
                                                                     (gchar *) expressions_of_doubt[expr_of_doubt_idx], 
                                                                     (gchar *) g_ptr_array_index (random_quote_elements, Q_OBJECT), 
                                                                     (!strstr (g_ptr_array_index (random_quote_elements, Q_OBJECT), 
                                                                               " can ")) ? "lying" : "standing", 
                                                                     (gchar *) g_ptr_array_index (random_quote_elements, Q_LOCATION));

            const guint string_length = strlen (quote35_unprocessed);
                  guint char_pos = 0, start_copy_pos = 0;
                  gint pos_at_line = 0;
            const guint8 max_chars_per_line = 42;
                  guint8 spaces_for_indent = 1;
                  guint8 buffer_len;
                  gboolean first_line_break_passed = FALSE;
                  gboolean space = TRUE; // Default

            gchar *next_utf8_char = g_utf8_find_next_char (quote35_unprocessed, NULL);
            gchar *prev_utf8_char;

            while (quote35_unprocessed[char_pos] != '\0') {
                if (pos_at_line > max_chars_per_line || char_pos == string_length - 1) {
                    if (char_pos < string_length - 1) {
                        prev_utf8_char = g_utf8_find_prev_char (quote35_unprocessed, quote35_unprocessed + char_pos);
                        while (TRUE) {
                            --char_pos;
                            if (quote35_unprocessed + char_pos == prev_utf8_char) {
                                --pos_at_line;
                                prev_utf8_char = g_utf8_find_prev_char (quote35_unprocessed, quote35_unprocessed + char_pos);
                            }
                            if ((quote35_unprocessed[char_pos] == ' ' || quote35_unprocessed[char_pos] == '-') && 
                                pos_at_line <= max_chars_per_line) {
                                if (quote35_unprocessed[char_pos] == '-') {
                                    ++char_pos;
                                    space = FALSE;
                                }

                                break;
                            }
                        }
                    }

                    buffer_len = char_pos - start_copy_pos + ((char_pos < string_length - 1) ? 1 : 2);
                    gchar buffer[buffer_len];
                    g_strlcpy (buffer, quote35_unprocessed + start_copy_pos, buffer_len);

                    g_string_append_printf (quote35_processed, "%s", buffer);
                    if (char_pos < string_length - 1) {
                        g_string_append (quote35_processed, "\n");
                        for (guint8 spaces_cnt = 1; spaces_cnt <= spaces_for_indent; ++spaces_cnt) {
                            g_string_append (quote35_processed, " ");
                        }
                    }

                    start_copy_pos = char_pos + ((space) ? 1 : 0);
                    char_pos = start_copy_pos;
                    next_utf8_char = g_utf8_find_next_char (quote35_unprocessed + char_pos, NULL);
                    pos_at_line = 0;
                    space = TRUE;
                }
                else {
                    if (quote35_unprocessed + char_pos == next_utf8_char) {
                        next_utf8_char = g_utf8_find_next_char (quote35_unprocessed + char_pos, NULL);
                        ++pos_at_line;
                    }
                    ++char_pos;
                }

                if (quote35_unprocessed[char_pos] == '\n') {
                    buffer_len = char_pos - start_copy_pos + 1;
                    gchar buffer[buffer_len];
                    g_strlcpy (buffer, quote35_unprocessed + start_copy_pos, buffer_len);

                    g_string_append_printf (quote35_processed, "%s\n%s", buffer, (!first_line_break_passed) ? " " : "");

                    pos_at_line = -1;
                    start_copy_pos = char_pos + 1;
                    if (!first_line_break_passed) {
                        first_line_break_passed = TRUE;
                    }
                    else {
                        spaces_for_indent = 6;
                    }
                }
            }
        }

        g_string_append (credits_and_quotes, (random_numbers[quotes_cnt] != number_of_available_quotes) ? quote : quote35_processed->str);
    }

    /* Switching header bars on and off like this is the only way to have the dialog window 
       using a header bar if other dialogs should have the option to not show header bars. 
       With gtk_about_dialog_new () it is not possible to set a header bar setting. */
    g_object_set (gtk_settings_get_default (), "gtk-dialogs-use-header", TRUE, NULL);

    ks.about = gtk_about_dialog_new ();

    g_object_set (gtk_settings_get_default (), "gtk-dialogs-use-header", FALSE, NULL);

    /* The only reason why gtk_show_about_dialog () is not used here is that by using it the whole window is 
       being kept in memory when the dialog is closed. That means that the random quotes won't change if the 
       dialog is opened again. */

    g_object_set (ks.about, 
                  "logo", logo, 
                  "program-name", "Kickshaw", 
                  "version", ks.version_info, 
                  "comments", _("A Menu Editor for Openbox"), 
                  "website", "https://savannah.nongnu.org/projects/obladi/", 
                  "website_label", _("Project Page on GNU Savannah"), 
                  "copyright", "© 2010–" RELEASED_IN " Marcus Schätzle", 
                  "license-type", GTK_LICENSE_GPL_2_0, 
                  "authors", (gchar *[]) { "Marcus Schätzle", credits_and_quotes->str, NULL }, 
                  NULL);

    gtk_dialog_run (GTK_DIALOG (ks.about));

    gtk_widget_destroy (ks.about);
}

/* 

    Sets attributes like foreground and background color, visibility of cell renderers, 
    and editability of cells according to certain conditions. Also highlights search results.

*/

static void set_column_attributes (G_GNUC_UNUSED GtkTreeViewColumn *cell_column, 
                                                 GtkCellRenderer   *txt_renderer,
                                                 GtkTreeModel      *cell_model, 
                                                 GtkTreeIter       *cell_iter, 
                                                 gpointer           column_number_pointer)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));

    const gboolean row_is_selected = gtk_tree_selection_iter_is_selected (selection, cell_iter);

    const guint column_number = GPOINTER_TO_UINT (column_number_pointer);
    g_autoptr(GtkTreePath) cell_path = gtk_tree_model_get_path (cell_model, cell_iter);
    guint cell_data_icon_img_status;
    gchar *cell_data[NUMBER_OF_TXT_FIELDS];

    // Defaults
    gboolean visualize_txt_renderer = TRUE;
    gboolean visualize_bool_renderer = FALSE;

    // Defaults
    gchar *background = NULL;
    gboolean background_set = FALSE;
    g_autoptr(GString) txt_with_markup = NULL;

    gboolean highlighting, highlight_separators, show_icons, show_element_visibility_column;

    if (ks.settings.show_menu_button) {
        g_autoptr(GVariant) action_state_show_icons                     = g_action_get_state (G_ACTION (ks.view_actions[M_SHOW_ICONS]));
        g_autoptr(GVariant) action_state_highlight_separators           = g_action_get_state (G_ACTION (ks.view_actions[M_HIGHLIGHT_SEPARATORS]));
        g_autoptr(GVariant) action_state_show_element_visibility_column = g_action_get_state (G_ACTION (ks.view_action_visibility));

        show_icons                     = g_variant_get_boolean (action_state_show_icons);
        highlight_separators           = g_variant_get_boolean (action_state_highlight_separators);
        show_element_visibility_column = g_variant_get_boolean (action_state_show_element_visibility_column);

        g_autoptr(GVariant) visibility_variant = g_action_get_state (G_ACTION (ks.view_action_visibility));
        if (!g_variant_get_boolean (visibility_variant)) {
            highlighting = TRUE;
        }
        else {
            GAction *highlighting_action = g_action_map_lookup_action (G_ACTION_MAP (ks.app), "sf-highlightingstate");
            g_autoptr(GVariant) action_state_highlighting = g_action_get_state (G_ACTION (highlighting_action));
            highlighting = g_variant_get_boolean (action_state_highlighting);
        }
    }
    else {
        show_icons = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_items[M_SHOW_ICONS]));
        highlight_separators = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_items[M_HIGHLIGHT_SEPARATORS]));
        highlighting = gtk_check_menu_item_get_active 
            (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL]));
        show_element_visibility_column = gtk_check_menu_item_get_active 
            (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD]));
    }

    gtk_tree_model_get (cell_model, cell_iter, 
                        TS_ICON_IMG_STATUS, &cell_data_icon_img_status, 
                        TS_ICON_PATH, &cell_data[ICON_PATH_TXT], 
                        TS_MENU_ELEMENT, &cell_data[MENU_ELEMENT_TXT], 
                        TS_TYPE, &cell_data[TYPE_TXT], 
                        TS_VALUE, &cell_data[VALUE_TXT], 
                        TS_MENU_ID, &cell_data[MENU_ID_TXT], 
                        TS_EXECUTE, &cell_data[EXECUTE_TXT], 
                        TS_ELEMENT_VISIBILITY, &cell_data[ELEMENT_VISIBILITY_TXT], 
                        -1);

    // cell_data starts with ICON_PATH, but this is not part of the treeview.
    gchar *original_cell_txt = cell_data[column_number + 1]; // Only introduced for better readability.

    const gboolean icon_to_be_shown = show_icons && cell_data[ICON_PATH_TXT];

    /*
        Set the cell renderer type of the "Value" column to toggle if it is a "prompt" option of a non-Execute action or 
        an "enabled" option of a "startupnotify" option block.
    */

    if (column_number == COL_VALUE && STREQ (cell_data[TYPE_TXT], "option") && 
        streq_any (cell_data[MENU_ELEMENT_TXT], "prompt", "enabled", NULL)) {
        GtkTreeIter parent;
        g_autofree gchar *cell_data_menu_element_parent_txt;

        gtk_tree_model_iter_parent (cell_model, &parent, cell_iter);
        gtk_tree_model_get (ks.ts_model, &parent, TS_MENU_ELEMENT, &cell_data_menu_element_parent_txt, -1);
        if (!STREQ (cell_data_menu_element_parent_txt, "Execute")) {
            visualize_txt_renderer = FALSE;
            visualize_bool_renderer = TRUE;
            g_object_set (ks.renderers[BOOL_RENDERER], "active", STREQ (cell_data[VALUE_TXT], "yes"), NULL);
        }
    }

    g_object_set (txt_renderer, "visible", visualize_txt_renderer, NULL);
    g_object_set (ks.renderers[BOOL_RENDERER], "visible", visualize_bool_renderer, NULL);
    g_object_set (ks.renderers[PIXBUF_RENDERER], "visible", icon_to_be_shown, NULL);
    g_object_set (ks.renderers[EXCL_TXT_RENDERER], "visible", 
                  G_UNLIKELY (icon_to_be_shown && cell_data_icon_img_status), NULL);

    /*
        If the icon is one of the two built-in types that indicate an invalid path or icon image, 
        set two red exclamation marks behind it to clearly distinguish this icon from icons of valid image files.
    */ 
    if (G_UNLIKELY (column_number == COL_MENU_ELEMENT && cell_data_icon_img_status)) {
        g_autofree gchar *exclamation_marks = g_strdup_printf ("<span foreground='%s'><b>!!</b></span>", ks.red_hue);  

        g_object_set (ks.renderers[EXCL_TXT_RENDERER], "markup", exclamation_marks, NULL);
    }

    // Emphasis that a menu, pipe menu or item has no label (=invisible).
    if (G_UNLIKELY (column_number == COL_MENU_ELEMENT && 
                    streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", "item", NULL) && !cell_data[MENU_ELEMENT_TXT])) {
        g_object_set (txt_renderer, "text", _("(No Label)"), NULL);
    }

    if (highlighting) {
        guint8 visibility_of_parent = NONE_OR_VISIBLE_ANCESTOR; // Default
        if (G_UNLIKELY ((cell_data[ELEMENT_VISIBILITY_TXT] && !STREQ (cell_data[ELEMENT_VISIBILITY_TXT], "visible")) || 
                        (visibility_of_parent = check_if_invisible_ancestor_exists (cell_model, cell_path)))) {
            background = ((cell_data[ELEMENT_VISIBILITY_TXT] && 
                          g_str_has_suffix (cell_data[ELEMENT_VISIBILITY_TXT], "orphaned menu")) || 
                          visibility_of_parent == INVISIBLE_ORPHANED_ANCESTOR) ? "#364074" : "#ff6b00";
            background_set = TRUE;
        }
    }

    /* Visibility states are stored internally in English, regardless of the current locale. If, for example, the program got irregularly terminated, 
       the locale was changed afterwards, and the program is restarted again with the recovered autosave, 
       this separation of stored and displayed content ensures that the program does not get "confused" by visibility states in another language. */
    if (column_number == COL_ELEMENT_VISIBILITY && show_element_visibility_column) {
        const gchar *visibility_states[] = { N_("visible"), 
		                                     N_("invisible menu"), 
											 N_("invisible item"), 
											 N_("invisible orphaned menu"), 
											 // Translation note: "dsct." is short for "descendant"
                                             N_("invisible dsct. of invisible menu"), 
											 // Translation note: "dsct." is short for "descendant"
											 N_("invisible dsct. of invisible orphaned menu") };

        for (guint8 visibiltiy_states_cnt = 0; visibiltiy_states_cnt < NUMBER_OF_VISIBILITY_STATES; ++visibiltiy_states_cnt) {
            if (STREQ (cell_data[ELEMENT_VISIBILITY_TXT], visibility_states[visibiltiy_states_cnt])) {
                g_object_set (txt_renderer, "text", _(visibility_states[visibiltiy_states_cnt]), NULL);
            }
        }
    }

    // If a search is going on, highlight all matches.
    if (column_number < COL_ELEMENT_VISIBILITY && *ks.search_term->str && original_cell_txt && 
        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[column_number])) && 
        !gtk_widget_get_visible (ks.action_option_grid)) {
        g_autofree gchar *final_search_str = compute_final_search_string (ks.search_term->str);

        if (g_regex_match_simple (final_search_str, original_cell_txt, 
                                  (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.special_options[MATCH_CASE]))) ? 
                                  0 : G_REGEX_CASELESS, 
                                  G_REGEX_MATCH_NOTEMPTY)) {
            g_autoptr(GRegex) regex = g_regex_new (final_search_str, 
                                                   (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.special_options[MATCH_CASE]))) ? 
                                                   0 : G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY, NULL);
            g_autoptr(GMatchInfo) match_info;

            gchar *counter_char = original_cell_txt; // counter_char will move through all the characters of original_cell_txt.
            gint counter;

            gunichar unichar;
            gchar utf8_char[6]; // Six bytes is the buffer size needed later by g_unichar_to_utf8 (). 
            gint utf8_length;

            enum { KS_START_POS, KS_END_POS, KS_NUMBER_OF_POSITION_MARKERS };
            GArray *positions[KS_NUMBER_OF_POSITION_MARKERS];
            positions[KS_START_POS] = g_array_new (FALSE, FALSE, sizeof (gint));
            positions[KS_END_POS] = g_array_new (FALSE, FALSE, sizeof (gint));
            gint start_position, end_position;

            txt_with_markup = g_string_new ((!background_set) ? NULL : "<span foreground='white'>");

            g_regex_match (regex, original_cell_txt, 0, &match_info);

            while (g_match_info_matches (match_info)) {
                g_match_info_fetch_pos (match_info, 0, &start_position, &end_position);
                g_array_append_val (positions[KS_START_POS], start_position);
                g_array_append_val (positions[KS_END_POS], end_position);
                g_match_info_next (match_info, NULL);
            }

            do {
                unichar = g_utf8_get_char (counter_char);
                counter = counter_char - original_cell_txt; // pointer arithmetic

                if (counter == g_array_index (positions[KS_END_POS], gint, 0)) {
                    txt_with_markup = g_string_append (txt_with_markup, "</span>");
                    // It's simpler to always access the first element instead of looping through the whole array.
                    g_array_remove_index (positions[KS_END_POS], 0);
                }
                /*
                    No "else if" is used here, since if there is a search for a single character going on and  
                    such a character appears double as 'm' in "command", between both m's a span tag has to be 
                    closed and opened at the same position.
                */
                if (counter == g_array_index (positions[KS_START_POS], gint, 0)) {
                    txt_with_markup = g_string_append (txt_with_markup, 
                                                      (row_is_selected) ? 
                                                      "<span background='black' foreground='white'>" : 
                                                      "<span background='yellow' foreground='black'>");
                    // See the comment for the similar instruction above.
                    g_array_remove_index (positions[KS_START_POS], 0);
                }

                utf8_length = g_unichar_to_utf8 (unichar, utf8_char);
                /*
                    Instead of using a switch statement to check whether the current character needs to be escaped, 
                    for simplicity the character is sent to the escape function regardless of whether there will be 
                    any escaping done by it or not.
                */
                g_autofree gchar *utf8_escaped = g_markup_escape_text (utf8_char, utf8_length);

                txt_with_markup = g_string_append (txt_with_markup, utf8_escaped);

                counter_char = g_utf8_find_next_char (counter_char, NULL);
            } while (*counter_char != '\0');

            /*
                There is a '</span>' to set at the end; because the end position is one position after the string size
                this couldn't be done inside the preceding loop.
            */            
            if (positions[KS_END_POS]->len) {
                g_string_append (txt_with_markup, "</span>");
            }

            if (background_set) {
                txt_with_markup = g_string_append (txt_with_markup, "</span>");
            }

            g_object_set (txt_renderer, "markup", txt_with_markup->str, NULL);

            // Cleanup
            g_array_free (positions[KS_START_POS], TRUE);
            g_array_free (positions[KS_END_POS], TRUE);
        }
    }

    /*
        Set:
        - font weight
        - font family
        - font color (if applicable)
        - cell color (if applicable)
        - editability of cell
    */
    g_object_set (txt_renderer, "weight", (highlight_separators && STREQ (cell_data[TYPE_TXT], "separator")) ? 1000 : 400, 

                  "family", (STREQ (cell_data[TYPE_TXT], "separator") && highlight_separators) ? 
                            "monospace, courier new, courier" : "sans, sans-serif, arial, helvetica",
 
                  "foreground", "white", "foreground-set", (row_is_selected || (background_set && !txt_with_markup)),
                  "background", background, "background-set", background_set, 

                  "editable", 
                  (((column_number == COL_MENU_ELEMENT && 
                  (STREQ (cell_data[TYPE_TXT], "separator") || 
                  (cell_data[MENU_ELEMENT_TXT] &&
                  streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", "item", NULL)))) || 
                  (column_number == COL_VALUE && STREQ (cell_data[TYPE_TXT], "option")) || 
                  (column_number == COL_MENU_ID && streq_any (cell_data[TYPE_TXT], "menu", "pipe menu", NULL)) ||
                  (column_number == COL_EXECUTE && STREQ (cell_data[TYPE_TXT], "pipe menu")))), 

                  NULL);

    for (guint8 renderer_cnt = EXCL_TXT_RENDERER; renderer_cnt < NUMBER_OF_RENDERERS; ++renderer_cnt) {
        g_object_set (ks.renderers[renderer_cnt], "cell-background", background, "cell-background-set", background_set, NULL);
    }

    if (txt_with_markup && gtk_cell_renderer_get_visible (ks.renderers[BOOL_RENDERER])) {
        g_object_set (ks.renderers[BOOL_RENDERER], "cell-background", "yellow", "cell-background-set", TRUE, NULL);
    }

    // Cleanup
    free_elements_of_static_string_array (cell_data, NUMBER_OF_TXT_FIELDS, FALSE);
}

/* 

    Changes certain aspects of the tree view or misc. settings. Variant for GMenu.

*/

void change_view_and_options_GMenu (GSimpleAction *simple_action, 
                                    GVariant      *parameter, 
                                    gpointer       key_str_pointer)
{
    const gchar *name = g_action_get_name (G_ACTION (simple_action));

    gboolean state_is_boolean;
    GVariant *new_state;

    if ((state_is_boolean = !STREQ (name, "sf-gridstate"))) {
        g_autoptr(GVariant) state = g_action_get_state (G_ACTION (simple_action));
        const gboolean new_state_bool = (key_str_pointer) ? STREQ (key_str_pointer, "true") : !g_variant_get_boolean (state);

        new_state = g_variant_new_boolean (new_state_bool);

        if (streq_any (name, "sf-showmenuidcol", "sf-showexecutecol", NULL)) {
            gtk_tree_view_column_set_visible (ks.columns[STREQ (name, "sf-showmenuidcol") ? COL_MENU_ID : COL_EXECUTE], 
                                              new_state_bool);
        }
        else if (STREQ (name, "sf-elmviscol")) {
            if (new_state_bool) {
                g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_action_highlighting));
                g_action_change_state (G_ACTION (ks.view_action_highlighting), g_variant_new_boolean (TRUE));
            }
            else {
                g_action_map_remove_action (G_ACTION_MAP (ks.app), "sf-highlightingstate");
            }

            gtk_tree_view_column_set_visible (ks.columns[COL_ELEMENT_VISIBILITY], new_state_bool);
        }
        else if (STREQ (name, "sf-showicons")) {
            gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview)); // If icon visibility has been switched on.
        }
        else if (STREQ (name, "sf-showtreelines")) {
            gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (ks.treeview), new_state_bool);
        }
        else {
            gchar *names[] = { "sf-createbackup", "sf-usetabs", "sf-separaterootmenu", "sf-sortoptions", "sf-notifyaboutconversions"};

            for (guint8 name_cnt = 0; name_cnt < G_N_ELEMENTS (names); ++name_cnt) {
                if (STREQ (name, names[name_cnt])) {
                    if (!(STREQ (names[name_cnt], "sf-sortoptions"))) {
                        set_settings (name_cnt, new_state_bool, -1);
                    }
                    else {
                        if ((ks.settings.autosort_options = new_state_bool)) {
                            gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) sort_loop_after_sorting_activation, NULL);
                        }

                        /*
                           A change of the activation status of autosorting requires 
                           (de)activation of the movement arrows and 
                           (de)activation of the menu items of the popup menu.
                        */
                        row_selected ();
                    }

                    break;
                }
            }
        }
    }
    else if (STREQ (name, "sf-gridstate")) {
        const gchar *state_str = g_variant_get_string (parameter, NULL);
        guint8 grid_settings_cnt, grid_lines_type_cnt;

        for (grid_settings_cnt = M_NO_GRID_LINES, grid_lines_type_cnt = GTK_TREE_VIEW_GRID_LINES_NONE; 
             grid_settings_cnt < NUMBER_OF_VIEW_MENU_GRID_ITEMS; 
             ++grid_settings_cnt, ++grid_lines_type_cnt) {
            if (STREQ (ks.grid_states[grid_settings_cnt], state_str)) {
                gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (ks.treeview), grid_lines_type_cnt);

                break;
            }
        }
    }

    g_action_change_state (G_ACTION (simple_action), (state_is_boolean) ? new_state : parameter);

    // new_state and parameter are consumed, so they are not unrefed here.

    gtk_widget_queue_draw (ks.treeview); // Redraw treeview
    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));

    if (!key_str_pointer && streq_any (name, "sf-useheaderbar", "sf-showmenubutton", NULL)) {
        if (show_restart_dialog (STREQ (name, "sf-useheaderbar"))) {
            restart_app ();
        }
        else {
            GVariant *state = g_action_get_state (G_ACTION (simple_action));
            const gboolean old_state_bool = !g_variant_get_boolean (state);

            g_action_change_state (G_ACTION (simple_action), g_variant_new_boolean (old_state_bool));
        }
    }

    // TRUE only after loading process has been completed.
    if (!key_str_pointer) {
        write_settings (WRITE_CURRENT_STATES);
    }
}

/* 

    Changes certain aspects of the tree view or misc. settings. Variant for GtkMenu.
    In contrast to the GMenu variant, this function is defined as static. Other modules do not call 
    change_view_and_options_GtkMenu () directly; this happens by activating signals.

*/

static void change_view_and_options_GtkMenu (              GtkMenuItem *menu_item, 
                                             G_GNUC_UNUSED gpointer     user_data)
{
    GtkWidget *menu_widget = GTK_WIDGET (menu_item);
    const gboolean menu_item_activated = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item));

    // View
    if (menu_widget == ks.MBar_view_menu_items[M_SHOW_MENU_ID_COL] || 
        menu_widget == ks.MBar_view_menu_items[M_SHOW_EXECUTE_COL]) { 
        const guint8 column_number = (menu_widget == ks.MBar_view_menu_items[M_SHOW_MENU_ID_COL]) ? COL_MENU_ID : COL_EXECUTE;

        gtk_tree_view_column_set_visible (ks.columns[column_number], menu_item_activated);
    }
    else if (menu_widget == ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD]) {
        gtk_tree_view_column_set_visible (ks.columns[COL_ELEMENT_VISIBILITY], menu_item_activated);

        gtk_widget_set_sensitive (ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL], 
                                  menu_item_activated);

        gtk_check_menu_item_set_active 
            (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL]), TRUE);
    }
    else if (menu_widget == ks.MBar_view_menu_items[M_SHOW_TREE_LINES]) {
        gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (ks.treeview), menu_item_activated);
    }
    else if (menu_widget == ks.MBar_view_menu_grid_items[M_NO_GRID_LINES] || 
             menu_widget == ks.MBar_view_menu_grid_items[M_SHOW_GRID_HOR] || 
             menu_widget == ks.MBar_view_menu_grid_items[M_SHOW_GRID_VER] || 
             menu_widget == ks.MBar_view_menu_grid_items[M_BOTH]) {
        guint8 grid_settings_cnt, grid_lines_type_cnt;

        for (grid_settings_cnt = M_NO_GRID_LINES, grid_lines_type_cnt = GTK_TREE_VIEW_GRID_LINES_NONE; 
             grid_settings_cnt <= M_BOTH; 
             ++grid_settings_cnt, ++grid_lines_type_cnt) {
            if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_grid_items[grid_settings_cnt]))) {
            //if (STREQ (gtk_menu_item_get_label (menu_item), ks.grid_submenu_item_txts[grid_settings_cnt])) {
                gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (ks.treeview), grid_lines_type_cnt);
                break;
            }
        }
    }
    // Options
    else {
        for (guint8 widget_cnt = 0; widget_cnt < NUMBER_OF_OPTIONS_MENU_ITEMS; ++widget_cnt) {
            if (menu_widget == ks.MBar_options_menu_items[widget_cnt]) {
                if (widget_cnt < M_USE_HEADER_BAR) {
                    if (widget_cnt != M_SORT_EXECUTE_AND_STARTUPN_OPTIONS) {
                        set_settings (widget_cnt, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.MBar_options_menu_items[widget_cnt])), -1);
                    }
                    else {
                        if ((ks.settings.autosort_options = menu_item_activated)) {
                            gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) sort_loop_after_sorting_activation, NULL);
                        }

                        /*
                           A change of the activation status of autosorting requires 
                           (de)activation of the movement application menu items and buttons.
                        */
                        row_selected ();
                    }
                } 
                else {
                    const gboolean use_header_bar = (menu_widget == ks.MBar_options_menu_items[M_USE_HEADER_BAR]);

                    if (show_restart_dialog (use_header_bar)) {
                        restart_app ();
                    }
                    else {
                        const gulong handler_id = (use_header_bar) ? ks.handler_id_use_header_bar : ks.handler_id_show_menu_button;
                        const guint8 menu_item_idx = (use_header_bar) ? M_USE_HEADER_BAR : M_SHOW_MENU_BUTTON;

                        // Prevent recursion
                        g_signal_handler_block (ks.MBar_options_menu_items[menu_item_idx], handler_id);
                        // Reset to old value
                        gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.MBar_options_menu_items[menu_item_idx]), 
                                                        !gtk_check_menu_item_get_active 
                                                            (GTK_CHECK_MENU_ITEM (ks.MBar_options_menu_items[menu_item_idx])));
                        g_signal_handler_unblock (ks.MBar_options_menu_items[menu_item_idx], handler_id);
                    }
                }

                break;
            }
        }
    }

    gtk_widget_queue_draw (ks.treeview); // Redraw treeview
    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));

    write_settings (WRITE_CURRENT_STATES);
}

/*

    Show a dialog to confirm the restart of the app to apply a decorations type or menu type change.
    This function is called from both change_view_and_options function variants.

*/

static guint8 show_restart_dialog (const gboolean dialog_for_headerbar)
{
    GtkWidget *dialog;
    const gchar *dialog_title = (dialog_for_headerbar) ? _("Server-Side Decorations <-> Client-Side Decorations") : _("Menu Button <-> Menubar");
    g_autofree gchar *dialog_txt = g_strdup_printf ("%s %s", 
                                                    (dialog_for_headerbar) ? _("A change of the decorations type requires a restart of the application.") : 
                                                                             _("A change of the application menu type requires a restart of the application."), 
                                                    _("The menu data incl. the editing history (for undo/redo) will be restored."));
    guint8 result;

    create_dialog (&dialog, dialog_title, "gtk-info", dialog_txt, C_("Cancel|No File Dialogue", "_Cancel"), _("_Restart App"), NULL, SHOW_IMMEDIATELY);

    result = gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);

    return (result == 2); // 1 = Cancel, 2 = Restart App
}

/*

    Restarts Kickshaw if the decorations type or application menu type has been changed.

*/

static void restart_app (void)
{
    // Appears at the end of the change_view_and_options functions, but since restart_app() is called before the end of 
    // these functions, write_settings() has to be called here explicitly.
    write_settings (WRITE_CURRENT_STATES);

    g_autoptr(GError) error = NULL;

    g_autofree gchar *autosave_file_path = g_build_filename (ks.home_dir, ".kickshaw_autosave", NULL);
    g_autoptr(GFile) autosave_file = g_file_new_for_path (autosave_file_path);

    // If no autosave file exists because nothing has been edited yet, create one so that the menu can be recreated from it.
    if (!(g_file_query_exists (autosave_file, NULL))) { 
        g_autofree gchar *autosave_file_path = g_build_filename (ks.tmp_path, "1", NULL);
        g_autoptr(GFile) autosave_file = g_file_new_for_path (autosave_file_path);

        write_autosave (autosave_file);
    }

    g_autofree gchar *restart_indicator_path = g_build_filename (ks.tmp_path, "kickshaw_restart_indicator", NULL);
    g_autoptr(GFile) restart_indicator = g_file_new_for_path (restart_indicator_path);
    g_autoptr(GFileOutputStream) restart_file = g_file_create (restart_indicator, G_FILE_CREATE_NONE, NULL, &error);

    if (G_UNLIKELY (error)) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not create the restart indicator file</b> <tt>%s</tt> <b>!\n"
                                                       "Error:</b> %s"), restart_indicator_path, error->message);

        show_errmsg (err_msg);

        return;
    }

    g_application_quit (G_APPLICATION (ks.app));

    g_spawn_command_line_async (installed_or_local (), &error);

    if (G_LIKELY (!error)) {
        exit (EXIT_SUCCESS);
    }
    else {
        g_printerr ("%s\n", error->message);
        exit (EXIT_FAILURE);
    }
}

/*

    Sets the sensitivity of the expand/collapse menu items and toolbar buttons
    according to the status of the nodes of the treeview.

*/

void set_status_of_expand_and_collapse_buttons_and_menu_items (void)
{
    b_ExpansionStatuses expansion_statuses_of_nodes = { FALSE };

    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) check_expansion_statuses_of_nodes, &expansion_statuses_of_nodes);

    if (ks.settings.show_menu_button) {
        if (expansion_statuses_of_nodes.at_least_one_is_expanded) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_actions[M_COLLAPSE_ALL_NODES]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "collapse");
        }

        if (expansion_statuses_of_nodes.at_least_one_is_collapsed) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_actions[M_EXPAND_ALL_NODES]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "expand");
        }
    }
    else {
        gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES], 
                                  expansion_statuses_of_nodes.at_least_one_is_expanded);
        gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_EXPAND_ALL_NODES], 
                                  expansion_statuses_of_nodes.at_least_one_is_collapsed);
    }

    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_COLLAPSE_ALL_NODES], expansion_statuses_of_nodes.at_least_one_is_expanded);
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_EXPAND_ALL_NODES], expansion_statuses_of_nodes.at_least_one_is_collapsed);
}

/* 

    Asks whether to continue if there are unsaved changes.

*/

gboolean continue_despite_unsaved_changes (void)
{
    GtkWidget *dialog;
    const gchar *dialog_title_txt;
    g_autoptr(GString) dialog_txt = g_string_new (NULL);

    gint result;

    #define CONTINUE_DESPITE_UNSAVED_CHANGES 2

    g_string_append (dialog_txt, _("<b>This menu has unsaved changes."));

    if (G_LIKELY (!ks.restored_autosave_that_hasnt_been_saved_yet)) {
        const gboolean execute_option_conversion_without_edit_done = ((ks.loading_process_edit_types[ICON_INFO_CORRECTED] || 
                                                                       ks.loading_process_edit_types[INVALID_OPTION_VALUE_CORRECTED] || 
                                                                       ks.loading_process_edit_types[INVISIBLE_ELEMENT_VISUALIZED_OR_REMOVED] || 
                                                                       ks.loading_process_edit_types[DEPRECATED_EXE_CMD_CVTD]) && 
                                                                      (ks.pos_inside_undo_stack == 1 && 
                                                                       ks.number_of_undo_stack_items == 1));

        if (ks.pos_inside_undo_stack != ks.last_save_pos || execute_option_conversion_without_edit_done) {
            dialog_title_txt = _("Menu Has Unsaved Changes");
            if (execute_option_conversion_without_edit_done) {
                gchar *edit_type_txts[] = { N_("A correction of at least one icon path or format"), 
                                            N_("A correction of at least one invalid option value"), 
                                            // Translation note: "Visualization" in the sense of making something visible
                                            N_("A visualization/deletion of at least one invisible menu element"), 
											// Translation note: "Application" in the sense of the act of applying something
                                            N_("An application of at least one \"execute\" option conversion") };
                guint8 number_of_edit_types_done = 0, number_of_phrases_added = 0;

                guint8 edit_type_cnt;

                for (edit_type_cnt = 0; edit_type_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES; ++edit_type_cnt) {
                    if (ks.loading_process_edit_types[edit_type_cnt]) {
                        ++number_of_edit_types_done;
                    }
                }

                // Replace default message
                g_string_assign (dialog_txt, ngettext ("<b>This menu has been altered due to the following change that has been applied during the loading process:\n",
                                                       "<b>This menu has been altered due to the following changes that have been applied during the loading process:\n",
                                                       (number_of_edit_types_done == 1)));

                for (edit_type_cnt = 0; edit_type_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES; ++edit_type_cnt) {
                    if (ks.loading_process_edit_types[edit_type_cnt]) {
                        g_string_append_printf (dialog_txt, "• %s\n", _(edit_type_txts[edit_type_cnt]));
                        ++number_of_phrases_added;
                    }
                }
            }
        }
        else {
            dialog_title_txt = _("Changes to the Menu Will Be Discarded");
            if (ks.last_save_pos > 1) {
                // Replace default message
                g_string_assign (dialog_txt, _("<b>This menu has been edited, but the changes have been reverted and the menu is now back at the state after its last save."));
            }
            else {
                // Replace default message
                g_string_assign (dialog_txt, _("<b>This menu has been edited, but the changes have been reverted and the menu is now back at its initial state."));
            }
        }
        if (!execute_option_conversion_without_edit_done) {
            g_string_append (dialog_txt, _(" Hence, the menu will remain unaltered and the editing history will be discarded."));
        }
        else
        {
            g_string_append (dialog_txt, _(" Hence, the menu will remain unaltered."));
        }
    }
    else {
        dialog_title_txt = _("Menu Restored From Autosave Hasn't Been Saved Yet");
        // Replace default message
        g_string_assign (dialog_txt, _("<b>This menu has been restored from an autosave and has never been saved."));
    }

    g_string_append (dialog_txt, _(" Continue anyway?</b>"));

    create_dialog (&dialog, dialog_title_txt, "dialog-warning", dialog_txt->str, C_("Cancel|No File Dialogue", "_Cancel"), _("C_ontinue"), NULL, SHOW_IMMEDIATELY);

    result = gtk_dialog_run (GTK_DIALOG (dialog));

    gtk_widget_destroy (dialog);

    return (result == CONTINUE_DESPITE_UNSAVED_CHANGES);
}

/* 

    Clears the data that is held throughout the running time of the program.

*/

void clear_global_data (void)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));

    FREE_AND_REASSIGN (ks.filename, NULL);

#if GLIB_CHECK_VERSION(2,64,0)
    g_clear_slist (&ks.menu_ids, (GDestroyNotify) g_free);
#else
    g_slist_free_full (ks.menu_ids, (GDestroyNotify) g_free);
    ks.menu_ids = NULL;
#endif

    memset (&ks.loading_process_edit_types, 0, sizeof ks.loading_process_edit_types);
    ks.last_save_pos = 1;

    if (ks.rows_with_icons) {
        stop_timeout ();
    }

    if (gtk_widget_get_visible (ks.find_grid)) {
        show_or_hide_find_grid ();
    }

    g_signal_handler_block (selection, ks.handler_id_row_selected);
    gtk_tree_store_clear (ks.treestore);
    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    /*
        If Kickshaw is started and there is a menu.xml in .config/openbox, the latter is loaded and 
        clear_global_data () called before the creation of a tmp path.
    */
    if (ks.tmp_path) {
        delete_undo_stack_items ();

        ks.number_of_undo_stack_items = ks.pos_inside_undo_stack = 0;
    }

    ks.statusbar_msg_shown = FALSE;
}

/* 

    Activates "Save" menu item/toolbar button (provided that there is a filename) if a change has been done.
    Also sets a global veriable so a program-wide check for a change is possible.
    If a search is still active, the list of results is recreated.     
    A list of rows with icons is (re)created, too.

*/

void activate_change_done (void)
{
    if (*ks.search_term->str) {
        create_list_of_rows_with_found_occurrences ();
    }

    stop_timeout ();
    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) add_icon_occurrence_to_list, NULL);
    ks.timeout_id = g_timeout_add_seconds (1, (GSourceFunc) check_for_external_file_and_settings_changes, "timeout");

    ks.change_done = TRUE;
}

/* 

    Writes all view and option settings into a file.

*/

static void write_settings (const gboolean change_states_to_defaults)
{
    g_autofree gchar *settings_file_str = g_build_filename (ks.home_dir, ".kickshawrc", NULL);
    g_autoptr(GFile) settings_file = g_file_new_for_path (settings_file_str);
    g_autoptr(GFileOutputStream) settings_file_output_stream;
    g_autoptr(GError) error = NULL;

    if (ks.settings.show_menu_button && change_states_to_defaults) {
        const gchar *menu_items_to_be_set_to_true[] = { "sf-showmenuidcol", "sf-showexecutecol", "sf-showicons", 
                                                        "sf-highlightsep", "sf-showtreelines", "sf-createbackup", 
                                                        "sf-usetabs", "sf-separaterootmenu", "sf-notifyaboutconversions", 
                                                        "sf-showmenubutton" };
        guint8 menu_items_cnt;

        FOREACH_IN_ARRAY (menu_items_to_be_set_to_true, menu_items_cnt) {
            change_view_and_options_GMenu (G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (ks.app), 
                                                                                        menu_items_to_be_set_to_true[menu_items_cnt])), 
                                                                                        NULL, "true");
        }
    }

    if (G_UNLIKELY (!(settings_file_output_stream = g_file_replace (settings_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error)))) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not open settings file</b> <tt>%s</tt> <b>for writing!\n"
                                                       "Error:</b> %s"), settings_file_str, error->message);

        show_errmsg (err_msg);

        return;
    }

    // Writing the settings file

    const gchar *err_str = _("<b>An error occurred while writing the settings file</b> "
                             "<tt>.kickshawrc</tt> <b>inside</b> <tt>%s</tt> <b>!\nError:</b> %s");
    g_auto(GStrv) settings_file_actions = g_strsplit (ks.settings_file_actions, "|", -1);
    guint8 action_idx = 0;

    /* This has to be defined as const, as it might receive the return string of a g_variant_get_string () function, 
       which is qualified as const. */
    const gchar *output_str = "# Generated by Kickshaw GTK3 " KICKSHAW_VERSION "\n\n[VIEW]\n\n";
    if (G_UNLIKELY (!(g_output_stream_write (G_OUTPUT_STREAM (settings_file_output_stream), 
                                             output_str, strlen (output_str), NULL, &error)))) {
        g_autofree gchar *err_msg = g_strdup_printf (err_str, ks.home_dir, error->message);

        show_errmsg (err_msg);

        return;
    }

    if (ks.settings.show_menu_button) {
        g_autoptr(GVariant) state_visibility;
        gboolean show_element_visibilty_column;

        state_visibility = g_action_get_state (G_ACTION (ks.view_action_visibility));
        show_element_visibilty_column = g_variant_get_boolean (state_visibility);

        while (settings_file_actions[action_idx]) {
            gchar *action_str = settings_file_actions[action_idx];
            const gboolean highlighting_action_is_removed = STREQ (action_str, "sf-highlightingstate") && !show_element_visibilty_column;

            if (STREQ (action_str, "sf-createbackup")) {
                output_str = "\n[OPTIONS]\n\n";
                if (G_UNLIKELY (!(g_output_stream_write (G_OUTPUT_STREAM (settings_file_output_stream), 
                                                         output_str, strlen (output_str), NULL, &error)))) {
                    break;
                }
            }
            if (!highlighting_action_is_removed) {
                g_autoptr(GVariant) state;
                GAction *action = g_action_map_lookup_action (G_ACTION_MAP (ks.app), action_str);

                state = g_action_get_state (action);

                if (!STREQ (action_str, "sf-gridstate")) {
                    output_str = (g_variant_get_boolean (state)) ? "true" : "false";
                }
                else {
                    output_str = g_variant_get_string (state, NULL);
                }
            }
            else {
                output_str = "true";
            }

            if (G_UNLIKELY (!(g_output_stream_printf (G_OUTPUT_STREAM (settings_file_output_stream), NULL, NULL, &error, 
                                                      "%s=%s\n", (const gchar *) action_str, output_str)))) {            
                break;
            }

            ++action_idx;
        }
    }
    else {
        const gchar *menu_item_status_str = NULL; // Initialization avoids compiler warning.

        while (settings_file_actions[action_idx]) {
            gchar *key_str = settings_file_actions[action_idx];

            if (STREQ (key_str, "sf-createbackup")) {
                output_str = "\n[OPTIONS]\n\n";
                if (G_UNLIKELY (!(g_output_stream_write (G_OUTPUT_STREAM (settings_file_output_stream), 
                                                         output_str, strlen (output_str), NULL, &error)))) {
                    break;
                }
            }

            if (!(STREQ (key_str, "sf-gridstate"))) {
                const gboolean menu_item_status_bool = gtk_check_menu_item_get_active (g_ptr_array_index (ks.settings_menu_items, action_idx));
                menu_item_status_str = (menu_item_status_bool) ? "true" : "false";
            }
            else {
                guint8 menu_items_cnt;

                FOREACH_IN_ARRAY (ks.grid_states, menu_items_cnt) {
                    if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (ks.MBar_view_menu_grid_items[menu_items_cnt]))) {
                        menu_item_status_str = ks.grid_states[menu_items_cnt];
                        break;
                    }
                }
            }

            if (G_UNLIKELY (!(g_output_stream_printf (G_OUTPUT_STREAM (settings_file_output_stream), NULL, NULL, &error, 
                                                      "%s=%s\n", key_str, menu_item_status_str)))) {            
                break;
            }

            ++action_idx;
        }
    }

    if (G_UNLIKELY (error)) {
        g_autofree gchar *err_msg = g_strdup_printf (err_str, ks.home_dir, error->message);

        show_errmsg (err_msg);
    }
}

/* 

    Replaces the file name and window title.

*/

void set_filename_and_window_title (gchar *new_filename)
{
    g_autofree gchar *basename, *dirname, *subtitle;

    FREE_AND_REASSIGN (ks.filename, new_filename);

    basename = g_path_get_basename (ks.filename);
    dirname = g_path_get_dirname (ks.filename);

    g_autofree gchar *regex_str = g_strconcat ("^", ks.home_dir, "/", NULL);

    // The menu file is located inside a subfolder of the ⟨username⟩ folder.
    if (G_LIKELY (g_regex_match_simple (regex_str, new_filename, 0, 0)) && g_regex_match_simple (regex_str, dirname, 0, 0)) { 
        g_autoptr(GRegex) regex = g_regex_new (regex_str, 0, 0, NULL);

        // Replace home folder with tilde, eg. /home/(username)/.config./openbox -> ~/.config/openbox
        subtitle = g_regex_replace (regex, dirname, strlen (dirname), 0, "~/", 0, NULL);
    }
    else { // The menu file is located directly inside the (username) folder or somewhere else entirely (tmp, etc.)
        subtitle = g_strdup (dirname);
    }

    if (!ks.settings.use_header_bar) {
        g_autofree gchar *window_title = g_strdup_printf ("%s (%s)", basename, subtitle);

        gtk_window_set_title (GTK_WINDOW (ks.window), window_title);
    }
    else {
        gtk_label_set_label (GTK_LABEL (ks.basename_label), basename);
        gtk_widget_show (ks.subtitle_label);
        gtk_label_set_label (GTK_LABEL (ks.subtitle_label), subtitle);
    }
}

/*

    Shows an error message dialog.

*/

void show_errmsg (const gchar *errmsg_raw_txt)
{
    g_autofree gchar *label_txt = g_strdup_printf ((!strstr (errmsg_raw_txt, "<b>")) ? "<b>%s</b>" : "%s", errmsg_raw_txt);

    show_message_dialog  (_("Error"), 
	                      "dialog-error", 
						  label_txt, 
						  // Translation note: the verb "Close"
						  _("_Close"));
}

/* 

    Shows a message in the statusbar at the botton for information.

*/

void show_msg_in_statusbar (const gchar *message)
{
    gtk_statusbar_remove_all (GTK_STATUSBAR (ks.statusbar), 1);
    gtk_statusbar_push (GTK_STATUSBAR (ks.statusbar), 1, message); // Only one context (indicated by 1) with one message.

    ks.statusbar_msg_shown = TRUE;
}

/* 

    Function that deals with key press events; currently only used for the "delete" key.

*/

static gboolean key_pressed (              GtkWidget   *treeview, 
                                           GdkEventKey *event, 
                             G_GNUC_UNUSED gpointer     user_data)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));

    if (gtk_tree_selection_count_selected_rows (selection) > 0 && event->keyval == GDK_KEY_Delete) {
        remove_rows ("delete key");
    }

    // FALSE = Propagate other events.
    return FALSE;
}

/* 

    Sets if the selection state of a node may be toggled.

*/

gboolean selection_block_unblock (G_GNUC_UNUSED GtkTreeSelection *selection, 
                                  G_GNUC_UNUSED GtkTreeModel     *model,
                                  G_GNUC_UNUSED GtkTreePath      *path, 
                                  G_GNUC_UNUSED gboolean          path_currently_selected, 
                                                gpointer          block_state)
{
    return GPOINTER_TO_INT (block_state);
}

/* 

    A right click inside the treeview opens the context menu; a left click activates blocking of
    selection changes if more than one row has been selected and one of these rows has been clicked again.

*/

static gboolean mouse_pressed (              GtkTreeView    *treeview, 
                                             GdkEventButton *event, 
                               G_GNUC_UNUSED gpointer        user_data)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));

    if (event->type == GDK_BUTTON_PRESS) {
        if (event->button == GDK_BUTTON_SECONDARY) { // Right click
            create_context_menu (event);

            return TRUE; // Stop other handlers from being invoked for the event.
        }
        else if (event->button == 1 && gtk_tree_selection_count_selected_rows (selection) > 1) {
            if (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
                return FALSE;
            }

            g_autoptr(GtkTreePath) path = NULL;

            // The last three arguments (column, cell_x and cell_y) are not used.
            if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) {
                return FALSE;
            }

            if (gtk_tree_selection_path_is_selected (selection, path)) {
                // NULL = No destroy function for user data.
                gtk_tree_selection_set_select_function (selection, (GtkTreeSelectionFunc) selection_block_unblock, 
                                                        GINT_TO_POINTER (FALSE), NULL);
            }
        }
    }

    return FALSE; // Propagate the event further.
}

/* 

    Unblocks selection changes.

*/

static gboolean mouse_released (              GtkTreeView    *treeview, 
                                G_GNUC_UNUSED GdkEventButton *event, 
                                G_GNUC_UNUSED gpointer        user_data)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));

    if (gtk_tree_selection_count_selected_rows (selection) < 2) {
        return FALSE;
    }

    // NULL = No destroy function for user data.
    gtk_tree_selection_set_select_function (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)), 
                                            (GtkTreeSelectionFunc) selection_block_unblock, GINT_TO_POINTER (TRUE), NULL);

    return FALSE;
}

/* 

    Expands the tree view so the whole structure is visible or
    collapses the tree view so just the toplevel elements are visible.

*/

static void expand_or_collapse_all (gpointer expand_pointer)
{
    const gboolean expand = GPOINTER_TO_INT (expand_pointer);

    if (expand) {
        gtk_tree_view_expand_all (GTK_TREE_VIEW (ks.treeview));
        
        if (ks.settings.show_menu_button) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_actions[M_COLLAPSE_ALL_NODES]));
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "expand");
        }
        else {
            gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES], TRUE);
            gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_EXPAND_ALL_NODES], FALSE);
        }
    }
    else {
        gtk_tree_view_collapse_all (GTK_TREE_VIEW (ks.treeview));
        gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));

        if (ks.settings.show_menu_button) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.view_actions[M_EXPAND_ALL_NODES]));
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "collapse");
        }
        else {
            gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_COLLAPSE_ALL_NODES], FALSE);
            gtk_widget_set_sensitive (ks.MBar_view_menu_items[M_EXPAND_ALL_NODES], TRUE);
        }
    }

    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_EXPAND_ALL_NODES], !expand);
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_COLLAPSE_ALL_NODES], expand);
}

/* 

    Clears treestore and global variables, also resets window title and menu-/tool-/button bar widgets accordingly.

*/

static void new_menu (void)
{
    if (ks.change_done && !continue_despite_unsaved_changes ()) {
        return;
    }

    if (!ks.settings.use_header_bar) {
        gtk_window_set_title (GTK_WINDOW (ks.window), _("Unsaved Menu"));
    }
    else {
        gtk_label_set_label (GTK_LABEL (ks.basename_label), _("Unsaved Menu"));
        gtk_widget_hide (ks.subtitle_label);
    }

    clear_global_data ();

    push_new_item_on_undo_stack (NOT_AFTER_SAVE); // Push new original state on the stack.
    row_selected (); // Switches the settings for application menu and toolbar buttons to that of an empty menu.

    ks.change_done = FALSE;

    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));
}

/* 

    Quits program; if there are unsaved changes a confirmation dialog window is shown.

*/

gboolean quit_program (void)
{
    if ((ks.change_done || ks.restored_autosave_that_hasnt_been_saved_yet) && !continue_despite_unsaved_changes ()) {
        return TRUE; // Do not close the window.
    }

    g_autoptr(GError) error = NULL;

    g_autoptr(GFile) tmp_folder = g_file_new_for_path (ks.tmp_path);

    g_autofree gchar *symlink_pos_inside_undo_stack_path = g_build_filename (ks.tmp_path, "pos_inside_undo_stack_symlink", NULL);
    g_autoptr(GFile) symlink_pos_inside_undo_stack = g_file_new_for_path (symlink_pos_inside_undo_stack_path);

    if (G_UNLIKELY (!(g_file_delete (symlink_pos_inside_undo_stack, NULL, &error)))) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not delete symbolic link</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                                     symlink_pos_inside_undo_stack_path, error->message);

        show_errmsg (err_msg);

        // Cleanup
        g_clear_error (&error);
    }

    if (G_UNLIKELY (!(rm_rf (tmp_folder, NULL, &error)))) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not delete tmp folder</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                                     ks.tmp_path, error->message);

        show_errmsg (err_msg);

        // Cleanup
        g_clear_error (&error);
    }

    if (G_UNLIKELY (!(g_file_delete (ks.symlink_tmp_folder, NULL, &error)))) {
        g_autofree gchar *symlink_tmp_folder_path = g_file_get_path (ks.symlink_tmp_folder);
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not delete symbolic link</b> <tt>%s</tt> <b>!\nError:</b> %s"), 
                                                     symlink_tmp_folder_path, error->message);

        show_errmsg (err_msg);
    }

    delete_autosave ();

    g_application_quit (G_APPLICATION (ks.app));

    return FALSE;
}
