/*
   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> // May need to be included explicitly in some environments.
#include <string.h> // May need to be included explicitly in some environments.
#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 = { // Translation note: Column header in a tree view.
	                        // If the target language distinguishes between definite and indefinite forms, 
							// please use the indefinite form (“Menu Element”), not the definite one (“The Menu Element”).
                            N_("Menu Element"), 
	                        // Translation note: Column header in a tree view.
							// If the target language distinguishes between definite and indefinite forms, 
							// please use the indefinite form (“Type”), not the definite one (“The Type”). "Type" refers to the type of a variable.
	                        N_("Type"), 
							// Translation note: Column header in a tree view.
							// If the target language distinguishes between definite and indefinite forms, 
							// please use the indefinite form (“Value”), not the definite one (“The Value”). "Value" refers to the value of a variable.
							N_("Value"), 
							// Translation note: Column header in a tree view.
							// If the target language distinguishes between definite and indefinite forms, 
							// please use the indefinite form (“Menu ID”), not the definite one (“The Menu ID”).
							N_("Menu ID"), 
							// Translation note: Do not translate this. Column header in a tree view.
							N_("Execute"), 
							// Translation note: This is a column header in a tree view. 
							// If the target language distinguishes between definite and indefinite forms, 
							// please use the indefinite form (“Element Visibility”), not the definite one (“The Element Visibility”).
							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 on_shutdown (G_GNUC_UNUSED GApplication app, G_GNUC_UNUSED gpointer user_data);
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 value.
    ks.locale = "en_US";

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

    gboolean exact_locale_match = FALSE;

    // Look for an exact match of the current locale in 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";
        }
		
		/* Look for a language-only match of the current locale in provided_locales.
           For example, if the current locale is "fil_PH", "fil" will be matched.
           If the current locale is "pt" without a region suffix, "pt_BT" will be chosen 
           because it is alphabetically first in 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 based on the given argument. ###

        g_autoptr(GResource) resources = resources_get_resource ();

        g_resources_register (resources);

        // ### Check whether the file path exists. ###

        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 starts with a tilde, the system has already expanded it 
            // to the home directory and username, so no replacement is needed.
            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, some iconv variants (e.g., on FreeBSD) do not support the '-o' option 
                           to redirect output. To hide iconv output, std_out_iconv is defined below and passed 
                           to g_spawn_command_line_sync(). Using the parameter instead of the option is simple and 
                           avoids repeatedly checking support for '-o'. */
                        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;
            // Default values.
            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"), 
                                                                     !(argc == 3 && options == 0) ? _("invalid argument") : _("invalid arguments"));

                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 complete setup. ###


#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);
    g_signal_connect(ks.app, "shutdown", G_CALLBACK(on_shutdown), NULL);

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

    g_object_unref (ks.app);

    return status;
}

/* 

    Creates the GUI and connects signals.
    Also loads settings and the 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[] = { // Translation note: Menu item label.
                                   N_("File"), 
	                               // Translation note: Like the German "Bearbeiten". Menu item label.
	                               N_("Edit"), 
	                               // Translation note: Menu item label.
								   N_("Search"), 
								   // Translation note: Menu item label (the menu contains options affecting how the content is displayed, "Ansicht" in German).
								   N_("View"), 
								   // Translation note: Menu item label.
								   N_("Options"), 
								   // Translation note: Menu item label.
								   N_("Help") };

    const gchar *file_menu_item_txts[] = { // Translation note: Menu item label in a "File" menu.
                                           N_("New"),
	                                       // Translation note: The verb "Open". Menu item label in a "File" menu.
	                                       N_("Open"), 
	                                       // Translation note: Menu item label in a "File" menu.
										   N_("Save"), 
										   // Translation note: Menu item label in a "File" menu.
										   N_("Save As…"), 
										   // Translation note: Menu item label in a "File" menu.
										   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[] = { // Translation note: Tooltip of a toolbar button.
                                           N_("Undo"),
                                           // Translation note: Tooltip of a toolbar button.
	                                       N_("Redo"),
										   // Translation note: Context menu label for moving a node in a tree view.
										   N_("Move to Top"),
										   // Translation note: Context menu label for moving a node in a tree view.
										   N_("Move Up"),
										   // Translation note: Context menu label for moving a node in a tree view.
										   N_("Move Down"),
										   // Translation note: Context menu label 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[] = { // Translation note: Menu item label and tooltip.
                                           N_("Expand All Nodes"),
                                           // Translation note: Menu item label and tooltip.
	                                       N_("Collapse All Nodes"), 
	                                       // Translation note: Menu item label.
										   N_("Show Menu ID Column"), 
										   // Translation note: Menu item label. Do not translate the word "Execute".
                                           N_("Show Execute Column"), 
                                           // Translation note: Menu item label.
										   N_("Show Element Visibility Column"), 
										   // Translation note: Menu item label.
										   N_("Show Icons"), 
										   // Translation note: Menu item label. Here, "highlight" means “make a separator visually stand out”.
                                           N_("Highlight Separators"), 
                                           // Translation note: Menu item label.
										   N_("Show Tree Lines"), 
										   // Translation note: Menu item label.
										   N_("Show Grid") };
    const gchar *view_menu_item_accelerators[] = { N_("<Shift><Ctl>X"), N_("<Shift><Ctl>C"), "", "", "", "", "", "", "" };

    // Translation note: Menu item label.
    const gchar *view_keep_highlighting_txt = _("Keep Highlighting");

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

    const gchar *help_menu_item_txts[] = { // Translation note: Menu item label.
                                           N_("Hints"), 
                                           // Translation note: Menu item label.
                                           N_("Shortcuts"), 
                                           // Translation note: Menu item label.
                                           N_("Report Issue"), 
                                           // Translation note: Menu item label.
                                           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: Tooltip.
                                   N_("Save Menu"), 
                                   // Translation note: Tooltip.
                                   N_("Save Menu As…"), 
                                   N_("Undo"), 
                                   N_("Redo"), 
                                   N_("Find"), 
                                   // Translation note: Menu item label and tooltip.
                                   N_("Expand All Nodes"), 
                                   // Translation note: Menu item label and tooltip.
                                   N_("Collapse All Nodes") };

    GtkWidget *main_box;

    // The label text of the last button is set dynamically based on the type of the selected row.
    const gchar *bt_add_txts[] = { // Translation note: If the target language distinguishes between definite and indefinite forms, 
	                               // please use the indefinite form (“Menu”), not the definite one (“The Menu”).
                                   N_("_Menu"), 
                                   // Translation note: If the target language distinguishes between definite and indefinite forms, 
								   // please use the indefinite form (“Pipe Menu”), not the definite one (“The Pipe Menu”).
                                   N_("_Pipe Menu"), 
                                   // Translation note: If the target language distinguishes between definite and indefinite forms, 
								   // please use the indefinite form (“Item”), not the definite one (“The Item”).
                                   N_("_Item"), 
                                   // Translation note: If the target language distinguishes between definite and indefinite forms, 
								   // please use the indefinite form (“Separator”), not the definite one (“The Separator”).
                                   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 script direction.
    // This addresses a case observed on one system where the layout direction was incorrect.  
    if (streq_any (ks.locale, "ar", "fa", "he", "ks", "ps", "sd", "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 detect when an entry field loses focus.
    gtk_widget_add_events (ks.window, GDK_FOCUS_CHANGE_MASK);
    determine_theme_type ();


     /* ### Check the settings file (if it exists) to determine whether the window should: ###
        ### - Contain a header bar.                                                        ###
        ### - 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 access 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 (_(view_menu_item_txts[M_SHOW_ELEMENT_VIS_COL]), 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 (view_keep_highlighting_txt, 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 the 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[] = { // Translation note: Menu item label.
                                            N_("_File"), 
		                                    // Translation note: Menu item label (e.g., German "Bearbeiten").
	                                        N_("_Edit"), 
	                                        // Translation note: Menu item label.
                                            N_("_Search"), 
											// Translation note: Menu item label (the menu contains options affecting how the content is displayed, "Ansicht" in German).
											N_("_View"),
											// Translation note: Menu item label.
											N_("_Options"), 
											// Translation note: Menu item label.
											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

        // Translation note: Menu item label.
        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);

        // Translation note: Menu item label.
        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 (_(view_menu_item_txts[M_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 (_(view_menu_item_txts[M_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 (_(view_menu_item_txts[M_SHOW_MENU_ID_COL]));
        ks.MBar_view_menu_items[M_SHOW_EXECUTE_COL] = gtk_check_menu_item_new_with_label (_(view_menu_item_txts[M_SHOW_EXECUTE_COL]));
        ks.MBar_view_menu_items[M_SHOW_ELEMENT_VIS_COL] = gtk_menu_item_new_with_label (_(view_menu_item_txts[M_SHOW_ELEMENT_VIS_COL]));
        ks.MBar_view_menu_items[M_SHOW_ICONS] = gtk_check_menu_item_new_with_label (_(view_menu_item_txts[M_SHOW_ICONS]));
        ks.MBar_view_menu_items[M_HIGHLIGHT_SEPARATORS] = gtk_check_menu_item_new_with_label (_(view_menu_item_txts[M_HIGHLIGHT_SEPARATORS]));
        ks.MBar_view_menu_items[M_SHOW_TREE_LINES] = gtk_check_menu_item_new_with_label (_(view_menu_item_txts[M_SHOW_TREE_LINES])); 
        ks.MBar_view_menu_items[M_SHOW_GRID] = gtk_menu_item_new_with_label (_(view_menu_item_txts[M_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
        ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_ACTVTD] = 
            gtk_check_menu_item_new_with_label (_(view_menu_item_txts[M_SHOW_ELEMENT_VIS_COL]));
        ks.MBar_view_menu_visibility_items[M_SHOW_ELEMENT_VISIBILITY_COL_KEEP_HIGHL] = 
            gtk_check_menu_item_new_with_label (view_keep_highlighting_txt);

        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 menu 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 the decorations-type change dialog from being triggered while parsing the key file,
           set the menu item to ks.settings.use_header_bar before connecting signals. When the key file is
           parsed again later, the menu item will already match the stored value. Because
           gtk_check_menu_item_set_active() calls the callback only when the state actually changes, the
           callback is not invoked and the dialog is 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);

    // The label text is set dynamically depending on whether a row can be added.
    ks.bt_bar_label = gtk_label_new (_("Add New:  "));
    gtk_container_add (GTK_CONTAINER (button_add_grid), ks.bt_bar_label);

    /* The label text depends on the type of the selected row, but it is set here to establish an
       initial size for 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: Context menu label for moving a node in a tree view.
	                                        N_("Move to Top"),
											// Translation note: Context menu label for moving a node in a tree view.
	                                        N_("Move Up"),
											// Translation note: Context menu label for moving a node in a tree view.
											N_("Move Down"),
											// Translation note: Context menu label 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);
    // Do not unref the CSS provider here, as it is 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 the action/option combo box; the combo box isn't 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 because it does not shrink
       when repopulated with shorter items, which looks clunky. The code for adding and deleting the combo
       box is in adding_and_deleting.c, not in this function or module. 
	*/
    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 setting.
    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);

    // Translation note: If the target language distinguishes between definite and indefinite forms, 
    // please use the indefinite form (“Icon”), not the definite one (“The Icon”).
    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);
    // Translation note: If the target language distinguishes between definite and indefinite forms, 
    // please use the indefinite form (“Icon”), not the definite one (“The Icon”).
    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);
    // Translation note: If the target language distinguishes between definite and indefinite forms, 
    // please use the indefinite form (“Icon”), not the definite one (“The Icon”).
    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) {
        // Translation note: If the target language distinguishes between definite and indefinite forms, 
        // please use the indefinite form (“Menu ID”), not the definite one (“The Menu ID”).
        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");
    // 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);
    // Must be placed here because the signal to deactivate the other column check boxes needs 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);

    /* 
        Set the message label’s height to be identical to that of the buttons so the button grid does not
        shrink when the buttons are missing. This can only be done after all widgets have been added to
        the grid, because only then is the button grid’s height (with buttons shown) known.
	*/
    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 so it does not shrink when ks.bt_add[ACTION_OR_OPTION] is hidden.
    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);

    /* 
	    Calling row_selected() hides the following widgets:
        * 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 happens before the settings file is loaded. If there is an issue with the settings file,
        a dialog window is shown, and the elements above should already be hidden.
    */
    row_selected ();

    /*
        ks.find_grid is not hidden by row_selected(). It would be hidden later if a menu file exists, 
		but since that might not be the case, hide it here for simplicity.
    */
    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], 
                                   // Placeholder element; NULL cannot be used because it acts as a terminator.
                                   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) 
					   and ignored by change_view_and_options_GMenu(). 
					   If new_variant != NULL, key_str is ignored by change_view_and_options_GMenu(). */
                    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 temporary folder for undo/redo,                                                              ###
	   ### or retrieve an existing one if the program was restarted or started again after 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 value.

    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 whether a symbolic link to the Kickshaw tmp folder exists.   ###
       ### If a restart indicator file is present, the program was restarted. ###
       ### Otherwise, the program was likely terminated irregularly.          ### */


    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 was 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 cannot be reconstructed. "
                                           "It is stored inside a folder of a temporary directory (e.g. /tmp) "
                                           "and has probably been removed by the system."));

            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 the menu if it 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 the first undo stack item if the program was not restarted or was terminated irregularly, ###
       ### or restore the stack pointers using the existing symlinks.                                              ### */


    // If this is not a program restart, or the tmp folder from a previous run is no longer present 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 to row_selected(),
        the visibility of MBut_file_menu_items[M_NEW]/MBar_file_menu_items[M_NEW] and
        the sensitivity of tb_buttons[TB_NEW] are also set, depending on whether a menu file has been loaded.

   		Unlike the previous call to row_selected(), this must be done after the menu file has been loaded.

        If a settings file exists, 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);
}

/*

    Creates and adds 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]));
    }
}

/*

    Adds 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, places it in a grid with extra margin, and adds the grid 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 activating 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;
}

/*

    Show a window with usage hints for 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;
	// Initialize shortcuts_group to NULL to avoid a static-analyzer warning about an uninitialized value.
    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);
	/* Showing at least one section is required before adding it to the window,
       otherwise the window opens on search results with no way out.
       GtkShortcutsWindow’s show_all() is insufficient because 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 leak memory,
               because shortcuts_group is owned by the shortcuts window. When the window is destroyed,
               all its child objects are destroyed 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;
    }

    // Finalize

    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 to the Kickshaw Bug Tracker “Submit Item” page.   

*/

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 a connection to the download server is available, tries to fetch the version file and reads 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)))) {
                    // Translation note: Leave <tt>latest</tt> as is, no translation for this part.                           
                    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);
                    ks.error_msg_in_about_dialog = TRUE;
                }

                if (G_UNLIKELY (!(g_file_delete (latest_local, NULL, &error)))) {
                    // Translation note: Leave <tt>latest</tt> as is, no translation for this part.
                    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);
                    ks.error_msg_in_about_dialog = TRUE;
                }

#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 {
                // Translation note: Leave <tt>latest</tt> as is, no translation for this part. 
                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);
                ks.error_msg_in_about_dialog = TRUE;
            }
        }
		/*
		    Ignore a missing version-check file.
            The file may have been renamed on the server, or the version-check mechanism may have changed.
            The check will likely work again in a newer version of Kickshaw.
        */
        else if (!(g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))) {
            // Translation note: Leave <tt>latest</tt> as is, no translation for this part. 
            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);
            ks.error_msg_in_about_dialog = TRUE;
        }
    }
    else { // No internet connection, or the 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;
}

/* 

    Show an About dialog with 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);

    time_t t = time(NULL);              // Get current time
    struct tm *tm_info = localtime(&t); // Convert to local time

    gint weekday = tm_info->tm_wday; // Sunday = 0, Monday = 1, ..., Saturday = 6

    // Adjust so that Monday = 1, ..., Sunday = 7
    gint adjusted_weekday = (weekday == 0) ? 7 : weekday;

    g_autofree gchar *logo_path = g_strdup_printf("/org/kickshaw/imgs/logo_about_%i.%s", adjusted_weekday, (adjusted_weekday == 2 || adjusted_weekday == 4 || adjusted_weekday == 7) ? "png" : "jpg");
    g_autoptr(GBytes) data = g_resource_lookup_data (resources, logo_path, 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, four auxiliary resource files are used;
       subtract these 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); // Only used if quote35 is selected 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 is not already in the 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.

            /* Do not use the UTF‑8 ellipsis character here, as it breaks line-break calculations.
               The loop below advances by UTF8 characters, but an ellipsis renders at the width of three ASCII characters. 
			   A line containing an ellipsis would therefore take too much space if the UTF‑8 character were used. */
            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 1–12, 
																	   and the variable “month” is set to the same range as current_month.
                                                                       Because array indexes are 0–11, subtract 1 from month when using it 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);
    }

   /* 
       Toggling header bars on and off like this is the only way to give the dialog window 
       a header bar while allowing other dialogs to omit it. 
       gtk_about_dialog_new() does not provide a way to configure header bar usage.
    */
    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);

   /* 
       gtk_show_about_dialog() is not used here because it keeps the entire window in memory 
       after closing the dialog. This would prevent the random quotes from changing 
       when the dialog is reopened.
    */
    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 such as foreground/background color, cell renderer visibility, 
    and cell editability based on 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];

    // Default values.
    gboolean visualize_txt_renderer = TRUE;
    gboolean visualize_bool_renderer = FALSE;

    // Default values.
    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, which is not part of the tree view.
    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];

    /*
        Use a toggle cell renderer for the "Value" column if it is a "prompt" option 
        of a non-Execute action, or an "enabled" option within a "startupnotify" 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 “invalid” types (invalid path or invalid image), 
        display two red exclamation marks after it to clearly distinguish it from valid icons.
    */ 
    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);
    }

    // Emphasize 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. 
       This prevents issues if the program is restarted with a recovered autosave after a locale change, 
       ensuring states are not misinterpreted 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 active, highlight all matching text in the relevant columns.
    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; // Pointer that steps through all 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>");
					// Always access the first array element for simplicity instead of looping.
                    g_array_remove_index (positions[KS_END_POS], 0);
                }
				/*
                     No "else if" is used here; if the search is for a single character 
                     and it appears twice in a row (e.g., the 'm' in "command"), 
                     a span must be closed and reopened 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 checking whether the current character needs escaping, 
                    always send it to the escape function for simplicity.
                */
                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');

            /*
                The final closing </span> is added here because the end position 
                occurs one position beyond the string length, so it cannot be handled in the loop above.
            */           
            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 the temporary cell_data array.
    free_elements_of_static_string_array (cell_data, NUMBER_OF_TXT_FIELDS, FALSE);
}

/* 

    Changes certain aspects of the tree view or miscellaneous settings (GMenu variant).

*/

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);
                        }

                        /*
                           Changing the autosort activation state requires enabling/disabling 
                           the movement arrows and corresponding popup menu items.
                        */
                        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 here, so they are not unreferenced.

    gtk_widget_queue_draw (ks.treeview); // Redraw the tree view.
    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 the loading process has completed.
    if (!key_str_pointer) {
        write_settings (WRITE_CURRENT_STATES);
    }
}

/* 

    Changes certain aspects of the tree view or miscellaneous settings (GtkMenu variant).
    Unlike the GMenu variant, this is static — called only via signal handlers.

*/

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]))) {
                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);
                        }

                        /*
                           Changing the autosort activation state requires enabling/disabling 
                           the movement-related menu items and toolbar 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 the previous 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 the tree view.
    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));

    write_settings (WRITE_CURRENT_STATES);
}

/*

    Displays a dialog to confirm restarting the application to apply a change 
    in decorations type or menu type.
    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 changed.

*/

static void restart_app (void)
{
	// Since restart_app() is called before the end of both change_view_and_options(),
    // write_settings() must 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 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;
    }

    ks.should_restart = TRUE;

    g_application_quit (G_APPLICATION (ks.app));
}

/*

    Restarts the application after ensuring a proper shutdown.

*/

static void on_shutdown (G_GNUC_UNUSED GApplication app, G_GNUC_UNUSED gpointer user_data)
{
    g_autoptr(GError) error = NULL;

    if (ks.should_restart) {
        g_autofree gchar *command_str = g_strconcat ("sh -c 'while pgrep -x kickshaw > /dev/null; do sleep 0.1; done; exec ", installed_or_local (), "'", NULL);

        g_spawn_command_line_async (command_str, &error);
    }

    if (G_LIKELY (!error)) {
        exit (EXIT_SUCCESS);
    }
    else {
        g_printerr ("Failed to restart: %s", error->message);

        exit (EXIT_FAILURE);
    }
}

/*

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

*/

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);
}

/* 

    Prompts the user to confirm continuing 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" means making something visible.
                                            N_("A visualization/deletion of at least one invisible menu element"), 
											// Translation note: "Application" refers to 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, (number_of_edit_types_done == 1) ? _("<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"));

                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 has been restored to 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 has been restored to its initial state."));
            }
        }
        if (!execute_option_conversion_without_edit_done) {
            g_string_append (dialog_txt, _(" As a result, the menu will remain unchanged and the editing history will be discarded."));
        }
        else
        {
            g_string_append (dialog_txt, _(" As a result, 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 all data stored for the duration of the program’s execution.

*/

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 starts and finds a menu.xml in .config/openbox, it loads it and 
        calls clear_global_data() before creating a temporary 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 the “Save” menu item/toolbar button (if a filename exists) when a change is made.
    Also sets a global variable for program-wide change detection.
    If a search is active, the results list is rebuilt.
    Builds the list of rows with icons as well.

*/

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 to 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;
    }

    // Write 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;

    /* Must be defined as const because it might receive the return value of 
      g_variant_get_string(), 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 path with a tilde, e.g. /home/(username)/.config/openbox -> ~/.config/openbox
        subtitle = g_regex_replace (regex, dirname, strlen (dirname), 0, "~/", 0, NULL);
    }
    else { // The menu file is located directly in 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);

    // If the target language distinguishes between definite and indefinite forms, 
    // please use the indefinite form (“Error”), not the definite one (“The Error”).
    show_message_dialog  (_("Error"), 
	                      "dialog-error", 
						  label_txt, 
						  // Translation note: the verb "Close".
						  _("_Close"));
}

/* 

    Shows an informational message in the status bar at the bottom.

*/

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;
}

/* 

    Handles 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;
}

/* 

    Determines whether the selection state of a node can 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);
}

/* 

    Handles mouse press events.
    - Right click inside the tree view opens the context menu.
    - Left click blocks selection changes if multiple rows are selected and one of them is 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) {
             g_autoptr(GtkTreePath) path = NULL;

             if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) {   
                 gtk_tree_selection_unselect_all (selection);
             }

             return FALSE;
        }
        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, cell_y) are unused.
            if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), event->x, event->y, &path, NULL, NULL, NULL)) {
                gtk_tree_selection_unselect_all (selection);
            
                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 after mouse release.

*/

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 all nodes in the tree view or collapses it to show only top-level elements.

*/

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 the tree store and global variables, and resets the window title and menu/toolbar/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 the new original state onto the undo stack.
    row_selected (); // Switch settings for application menu and toolbar buttons to those of an empty menu.

    ks.change_done = FALSE;

    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));
}

/* 

    Quits the program; if there are unsaved changes, a confirmation dialog 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;
}
