/*
   Kickshaw - A Menu Editor for Openbox

   Copyright (c) 2010–2024        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/.
*/

#include <gtk/gtk.h>

#include <string.h> // Sometimes this might have to be included explicitly.

#include "declarations_definitions_and_enumerations.h"
#include "load_menu.h"

// LM = Load Menu, indicating that the enums are only used here; TS = Treestore
enum { LM_TS_BUILD_ICON_IMG, LM_TS_BUILD_ICON_IMG_STATUS, LM_TS_BUILD_ICON_MODIFICATION_TIME, LM_TS_BUILD_ICON_PATH, 
       LM_TS_BUILD_MENU_ELEMENT, LM_TS_BUILD_TYPE, LM_TS_BUILD_VALUE, LM_TS_BUILD_MENU_ID, LM_TS_BUILD_EXECUTE, 
       LM_TS_BUILD_ELEMENT_VISIBILITY, LM_TS_BUILD_PATH_DEPTH, LM_NUMBER_OF_TS_BUILD_FIELDS };
enum { LM_MENUS, LM_ROOT_MENU, LM_NUMBER_OF_MENU_LEVELS };
enum { LM_ITEM, LM_ACTION, LM_NUMBER_OF_CURRENT_ELEMENTS };
enum { LM_MENU_IDS_LIST, LM_MENUS_LIST = 0, LM_MENU_ELEMENTS_LIST, LM_ITEMS_LIST = 1, LM_NUMBER_OF_INVISIBLE_ELEMENTS_LISTS };
// Enumeration for dialogs.
enum { LM_ORPHANED_MENUS, LM_MISSING_LABELS };

typedef struct {
    gchar *line;
    gint line_number;

    GSList *tree_build;
    guint current_path_depth;
    guint previous_path_depth;
    guint max_path_depth;

    GSList *menu_ids;
    GSList *toplevel_menu_ids[LM_NUMBER_OF_MENU_LEVELS];

    GSList *menus_with_double_or_contradicting_attribute_values;

    gboolean ignore_all_upcoming_errs;

    gchar *current_elements[LM_NUMBER_OF_CURRENT_ELEMENTS];
    gboolean open_option_element;
    gchar *previous_type;

    gboolean icon_info_corrected;
    gboolean invalid_option_value_corrected;
    gboolean deprecated_exe_cmd_cvtd;

    guint8 loading_stage;
    gboolean change_done_loading_proc;
    gboolean root_menu_finished;
} MenuBuildingData;

static void create_dialogs_for_invisible_menus_and_items (const guint8             dialog_type, 
                                                                GtkTreeSelection  *selection, 
                                                                GSList           **invisible_elements_lists);
static void element_content (G_GNUC_UNUSED       GMarkupParseContext  *parse_context, 
                                           const gchar                *text, 
                             G_GNUC_UNUSED       gsize                 text_len, 
                                                 gpointer              menu_building_pnt, 
                             G_GNUC_UNUSED       GError              **error);
static gboolean elements_visibility (GtkTreeModel  *foreach_model, 
                                     GtkTreePath   *foreach_path,
                                     GtkTreeIter   *foreach_iter, 
                                     GSList       **invisible_elements_lists);
static void end_tags (G_GNUC_UNUSED       GMarkupParseContext  *parse_context, 
                      G_GNUC_UNUSED const gchar                *element_name,
                                          gpointer              menu_building_pnt, 
                      G_GNUC_UNUSED       GError              **error);
static void start_tags_their_attr_and_val__empty_element_tags (      GMarkupParseContext  *parse_context, 
                                                               const gchar                *element_name, 
                                                               const gchar               **attribute_names, 
                                                               const gchar               **attribute_values, 
                                                                     gpointer              menu_building_pnt, 
                                                                     GError              **error);
                                                               
/* 

    Parses start-tags, their attributes and the values of the latter. Parses also empty element-tags.
    Besides the syntax check done by GLib's XML subset parser, this function also checks for the correct nesting 
    of Openbox-specific tags, uniqueness of menu IDs and other things. It also prevents repeated appearances of 
    the same attribute inside a start-tag.

    GErrors constructed by g_set_error () don't receive a specific GQuark value since there is no differentiation, 
    the value is always set to one.

*/

static void start_tags_their_attr_and_val__empty_element_tags (      GMarkupParseContext  *parse_context, 
                                                               const gchar                *element_name,
                                                               const gchar               **attribute_names,
                                                               const gchar               **attribute_values,
                                                                     gpointer              menu_building_pnt,
                                                                     GError              **error)
{
    MenuBuildingData *menu_building = (MenuBuildingData *) menu_building_pnt; 

    // XML after the root menu is discarded by Openbox.
    if (menu_building->root_menu_finished || STREQ (element_name, "openbox_menu")) {
        return;
    }

    const gchar *current_attribute_name;
    const gchar *current_attribute_value;
    const guint number_of_attributes = g_strv_length ((gchar **) attribute_names);
    GSList **menu_ids = &(menu_building->menu_ids);
    GSList **menus_with_double_or_contradicting_attribute_values = 
        &(menu_building->menus_with_double_or_contradicting_attribute_values);
    guint current_path_depth = menu_building->current_path_depth;
    gchar *current_action = menu_building->current_elements[LM_ACTION];
    gint line_number = menu_building->line_number;
    guint8 *loading_stage = &(menu_building->loading_stage);
    GSList **toplevel_menu_ids = menu_building->toplevel_menu_ids;

    const gchar *valid;

    // This is a local variant of txt_fields for constant values.
    const gchar *txt_fields[NUMBER_OF_TXT_FIELDS] = { NULL }; // Defaults

    gboolean menu_id_found = FALSE; // Default
    const gchar *menu_label = NULL;

    gboolean menu_has_label_inside_root_and_outside_root_for_same_ID = FALSE, // Default
             menu_has_icon_path_inside_root_and_outside_root_for_same_ID = FALSE, // Default
             menu_has_execute_inside_root_and_outside_root_for_same_ID = FALSE; // Default
    enum { ICON_IMG_OUTSIDE_ROOT, ICON_IMG_STATUS_OUTSIDE_ROOT, ICON_MODIFICATION_TIME_OUTSIDE_ROOT, ICON_PATH_OUTSIDE_ROOT, 
           MENU_ID_OUTSIDE_ROOT, LABEL_OUTSIDE_ROOT, EXECUTE_OUTSIDE_ROOT, NUMBER_OF_OUTSIDE_ROOT_ELEMENTS };
    gpointer *outside_root[NUMBER_OF_OUTSIDE_ROOT_ELEMENTS];

    // Defaults = no icon
    GdkPixbuf *icon_img = NULL;
    guint8 icon_img_status = NONE_OR_NORMAL;
    gchar *icon_modification_time = NULL;
    gchar *icon_path = NULL;

    guint8 attribute_cnt;


    // --- Error checking ---


    // Check if current tag is a valid child of another tag.

    if (current_path_depth > 1) {
        const GSList *tag_stack = g_markup_parse_context_get_element_stack (parse_context);

        // This makes sure that static analyzers don't assume that tag_stack can be NULL.
#ifndef G_DISABLE_ASSERT
        g_assert (tag_stack);
#endif

        const gchar *parent_element_name = (g_slist_next (tag_stack))->data;
        const gchar *error_txt = NULL; // Default

        if (G_UNLIKELY (STREQ (element_name, "menu") && !STREQ (parent_element_name, "menu"))) {
            error_txt = _("A menu can only have another menu as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "item") && !STREQ (parent_element_name, "menu"))) {
            error_txt = _("An item can only have a menu as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "separator") && !STREQ (parent_element_name, "menu"))) {
            error_txt = _("A separator can only have a menu as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "action") && !STREQ (parent_element_name, "item"))) {
            error_txt = _("An action can only have an item as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "prompt") && !(STREQ (parent_element_name, "action") && 
                             streq_any (current_action, "Execute", "Exit", "SessionLogout", NULL)))) {
		    // Translation note: Do not translate "Execute", "Exit", "SessionLogout"
            error_txt = _("A \"prompt\" option can only have an \"Execute\", \"Exit\", or \"SessionLogout\" action as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "command") && !(STREQ (parent_element_name, "action") && 
                             streq_any (current_action, "Execute", "Restart", NULL)))) {
			// Translation note: Do not translate "command", "Execute", "Restart"
            error_txt = _("A \"command\" option can only have an \"Execute\" or \"Restart\" action as a parent");
        }
        else if (G_UNLIKELY (STREQ (element_name, "startupnotify") && !(STREQ (parent_element_name, "action") && 
                             STREQ (current_action, "Execute")))) {
			// Translation note: Do not translate "startupnotify", "Execute"
            error_txt = _("A \"startupnotify\" option can only have an \"Execute\" action as a parent");
        }
        else if (G_UNLIKELY (streq_any (element_name, "enabled", "icon", "name", "wmclass", NULL) && 
                             !STREQ (parent_element_name, "startupnotify"))) {
            if (STREQ (element_name, "enabled")) {
				// Translation note: Do not translate "enabled", "startupnotify"
                error_txt = _("An \"enabled\" option can only have a \"startupnotify\" option as a parent");
            }
            else if (STREQ (element_name, "icon")) {
				// Translation note: Do not translate "icon", "startupnotify"
                error_txt = _("An \"icon\" option can only have a \"startupnotify\" option as a parent");
            }
            else if (STREQ (element_name, "name")) {
				// Translation note: Do not translate "name", "startupnotify"
                error_txt = _("A \"name\" option can only have a \"startupnotify\" option as a parent");
            }
            else {
				// Translation note: Do not translate "wmclass", "startupnotify"
                error_txt = _("A \"wmclass\" option can only have a \"startupnotify\" option as a parent");
            }
        }
        else if (G_UNLIKELY (STREQ (menu_building->previous_type, "pipe menu") && 
                             menu_building->previous_path_depth < current_path_depth)) {
            error_txt = _("A pipe menu is a self-closing tag; it can't be used as a parent");
        }

        if (G_UNLIKELY (error_txt)) {
            g_set_error (error, 1, line_number, "%s", error_txt);

            return;
        }
    }

    // Too many attributes

    if (G_UNLIKELY ((STREQ (element_name, "menu") && number_of_attributes > 4) || 
                    (STREQ (element_name, "item") && number_of_attributes > 2) || 
                    (streq_any (element_name, "separator", "action", NULL) && number_of_attributes > 1))) {
        g_set_error (error, 1, line_number, _("Too many attributes for tag \"%s\""), element_name);

        return;
    }

    for (attribute_cnt = 0; attribute_cnt < number_of_attributes; ++attribute_cnt) {
        current_attribute_name = attribute_names[attribute_cnt];
        current_attribute_value = attribute_values[attribute_cnt];

        // Duplicate attributes

        for (guint8 attribute_cnt2 = 0; attribute_cnt2 < number_of_attributes; ++attribute_cnt2) {
            if (attribute_cnt == attribute_cnt2) {
                continue;
            }
            if (G_UNLIKELY (STREQ (current_attribute_name, attribute_names[attribute_cnt2]))) {
                g_set_error (error, 1, line_number, _("Tag \"%1$s\" has more than one \"%2$s\" attribute"), 
                             element_name, current_attribute_name);

                return;
            }
        }

        // Invalid attributes

        if (G_UNLIKELY ((STREQ (element_name, "menu") && 
                        !streq_any (current_attribute_name, "id", "label", "icon", "execute", NULL)) || 
                        (STREQ (element_name, "item") && !streq_any (current_attribute_name, "label", "icon", NULL)) || 
                        (STREQ (element_name, "separator") && !STREQ (current_attribute_name, "label")) || 
                        (STREQ (element_name, "action") && !STREQ (current_attribute_name, "name")))) {
            if (STREQ (element_name, "menu")) {
				// Translation note: Do not translate "id", "label", "icon", "execute"
                valid = _("valid are \"id\", \"label\", \"icon\", and \"execute\"");
            }
            else if (STREQ (element_name, "item")) {
				// Translation note: Do not translate "label", "icon"
                valid = _("valid are \"label\" and \"icon\"");
            }
            else if (STREQ (element_name, "separator")) {
				// Translation note: Do not translate "label"
                valid = _("valid is \"label\"");
            }
            else { // action
				// Translation note: Do not translate "name"
                valid = _("valid is \"name\"");
            }
            g_set_error (error, 1, line_number, _("Tag \"%1$s\" has an invalid attribute \"%2$s\"; %3$s"), 
                         element_name, current_attribute_name, valid);

            return;
        }

        if (STREQ (element_name, "menu")) {

            // Check if a found menu ID fulfills the necessary conditions.

            if (STREQ (current_attribute_name, "id")) {
                menu_id_found = TRUE;

                // Check if the ID is a root menu ID, if so, whether it's at toplevel.

                if (STREQ (current_attribute_value, "root-menu")) {
                    if (G_UNLIKELY (current_path_depth > 1)) {
                        g_set_error (error, 1, line_number, "%s", 
                                     _("The root menu can't be defined as a child of another menu"));

                        return;
                    }

                    *loading_stage = LM_ROOT_MENU;
                }

                // Check if the ID is unique.

                if (G_UNLIKELY (g_slist_find_custom (*menu_ids, current_attribute_value, (GCompareFunc) strcmp) &&
                                !(*loading_stage == LM_ROOT_MENU && current_path_depth == 2 && 
                                  g_slist_find_custom (toplevel_menu_ids[LM_MENUS], current_attribute_value, 
                                                       (GCompareFunc) strcmp) && 
                                  !g_slist_find_custom (toplevel_menu_ids[LM_ROOT_MENU], current_attribute_value, 
                                                        (GCompareFunc) strcmp)))) {
                    g_set_error (error, 1, line_number, 
                                 _("\"%s\" is a menu ID that has already been defined before"), current_attribute_value);

                    return;
                }
            }
            else if (STREQ (current_attribute_name, "label")) {
                menu_label = current_attribute_value;
            }
        }
    }

    // Missing or duplicate menu IDs

    if (G_UNLIKELY (STREQ (element_name, "menu") && !menu_id_found)) {
        g_set_error (error, 1, line_number, _("Menu%s%s%s has no \"id\" attribute"), 
                     (menu_label) ? " \"" : "",  (menu_label) ? menu_label : "", (menu_label) ? "\"" : "");

        return;
    }

    /* 
    
        Consider the following menu file:

        ---

        <?xml version="1.0" encoding="UTF-8"?>

        <openbox_menu>

        <menu label="Label Outside Root Menu" id="menu 1" icon="/home/someuser/images/image1.jpg" execute="program1"/>

        <menu id="root-menu" label="Openbox 3">
            <menu id="menu 1" label="Label Inside Root Menu" icon="/home/someuser/images/image2.jpg" execute="program2"/>
        </menu>

        </openbox_menu>

        ---

        This is a menu with contradictory attribute values. It will work, but Openbox does it in that kind of way that 
        it will show the file image2.jpg from the root menu as icon, whereas it will use the label and execute attributes 
        values defined outside the root menu. As Openbox does handle it this way, Kickshaw is supposed to do the same. 
        So if the program currently scans the root menu and a menu is found there, the program looks if there has been a 
        previous definition outside the root menu. If the depicted situation applies, it does the necessary preparations 
        before the actual values are processed.
        Because of the settings done here, for the menu inside the root menu its label and execute attribute values will be 
        ignored, since the relevant ones are the one from outside the root menu. For the icon the situation is vice versa; 
        the icon settings of the menu outside the root menu will be replaced by the one of the menu inside the root menu.

    */

    if (*loading_stage == LM_ROOT_MENU && STREQ (element_name, "menu")) {
        GSList *tree_build_loop;
        gpointer *tree_data;
        enum { MENU_ID_INSIDE_ROOT, LABEL_INSIDE_ROOT, ICON_PATH_INSIDE_ROOT, EXECUTE_INSIDE_ROOT, NUMBER_OF_INSIDE_ROOT_ELEMENTS };
        G_GNUC_EXTENSION gchar *inside_root[] = { [0 ... NUMBER_OF_INSIDE_ROOT_ELEMENTS - 1] = NULL };

        for (attribute_cnt = 0; attribute_cnt < number_of_attributes; ++attribute_cnt) {
            if (STREQ (attribute_names[attribute_cnt], "id")) {
                // attribute_values contains const strings; avoiding a const-qualification-cast-away by string duplication
                inside_root[MENU_ID_INSIDE_ROOT] = g_strdup (attribute_values[attribute_cnt]);
            }
            else if (STREQ (attribute_names[attribute_cnt], "label")) {
                inside_root[LABEL_INSIDE_ROOT] = g_strdup (attribute_values[attribute_cnt]); // see comment above
            }
            else if (STREQ (attribute_names[attribute_cnt], "icon")) {
                inside_root[ICON_PATH_INSIDE_ROOT] = g_strdup (attribute_values[attribute_cnt]); // see comment above
            }
            else { // Execute
                inside_root[EXECUTE_INSIDE_ROOT] = g_strdup (attribute_values[attribute_cnt]); // see comment above
            }
        }

        FOREACH_IN_LIST (menu_building->tree_build, tree_build_loop) {
            tree_data = tree_build_loop->data;
            outside_root[MENU_ID_OUTSIDE_ROOT] = tree_data[LM_TS_BUILD_MENU_ID];

            // outside_root[MENU_ID_OUTSIDE_ROOT] contains a gpointer value
            if (STREQ (inside_root[MENU_ID_INSIDE_ROOT], (gchar *) outside_root[MENU_ID_OUTSIDE_ROOT])) { 
                outside_root[ICON_PATH_OUTSIDE_ROOT] = &tree_data[LM_TS_BUILD_ICON_PATH];
                outside_root[LABEL_OUTSIDE_ROOT]     = tree_data[LM_TS_BUILD_MENU_ELEMENT];
                outside_root[EXECUTE_OUTSIDE_ROOT]   = tree_data[LM_TS_BUILD_EXECUTE];

                if (G_UNLIKELY (inside_root[LABEL_INSIDE_ROOT] && outside_root[LABEL_OUTSIDE_ROOT])) {
                    menu_has_label_inside_root_and_outside_root_for_same_ID = TRUE;
                }

                if (G_UNLIKELY (inside_root[ICON_PATH_INSIDE_ROOT] && *outside_root[ICON_PATH_OUTSIDE_ROOT])) {
                    menu_has_icon_path_inside_root_and_outside_root_for_same_ID = TRUE;

                    g_object_unref (tree_data[LM_TS_BUILD_ICON_IMG]);
                    g_free (tree_data[LM_TS_BUILD_ICON_MODIFICATION_TIME]);
                    g_free (tree_data[LM_TS_BUILD_ICON_PATH]);

                    outside_root[ICON_IMG_OUTSIDE_ROOT]               = &tree_data[LM_TS_BUILD_ICON_IMG];
                    outside_root[ICON_IMG_STATUS_OUTSIDE_ROOT]        = &tree_data[LM_TS_BUILD_ICON_IMG_STATUS];
                    outside_root[ICON_MODIFICATION_TIME_OUTSIDE_ROOT] = &tree_data[LM_TS_BUILD_ICON_MODIFICATION_TIME];
                }

                if (G_UNLIKELY (inside_root[EXECUTE_INSIDE_ROOT] && outside_root[EXECUTE_OUTSIDE_ROOT])) {
                    menu_has_execute_inside_root_and_outside_root_for_same_ID = TRUE;
                }

                if (G_UNLIKELY (menu_has_label_inside_root_and_outside_root_for_same_ID || 
                                menu_has_icon_path_inside_root_and_outside_root_for_same_ID || 
                                menu_has_execute_inside_root_and_outside_root_for_same_ID)) {
                    *menus_with_double_or_contradicting_attribute_values = 
                        g_slist_prepend (*menus_with_double_or_contradicting_attribute_values, 
                                         g_strdup (inside_root[MENU_ID_INSIDE_ROOT]));
                }

                break;
            }
        }

        // Cleanup
        free_elements_of_static_string_array (inside_root, NUMBER_OF_INSIDE_ROOT_ELEMENTS, FALSE);
    }


    // --- Retrieve attribute values


    if (streq_any (element_name, "menu", "item", "separator", NULL)) {
        txt_fields[TYPE_TXT] = element_name;

        for (attribute_cnt = 0; attribute_cnt < number_of_attributes; ++attribute_cnt) {
            current_attribute_name = attribute_names[attribute_cnt];
            current_attribute_value = attribute_values[attribute_cnt];

            if (STREQ (current_attribute_name, "label") && 
                G_LIKELY (!menu_has_label_inside_root_and_outside_root_for_same_ID)) {
                txt_fields[MENU_ELEMENT_TXT] = current_attribute_value;
                if (STREQ (element_name, "item")) {
                    FREE_AND_REASSIGN (menu_building->current_elements[LM_ITEM], g_strdup (current_attribute_value));
                }
            }
            if (!STREQ (element_name, "separator")) { // menu or item
                if (STREQ (current_attribute_name, "icon")) {
                    icon_path = g_strdup (current_attribute_value);
                }
                if (STREQ (element_name, "menu")) {
                    if (STREQ (current_attribute_name, "id")) {
                        /*
                            Root menu IDs are only included inside the menu_ids list 
                            if they did not already appear in an extern menu definition.
                        */
                        if (!(*loading_stage == LM_ROOT_MENU && 
                            (STREQ (current_attribute_value, "root-menu") || 
                             g_slist_find_custom (toplevel_menu_ids[LM_MENUS], current_attribute_value, (GCompareFunc) strcmp)))) {
                            *menu_ids = g_slist_prepend (*menu_ids, g_strdup (current_attribute_value));
                        }

                        if ((*loading_stage == LM_MENUS && current_path_depth == 1) || 
                            (*loading_stage == LM_ROOT_MENU && current_path_depth == 2)) { // This excludes the "root-menu" ID.
                            // TRUE -> LM_ROOT_MENU, FALSE -> LM_MENUS
                            GSList **menu_ids_list = &toplevel_menu_ids[(*loading_stage == LM_ROOT_MENU)];

                            *menu_ids_list = g_slist_prepend (*menu_ids_list, g_strdup (current_attribute_value));
                        }

                        txt_fields[MENU_ID_TXT] = current_attribute_value;
                    }
                    else if (STREQ (current_attribute_name, "execute")) {
                        txt_fields[TYPE_TXT] = "pipe menu"; // Overwrites "menu".
                        if (G_LIKELY (!menu_has_execute_inside_root_and_outside_root_for_same_ID)) {
                            txt_fields[EXECUTE_TXT] = current_attribute_value;
                        }
                    }
                }
            }
        }


        /*
            Create icon images.

            This has to follow after the retrieval loop, because if icon was the first attribute, 
            the other attributes would not have been retrieved yet; the latter are needed in case of an error 
            to compose an error message that informs about the ID (menu) or label (item), 
            so the element can be identified by the user.
        */

        if (icon_path) {
            GdkPixbuf *icon_in_original_size;

            g_autoptr(GError) icon_creation_error = NULL;
            gboolean *ignore_all_upcoming_errs = &(menu_building->ignore_all_upcoming_errs);

            if (G_UNLIKELY (!(icon_in_original_size = gdk_pixbuf_new_from_file (icon_path, &icon_creation_error)))) {
                g_autoptr(GFile) icon_path_file = g_file_new_for_path (icon_path);
                const gboolean file_exists = g_file_query_exists (icon_path_file, NULL);
                
                icon_img = gdk_pixbuf_copy (ks.invalid_icon_imgs[(file_exists)]); // INVALID_FILE_ICON (TRUE) or INVALID_PATH_ICON
                icon_img_status = (file_exists) ? INVALID_FILE : INVALID_PATH;

                if (file_exists) {
                    icon_modification_time = get_modification_time_for_icon (icon_path);
                }

                if (!(*ignore_all_upcoming_errs)) {
                    enum { CHOOSE_FILE = 1, CHECK_LATER, IGNORE_ALL_UPCOMING_ERRORS_AND_CHECK_LATER };

                    g_autofree gchar *icon_path_error_loop = g_strdup (icon_path);

                    while (icon_creation_error) {
                        GtkWidget *dialog;
                        g_autoptr(GString) dialog_txt = g_string_new (NULL);
                        gchar *icon_path_selected;
                        gint result;

                        // Translation note: "Line" (German "Zeile") means a line in a text here, e.g. "Line 57"
                        g_string_append_printf (dialog_txt, _("<b>Line %i:\n"), line_number);
                        if (streq_any (txt_fields[TYPE_TXT], "menu", "pipe menu", NULL)) {
                            g_string_append_printf (dialog_txt, _("The following error occurred while trying to create an icon for a "
                                                                  "menu with the menu ID \"%1$s\" from </b><tt>%2$s</tt>:\n\n"), 
                                                    txt_fields[MENU_ID_TXT], icon_path_error_loop);
                        }
                        else { // item
                            if (txt_fields[MENU_ELEMENT_TXT]) {
                                g_string_append_printf (dialog_txt, _("The following error occurred while trying to create "
                                                                       "an icon for an item with the label \"%1$s\" from </b><tt>%2$s</tt>:\n\n"), 
                                                                    txt_fields[MENU_ELEMENT_TXT], icon_path_error_loop);
                            }
                            else {
                                g_string_append_printf (dialog_txt, _("The following error occurred while trying to create an icon "
                                                                      "for an item without an assigned label from </b><tt>%s</tt>:\n\n"), 
                                                                    icon_path_error_loop);
                            }
                        }
                        g_string_append_printf (dialog_txt, _("<b><span foreground='%s'>%s</span></b>\n\n"
                                                              "It is not mandatory to choose the correct/another file now. "
                                                              "This single or all following icon creation error messages "
                                                              "can be ignored, and the erroneous paths can be checked "
                                                              "later from inside the program. "
                                                              "In this case, all nodes that contain menus and items "
                                                              "with invalid icon paths will be shown expanded "
                                                              "after the loading process."), 
                                                              ks.red_hue, icon_creation_error->message);

                        create_dialog (&dialog, _("Icon Creation Error"), "dialog-error", dialog_txt->str, 
                                       _("_Choose File"), _("Check _Later"), _("_Ignore All Errors and Check Later"), SHOW_IMMEDIATELY);

                        result = gtk_dialog_run (GTK_DIALOG (dialog));
                        gtk_widget_destroy (dialog);
                        switch (result) {
                            case CHOOSE_FILE:
                                if ((icon_path_selected = choose_icon ())) {
                                    // Cleanup and reset
                                    g_clear_error (&icon_creation_error);
                                    FREE_AND_REASSIGN (icon_path_error_loop, icon_path_selected);

                                    if (G_LIKELY ((icon_in_original_size = gdk_pixbuf_new_from_file (icon_path_error_loop, 
                                                                                                     &icon_creation_error)))) {
                                        FREE_AND_REASSIGN (icon_path, g_strdup (icon_path_error_loop));
                                        g_object_unref (icon_img);
                                        icon_img_status = NONE_OR_NORMAL;
                                        menu_building->icon_info_corrected = TRUE;
                                        menu_building->change_done_loading_proc = TRUE;
                                    }
                                }
                                break;
                            default: // If the dialog window is closed all upcoming errors will be ignored.
                                *ignore_all_upcoming_errs = (result != CHECK_LATER);

                                // Cleanup and reset
                                g_clear_error (&icon_creation_error);
                        }
                    }
                }
            }

            if (G_LIKELY (!icon_img_status)) { // Icon file and path without errors
                icon_img = gdk_pixbuf_scale_simple (icon_in_original_size, 
                                                    ks.font_size + 10, ks.font_size + 10, 
                                                    GDK_INTERP_BILINEAR);

                FREE_AND_REASSIGN (icon_modification_time, get_modification_time_for_icon (icon_path));

                // Cleanup
                g_object_unref (icon_in_original_size);
            }

            /* If there is a menu that has both icon attributes inside and outside the root menu for the same ID, 
               the ones of the menu outside the root menu are overwritten by the ones inside the root menu.
               Afterwards, the variables for the latter are freed and set to NULL. */
            if (G_UNLIKELY (menu_has_icon_path_inside_root_and_outside_root_for_same_ID)) {
                *outside_root[ICON_IMG_OUTSIDE_ROOT] = gdk_pixbuf_copy (icon_img);
                g_clear_pointer (&icon_img, (GDestroyNotify) g_object_unref);

                *outside_root[ICON_IMG_STATUS_OUTSIDE_ROOT] = GUINT_TO_POINTER (icon_img_status);
                icon_img_status = NONE_OR_NORMAL; // "None" in this case.

                *outside_root[ICON_MODIFICATION_TIME_OUTSIDE_ROOT] = g_strdup (icon_modification_time);
                g_clear_pointer (&icon_modification_time, (GDestroyNotify) g_free);

                *outside_root[ICON_PATH_OUTSIDE_ROOT] = g_strdup (icon_path);
                g_clear_pointer (&icon_path, (GDestroyNotify) g_free);
            }
        }
    }
    else if (STREQ (element_name, "action")) {
        txt_fields[TYPE_TXT] = element_name;
        txt_fields[MENU_ELEMENT_TXT] = attribute_values[0]; // There is only one attribute.
        FREE_AND_REASSIGN (menu_building->current_elements[LM_ACTION], g_strdup (attribute_values[0]));
    }
    else if (g_regex_match_simple ("prompt|command|execute|startupnotify|enabled|wmclass|name|icon", 
             element_name, 0, G_REGEX_MATCH_ANCHORED)) {
        if (!STREQ (element_name, "startupnotify")) {
            txt_fields[TYPE_TXT] = "option";
            menu_building->open_option_element = TRUE;
        }
        else  {
            txt_fields[TYPE_TXT] = "option block";
        }

        txt_fields[MENU_ELEMENT_TXT] = element_name;

        if (G_UNLIKELY (STREQ (txt_fields[MENU_ELEMENT_TXT], "execute"))) {
            txt_fields[MENU_ELEMENT_TXT] = "command";
            menu_building->deprecated_exe_cmd_cvtd = TRUE;
            menu_building->change_done_loading_proc = TRUE;
        }
    }


    // --- Store all values that are needed later to create a treeview row. ---


    gpointer *tree_data = g_malloc (sizeof (gpointer) * LM_NUMBER_OF_TS_BUILD_FIELDS);

    tree_data[LM_TS_BUILD_ICON_IMG] = icon_img;
    tree_data[LM_TS_BUILD_ICON_IMG_STATUS] = GUINT_TO_POINTER ((guint) icon_img_status);
    tree_data[LM_TS_BUILD_ICON_MODIFICATION_TIME] = icon_modification_time;
    tree_data[LM_TS_BUILD_ICON_PATH] = icon_path;
    /*
        txt_fields starts with ICON_PATH_TXT, but this array element is not used here and replaced by icon_path, 
        since there are separate variables here for all treestore fields that refer to an icon image.
    */
    for (guint8 ts_build_cnt = LM_TS_BUILD_MENU_ELEMENT; ts_build_cnt < LM_TS_BUILD_PATH_DEPTH; ++ts_build_cnt) {
        tree_data[ts_build_cnt] = g_strdup (txt_fields[ts_build_cnt - 3]);
    }
    tree_data[LM_TS_BUILD_PATH_DEPTH] = GUINT_TO_POINTER (current_path_depth);

    menu_building->tree_build = g_slist_prepend (menu_building->tree_build, tree_data);


    // --- Preparations for further processing ---


    menu_building->previous_path_depth = current_path_depth;
    FREE_AND_REASSIGN (menu_building->previous_type, g_strdup (txt_fields[TYPE_TXT]));

    if (current_path_depth > menu_building->max_path_depth) {
        menu_building->max_path_depth = current_path_depth;
    }
    ++(menu_building->current_path_depth);
}

/* 

    Parses end elements.

*/

static void end_tags (G_GNUC_UNUSED       GMarkupParseContext  *parse_context, 
                      G_GNUC_UNUSED const gchar                *element_name,
                                          gpointer              menu_building_pnt, 
                      G_GNUC_UNUSED       GError              **error)
{
    MenuBuildingData *menu_building = (MenuBuildingData *) menu_building_pnt;

    if (menu_building->root_menu_finished) { // XML after the root menu is discarded by Openbox.
        return;
    }

    if (streq_any (element_name, "item", "action", NULL)) {
        FREE_AND_REASSIGN (menu_building->current_elements[STREQ (element_name, "action")], NULL);
    }
    guint current_path_depth = --(menu_building->current_path_depth);

    if (menu_building->loading_stage == LM_ROOT_MENU && current_path_depth == 1) {
        menu_building->root_menu_finished = TRUE;
    }
}

/* 

    Parses text from inside an element.

*/

static void element_content (G_GNUC_UNUSED       GMarkupParseContext  *parse_context, 
                                           const gchar                *text, 
                             G_GNUC_UNUSED       gsize                 text_len, 
                                                 gpointer              menu_building_pnt, 
                             G_GNUC_UNUSED       GError              **error)
{
    MenuBuildingData *menu_building = (MenuBuildingData *) menu_building_pnt;

    // XML after the root menu is discarded by Openbox.
    if (!menu_building->tree_build || menu_building->root_menu_finished) {
        return;
    }

    gpointer *tree_data = menu_building->tree_build->data;

    if (menu_building->open_option_element) {
        g_autofree gchar *nul_terminated_text = g_strndup (text, text_len);
        gchar *current_element = tree_data[LM_TS_BUILD_MENU_ELEMENT];
        gchar *current_action = menu_building->current_elements[LM_ACTION];

        /*
            Avoids that spaces and returns are taken over in cases like
            <command>
                some_program
            </command>

            This will be written back as <command>some_program</command>
        */
        if (STREQ (tree_data[LM_TS_BUILD_TYPE], "option")) {
            g_strstrip (nul_terminated_text);
        }

        if (G_LIKELY (!((STREQ (current_element, "enabled") || 
                        (STREQ (current_element, "prompt") && 
                        streq_any (current_action, "Exit", "SessionLogout", NULL))) && 
                        !streq_any (nul_terminated_text, "yes", "no", NULL)))) {
            tree_data[LM_TS_BUILD_VALUE] = g_strdup (nul_terminated_text);
        }
        else {
            gchar *current_item = menu_building->current_elements[LM_ITEM];

            GtkWidget *dialog;
            g_autofree gchar *line_with_escaped_markup_txt;
            gchar *dialog_title_txt;
            g_autoptr(GString) dialog_txt = g_string_new (NULL);

            gint result;

            #define CHANGE_INVALID_OPTION_VALUE_TO_YES 1

            line_with_escaped_markup_txt = g_strstrip (g_markup_escape_text (menu_building->line, -1));
            dialog_title_txt = (STREQ (current_element, "enabled")) ? 
			                   // Translation note: Do not translate "Enabled" here
			                   _("Enabled Option Has Invalid Value") : 
							   // Translation note: Do not translate "Prompt" here
							   _("Prompt Option Has Invalid Value");
            // Translation note: "Line" (German "Zeile") means a line in a text here, e.g. "Line 57"
            g_string_append_printf (dialog_txt, _("<b>Line %i:</b>\n<tt>%s</tt>\n\n"), menu_building->line_number, line_with_escaped_markup_txt);

            if (current_item) {
                if (STREQ (current_action, "Exit")) {
					// Translation note: Do not translate "Exit", "prompt"
                    g_string_append_printf (dialog_txt, _("An item labeled <b>\"%1$s\"</b> contains an <b>\"Exit\" action</b> that has a "
                                                          "<b>\"prompt\" option</b> with the <b>invalid value <span foreground='%2$s'>%3$s</span></b>."),
                                                        current_item, ks.red_hue, nul_terminated_text);
                }
                else if (STREQ (current_action, "SessionLogout")) {
					// Translation note: Do not translate "SessionLogout", "prompt"
                    g_string_append_printf (dialog_txt, _("An item labeled <b>\"%1$s\"</b> contains a <b>\"SessionLogout\" action</b> that has a "
                                                           "<b>\"prompt\" option</b> with the <b>invalid value <span foreground='%2$s'>%3$s</span></b>."),
                                                         current_item, ks.red_hue, nul_terminated_text);
                }
                else { // Execute
				    // Translation note: Do not translate "Execute", "enabled"
                    g_string_append_printf (dialog_txt, _("An item labeled <b>\"%1$s\"</b> contains an <b>\"Execute\" action</b> that has an "
                                                          "<b>\"enabled\" option</b> with the <b>invalid value <span foreground='%2$s'>%3$s</span></b>."),
                                                        current_item, ks.red_hue, nul_terminated_text);
                }
            }

            // Translation note: Do not translate "Yes", "No"
            g_string_append (dialog_txt, _("\n\nPlease choose <b>either \"Yes\" or \"No\"</b> for the option."));

            create_dialog (&dialog, dialog_title_txt, "dialog-error", dialog_txt->str, "_Yes", "_No", NULL, SHOW_IMMEDIATELY);

            result = gtk_dialog_run (GTK_DIALOG (dialog));
            gtk_widget_destroy (dialog);
            tree_data[LM_TS_BUILD_VALUE] = g_strdup ((result == CHANGE_INVALID_OPTION_VALUE_TO_YES) ? "yes" : "no");
            menu_building->invalid_option_value_corrected = TRUE;
            menu_building->change_done_loading_proc = TRUE;
        }
        menu_building->open_option_element = FALSE;
    }
}

/* 

    Sets visibility value of menus, pipe menus, items, and separators and adds menus and items without labels to a list.

*/

static gboolean elements_visibility (GtkTreeModel  *foreach_model,
                                     GtkTreePath   *foreach_path,
                                     GtkTreeIter   *foreach_iter,
                                     GSList       **invisible_elements_lists)
{
    g_autofree gchar *type_txt, 
                     *element_visibility_txt;

    gtk_tree_model_get (foreach_model, foreach_iter, 
                        TS_TYPE, &type_txt, 
                        TS_ELEMENT_VISIBILITY, &element_visibility_txt, 
                        -1);

    if (streq_any (type_txt, "action", "option", "option block", NULL) || STREQ (element_visibility_txt, "visible")) {
        return FALSE;
    }

    const guint8 element_visibility_ancestor = check_if_invisible_ancestor_exists (foreach_model, foreach_path);
    g_autofree gchar *menu_element_txt;

    gtk_tree_model_get (foreach_model, foreach_iter, TS_MENU_ELEMENT, &menu_element_txt, -1);

    if (!STREQ (element_visibility_txt, "invisible orphaned menu")) {
        const gchar *new_element_visibility_txt;

        if (element_visibility_ancestor == INVISIBLE_ORPHANED_ANCESTOR) {
            new_element_visibility_txt = "invisible dsct. of invisible orphaned menu";
        }
        else {
            if (element_visibility_ancestor) {
                new_element_visibility_txt = "invisible dsct. of invisible menu";
            }
            else {
                if (menu_element_txt || STREQ (type_txt, "separator")) {
                    new_element_visibility_txt = "visible";
                }
                else {
                    new_element_visibility_txt = (STREQ (type_txt, "item")) ? "invisible item" : "invisible menu";
                }
            }
        }

        gtk_tree_store_set (ks.treestore, foreach_iter, TS_ELEMENT_VISIBILITY, new_element_visibility_txt, -1);
    }

    /*
        If the function is called from the "Missing Labels" dialog, the invisible elements lists were already built in a 
        previous call of this function. In this case, NULL instead of the invisible elements lists has been sent as 
        a parameter, so the following if/else statement is not executed.
    */
    if (invisible_elements_lists && !menu_element_txt && !STREQ (type_txt, "separator")) {
        GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
        const gint current_path_depth = gtk_tree_path_get_depth (foreach_path);
        GSList **label_list = &invisible_elements_lists[(STREQ (type_txt, "item")) ? LM_ITEMS_LIST : LM_MENUS_LIST];
        gchar *list_elm_txt = NULL; // Default, symbolizes a toplevel item without label.

        if (!STREQ (type_txt, "item")) {
            gtk_tree_model_get (foreach_model, foreach_iter, TS_MENU_ID, &list_elm_txt, -1);
        }
        else if (current_path_depth > 1) {
            GtkTreeIter iter_ancestor;
            g_autofree gchar *menu_id_txt_ancestor;

            gtk_tree_model_iter_parent (foreach_model, &iter_ancestor, foreach_iter);
            gtk_tree_model_get (foreach_model, &iter_ancestor, TS_MENU_ID, &menu_id_txt_ancestor, -1);

            list_elm_txt = g_strdup_printf (_("Child of menu with id \"%s\""), menu_id_txt_ancestor);
        }
 
        *label_list = g_slist_prepend (*label_list, list_elm_txt);

        // To select an iter, the equivalent row has to be visible.
        if (current_path_depth > 1 &&
            !gtk_tree_view_row_expanded (GTK_TREE_VIEW (ks.treeview), foreach_path)) {
             gtk_tree_view_expand_to_path (GTK_TREE_VIEW (ks.treeview), foreach_path);
        }
        gtk_tree_selection_select_iter (selection, foreach_iter);       
    }

    return FALSE;
}

/* 

    Creates dialogs that ask about the handling of invisible menus and items.

*/

static void create_dialogs_for_invisible_menus_and_items (guint8             dialog_type, 
                                                          GtkTreeSelection  *selection, 
                                                          GSList           **invisible_elements_lists)
{
    // This makes sure that static analyzers don't assume that there can be other values.
#ifndef G_DISABLE_ASSERT
    g_assert (dialog_type == LM_ORPHANED_MENUS || dialog_type == LM_MISSING_LABELS);
#endif

    GtkWidget *dialog, *content_area, *headline_grid, *label;
    const gchar *dialog_title_txt, *dialog_headline_txt;
    g_autofree gchar *dialog_txt;

    gint result;

    GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);

    GtkWidget *menu_grid;

    guint  menus_list_len, items_list_len;
    guint *lists_len[] = { &menus_list_len, &items_list_len };
    guint  number_of_toplvl_items_without_label = 0;

    guint grid_row_cnt = 0;
    guint8 index, lists_cnt;

    GSList *invisible_elements_lists_loop[2], *invisible_elements_lists_subloop;
    GtkTreeIter iter_loop;
    gboolean valid;

    enum { VISUALIZE = 1, KEEP_STATUS, DELETE };

    // Preliminary work - Creating dialog title and headline, lists for the orphaned menus dialog.

    if (dialog_type == LM_ORPHANED_MENUS) {
        // Create menu element and menu ID lists. 
        valid = gtk_tree_model_iter_nth_child (ks.ts_model, &iter_loop, NULL, gtk_tree_model_iter_n_children (ks.ts_model, NULL) - 1);
        while (valid) {
            g_autofree gchar *element_visibility_txt_loop;

            gtk_tree_model_get (ks.ts_model, &iter_loop, TS_ELEMENT_VISIBILITY, &element_visibility_txt_loop, -1);

            if (!STREQ (element_visibility_txt_loop, "invisible orphaned menu")) {
                break;
            }

            gchar *menu_element_txt_loop, 
                  *menu_id_txt_loop;

            gtk_tree_model_get (ks.ts_model, &iter_loop, 
                                TS_MENU_ELEMENT, &menu_element_txt_loop,
                                TS_MENU_ID, &menu_id_txt_loop,
                                -1);

            invisible_elements_lists[LM_MENU_IDS_LIST] = g_slist_prepend (invisible_elements_lists[LM_MENU_IDS_LIST], 
                                                                          menu_id_txt_loop);
            invisible_elements_lists[LM_MENU_ELEMENTS_LIST] = g_slist_prepend (invisible_elements_lists[LM_MENU_ELEMENTS_LIST], 
                                                                               menu_element_txt_loop);

            gtk_tree_selection_select_iter (selection, &iter_loop);
            valid = gtk_tree_model_iter_previous (ks.ts_model, &iter_loop);
        }

        invisible_elements_lists_loop[0] = invisible_elements_lists[LM_MENU_IDS_LIST];
        invisible_elements_lists_loop[1] = invisible_elements_lists[LM_MENU_ELEMENTS_LIST];

        // Create dialog title and headline.
        const guint number_of_orphaned_menus = g_slist_length (invisible_elements_lists[LM_MENU_IDS_LIST]);
        dialog_title_txt = ngettext ("Invisible Orphaned Menu Found", "Invisible Orphaned Menus Found", (number_of_orphaned_menus == 1));
        dialog_headline_txt = ngettext ("The following menu is <b>defined outside</b> the root menu,\n"
                                        "but <b><u>isn't used</u> inside it:</b>", 
                                        "The following menus are <b>defined outside</b> the root menu,\n"
                                        "but <b><u>aren't used</u> inside it:</b>", 
                                        (number_of_orphaned_menus == 1));
    }
    else { // dialog_type = LM_MISSING_LABELS
        // Create dialog title and headline
        menus_list_len = g_slist_length (invisible_elements_lists[LM_MENUS_LIST]);
        items_list_len = g_slist_length (invisible_elements_lists[LM_ITEMS_LIST]);

        if (menus_list_len > 1 && items_list_len > 1) {
            dialog_title_txt = _("Menus and Items Without Label Found");
            dialog_headline_txt = _("The following menus and items <b><u>have no label</u></b>");
        }
        else if (menus_list_len == 1 && items_list_len > 1) {
            dialog_title_txt = _("Menu and Items Without Label Found");
            dialog_headline_txt = _("The following menu and items <b><u>have no label</u></b>");
        }
        else if (menus_list_len > 1 && items_list_len == 1) {
            dialog_title_txt = _("Menus and Item Without Label Found");
            dialog_headline_txt = _("The following menus and item <b><u>have no label</u></b>");
        }
        else if (menus_list_len == 1 && items_list_len == 1) {
            dialog_title_txt = _("Menu and Item Without Label Found");
            dialog_headline_txt = _("The following menu and item <b><u>have no label</u></b>");
        }
        else if (menus_list_len > 1 && items_list_len == 0) {
            dialog_title_txt = _("Menus Without Label Found");
            dialog_headline_txt = _("The following menus <b><u>have no label</u></b>");
        }
        else if (menus_list_len == 1 && items_list_len == 0) {
            dialog_title_txt = _("Menu Without Label Found");
            dialog_headline_txt = _("The following menu <b><u>has no label</u></b>");
        }
        else if (menus_list_len == 0 && items_list_len > 1) { 
            dialog_title_txt = _("Items Without Label Found");
            dialog_headline_txt = _("The following items <b><u>have no label</u></b>");
        }
        else { // menus_list_len == 0 && items_list_len == 1
            dialog_title_txt = _("Item Without Label Found");
            dialog_headline_txt = _("The following item <b><u>has no label</u></b>");
        }
    }

    // Create menu grid that contains the list(s) of invisible menu elements.

    menu_grid = gtk_grid_new ();

    for (lists_cnt = 0; lists_cnt <= dialog_type; ++lists_cnt) {
        if (dialog_type == LM_MISSING_LABELS) {
            if (invisible_elements_lists[lists_cnt]) {
                const gchar *headline;

                // Display headlines for the missing labels lists.
                invisible_elements_lists[lists_cnt] = g_slist_reverse (invisible_elements_lists[lists_cnt]);

                if (lists_cnt == LM_MENUS_LIST) {
                    headline = ngettext ("\n<b>Menu:</b> (Menu ID shown)\n", "\n<b>Menus:</b> (Menu IDs shown)\n", (*(lists_len[lists_cnt]) == 1));
                }
                else { // LM_ITEMS_LIST
                    headline = ngettext ("\n<b>Item:</b> (Location shown)\n", "\n<b>Items:</b> (Locations shown)\n", (*(lists_len[lists_cnt]) == 1));
                }

                gtk_grid_attach (GTK_GRID (menu_grid), new_label_with_formattings (headline, FALSE), 0, grid_row_cnt++, 1, 1);

                // Display the number of toplevel items without label.
                if (lists_cnt == LM_ITEMS_LIST) {
                    FOREACH_IN_LIST (invisible_elements_lists[LM_ITEMS_LIST], invisible_elements_lists_subloop) {
                        if (!invisible_elements_lists_subloop->data) {
                            ++number_of_toplvl_items_without_label;
                        }
                    }
 
                    if (number_of_toplvl_items_without_label) {
                        g_autoptr(GString) cell_txt = g_string_new (NULL);
                        /*
                            If the number of toplevel items without label is below 10, 
                            the number is replaced with its spelled out equivalent so 
                            the whole sentence looks less machinery processed.
                        */
                        const gchar *small_numbers_spelled_out[] = { N_("One toplevel item"), N_("Two toplevel items"), N_("Three toplevel items"), 
                                                                     N_("Four toplevel items"), N_("Five toplevel items"), N_("Six toplevel items"), 
                                                                     N_("Seven toplevel items"), N_("Eight toplevel items"), N_("Nine toplevel items") };

                        if (number_of_toplvl_items_without_label < 10) {
                            g_string_assign (cell_txt, _(small_numbers_spelled_out[number_of_toplvl_items_without_label - 1]));
                        }
                        else {
                            g_string_printf (cell_txt, ngettext ("%i toplevel items", "%i toplevel items", number_of_toplvl_items_without_label), number_of_toplvl_items_without_label);
                        }

                        if (dialog_type == LM_MISSING_LABELS && number_of_toplvl_items_without_label == items_list_len) {
                            g_string_append (cell_txt, "\n");
                        }

                        gtk_grid_attach (GTK_GRID (menu_grid), new_label_with_formattings (cell_txt->str, FALSE), 0, grid_row_cnt++, 1, 1);

                        // The number of toplevel items w/o label has been displayed, so these labels can be removed from the list.
                        invisible_elements_lists[lists_cnt] = g_slist_remove_all (invisible_elements_lists[LM_ITEMS_LIST], NULL);
                    }
                }
            }
            invisible_elements_lists_loop[lists_cnt] = invisible_elements_lists[lists_cnt];
        }

        // Display the orphaned menus and missing labels lists.

        while (invisible_elements_lists_loop[lists_cnt]) {
            for (guint8 grid_column_cnt = 0; grid_column_cnt <= (dialog_type == LM_ORPHANED_MENUS); ++grid_column_cnt) {
                g_autofree gchar *cell_txt;

                index = ((dialog_type == LM_ORPHANED_MENUS && grid_column_cnt == 1) || 
                         (dialog_type == LM_MISSING_LABELS && lists_cnt == 1));

                /*
                    The list construction loop covers both dialogs:
                    For the 
                    - LM_ORPHANED_MENUS dialog it creates one list with two columns
                    - LM_MISSING_LABELS dialog it creates one or two lists with one column

                    If dialog_type == LM_ORPHANED_MENUS, there are two columns (=two loop iterations): 
                    One for the menu ID and one for the label (if it doesn't exist, the column is left blank).
                    The loop is called once, if there are orphaned menus.

                    If dialog_type == LM_MISSING_LABELS, there is one column (=one loop iteration).
                    The loop is called once, if there are either menus OR items without labels, 
                    or twice, if there are menus AND items without labels.
                    An additional new line is added to the end of the list of menus with missing labels if 
                    it is followed by a list of items with missing labels.
                */

                cell_txt = g_strdup_printf ("%s%s%s%s  ", 
                                            // Spacing before first row, if the current list is "Orphaned Menus"
                                            (dialog_type == LM_ORPHANED_MENUS && grid_row_cnt == 0) ? "\n" : "", 
                                            // Designation (only if current list is "Orphaned Menus")
                                            (dialog_type == LM_MISSING_LABELS || 
                                            (grid_column_cnt == 1 && !invisible_elements_lists_loop[LM_MENU_ELEMENTS_LIST]->data)) ? 
                                            "" : ((grid_column_cnt == 0) ? _("<b>Menu ID:</b> ") : 
                                            (g_slist_length (invisible_elements_lists[grid_column_cnt]) == 1) ? 
                                            _(" <b>Label:</b> ") : _("<b>Label:</b> ")), 
                                            // Orphaned or invisible menu element
                                            (dialog_type == LM_ORPHANED_MENUS && 
                                            !invisible_elements_lists_loop[grid_column_cnt]->data) ? 
                                            "" : (gchar *) invisible_elements_lists_loop[index]->data, 
                                            // Spacing to the next list, if it exists
                                            (invisible_elements_lists_loop[index]->next || 
                                            (!invisible_elements_lists_loop[index]->next && dialog_type == LM_MISSING_LABELS && 
                                            lists_cnt == LM_MENUS_LIST && invisible_elements_lists[LM_ITEMS_LIST])) ? "" : "\n");

                gtk_grid_attach (GTK_GRID (menu_grid), new_label_with_formattings (cell_txt, FALSE), 
                                 grid_column_cnt, grid_row_cnt, 1, 1);

                invisible_elements_lists_loop[index] = invisible_elements_lists_loop[index]->next;
            }
            ++grid_row_cnt;
        }
    }

    /* If the screen resolution is not very high and the standard font quite large, 
       the dialog window created by create_dialog () might turn out as too big, with the buttons at the bottom being inaccessible.
       That is why a custom dialog is created here. Besides that, this dialog's layout suits the purpose better. */

    dialog = gtk_dialog_new_with_buttons (NULL, GTK_WINDOW (ks.window), GTK_DIALOG_MODAL, 
	                                      // Translation note: "Visualize" in the sense of making something visible
                                          _("_Visualize"), 1, 
										  _("_Keep Status"), 2, 
										  _("_Delete"), 3,
                                          NULL);
    gtk_widget_set_size_request (dialog, 570, -1);

    set_header_bar_for_dialog (dialog, dialog_title_txt);

    content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
    gtk_container_set_border_width (GTK_CONTAINER (content_area), 10);

    headline_grid = gtk_grid_new ();
    gtk_widget_set_margin_bottom (headline_grid, 10);
    gtk_container_add (GTK_CONTAINER (headline_grid), gtk_image_new_from_icon_name ("dialog-information", GTK_ICON_SIZE_DIALOG));
    label = gtk_label_new (NULL);
    gtk_widget_set_margin_start (label, 10);
    gtk_label_set_markup (GTK_LABEL (label), dialog_headline_txt);
    gtk_container_add (GTK_CONTAINER (headline_grid), label);
    gtk_container_add (GTK_CONTAINER (content_area), headline_grid);

    // Add the rest of the dialog components and show the dialog.

    gtk_container_add (GTK_CONTAINER (scrolled_window), menu_grid);

    gtk_container_add (GTK_CONTAINER (content_area), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
    gtk_container_add (GTK_CONTAINER (content_area), scrolled_window);
    gtk_container_add (GTK_CONTAINER (content_area), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));

    g_autofree gchar *after_keep_status = g_strdup_printf ("%s%s", 
	                                                       // Translation note: "Visualization" in the sense of making something visible
                                                           _("Even after choosing \"<b>Keep Status</b>\" now, "
                                                             "the visualization can still be realized from inside the program. "), 
                                                           (dialog_type == LM_ORPHANED_MENUS) ?
														   // Translation note: "Visualize" in the sense of making something visible
                                                           _("This can be done by selecting the menus and either\n"
                                                             "<b>&#8226;</b> choosing \"<b>Edit</b> -> "
                                                             "<b>Visualize</b>/<b>Visualize recursively</b>\" "
                                                             "from the application menu or\n"
                                                             "<b>&#8226;</b> by rightclicking them and choosing "
                                                             "\"<b>Visualize</b>/<b>Visualize recursively</b>\" "
                                                             "from the context menu.") : 
														   // Translation note: "Visualize" in the sense of making something visible
                                                           _("This can be done by selecting the menus/items and either\n"
                                                             "<b>&#8226;</b> choosing \"<b>Edit</b> -> "
                                                             "<b>Visualize</b>/<b>Visualize recursively</b>\" "
                                                             "from the application menu or\n"
                                                             "<b>&#8226;</b> by rightclicking them and choosing "
                                                             "\"<b>Visualize</b>/<b>Visualize recursively</b>\" "
                                                             "from the context menu."));
    const gchar *core_msg, *highlighting;
    const gchar *separation = "\n<span size='3000'> </span>\n";

    if (dialog_type == LM_ORPHANED_MENUS) {
		// Translation note: "Visualize" in the sense of making something visible
        core_msg = _("\nMenus defined outside the root menu that don't appear inside it are not shown by Openbox. "
                     "They can be integrated by choosing \"<b>Visualize</b>\" here.");
        highlighting = _("Invisible orphaned menus and their children are "
                         "<b><span background='#364074' foreground='white'>highlighted in blue</span></b>.\n");
    }
    else { // LM_MISSING_LABELS
	    // Translation note: "Visualize" in the sense of making something visible
        core_msg = _("\nMenus and items without label are not shown by Openbox. "
                     "They can be visualized by creating a label for each one of them, "
                     "which is done by choosing \"<b>Visualize</b>\" here.");
        highlighting = _("Menus and items without label and their children are "
                         "<b><span background='#ff6b00' foreground='white'>highlighted in orange</span></b>.\n");
    }

    dialog_txt = g_strconcat (core_msg, separation, after_keep_status, separation, highlighting, NULL);

    label = new_label_with_formattings (dialog_txt, TRUE);
    gtk_widget_set_margin_bottom (label, 10);
    gtk_container_add (GTK_CONTAINER (content_area), label);

    // Cleanup
    for (lists_cnt = 0; lists_cnt < LM_NUMBER_OF_INVISIBLE_ELEMENTS_LISTS; ++lists_cnt) {
#if GLIB_CHECK_VERSION(2,64,0)
        g_clear_slist (&invisible_elements_lists[lists_cnt], (GDestroyNotify) g_free);
#else
        g_slist_free_full (invisible_elements_lists[lists_cnt], (GDestroyNotify) g_free);
        invisible_elements_lists[lists_cnt] = NULL;
#endif
    }

    gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ALWAYS);
    gtk_widget_show_all (dialog);
    gtk_widget_set_size_request (scrolled_window, -1, MIN (gtk_widget_get_allocated_height (menu_grid), 125));

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

    switch (result) {
        case VISUALIZE:
            {
#if GLIB_CHECK_VERSION(2,56,0)
                g_autolist(GtkTreePath) selected_rows = gtk_tree_selection_get_selected_rows (selection, &ks.ts_model);
#else
                GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, &ks.ts_model);
#endif
                GList *selected_rows_loop;

                FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
                    gtk_tree_model_get_iter (ks.ts_model, &iter_loop, selected_rows_loop->data);

                    if (dialog_type == LM_ORPHANED_MENUS) {
                        g_autofree gchar *menu_element_txt_loop;

                        gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ELEMENT, &menu_element_txt_loop, -1);
                    
                         // The visibility of subrows is set after the function has been left.
                         gtk_tree_store_set (ks.treestore, &iter_loop, TS_ELEMENT_VISIBILITY, 
                                             (menu_element_txt_loop) ? "visible" : "invisible menu", 
                                             -1);
                        }
                        else { // dialog_type = LM_MISSING_LABELS
                           g_autofree gchar *element_visibility_txt_loop;
                
                        gtk_tree_store_set (ks.treestore, &iter_loop, TS_MENU_ELEMENT, _("= Newly created label ="), -1);
                        gtk_tree_model_get (ks.ts_model, &iter_loop, TS_ELEMENT_VISIBILITY, &element_visibility_txt_loop, -1)    ;

                        // The visibility of subrows is set after the function has been left.
                        if (!g_str_has_suffix (element_visibility_txt_loop, "invisible orphaned menu")) {
                            gtk_tree_store_set (ks.treestore, &iter_loop, TS_ELEMENT_VISIBILITY, "visible", -1);
                        }
                    }
                }

                ks.loading_process_edit_types[INVISIBLE_ELEMENT_VISUALIZED_OR_REMOVED] = TRUE;
                ks.change_done = TRUE;

#if !(GLIB_CHECK_VERSION(2,56,0))
                // Cleanup
                g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
#endif
            }
            break;
        case KEEP_STATUS:
            break;
        case DELETE:
            remove_rows ("load menu");
            ks.loading_process_edit_types[INVISIBLE_ELEMENT_VISUALIZED_OR_REMOVED] = TRUE;
            ks.change_done = TRUE;
    }

    gtk_tree_selection_unselect_all (selection);
}

/* 

    Parses a menu file and appends collected elements to the tree view.

*/

gboolean get_menu_elements_from_file (gchar *new_filename)
{
    g_autoptr(GFile) file = g_file_new_for_path (new_filename);
    g_autoptr(GFileInputStream) file_input_stream;
    g_autoptr(GError) error = NULL;

    if (G_UNLIKELY (!((file_input_stream = g_file_read (file, NULL, &error))))) {
        g_autofree gchar *err_msg = g_strdup_printf (_("<b>Could not open menu file</b> <tt>%s</tt> <b>for reading!\n"
                                                       "Error:</b> %s"), new_filename, error->message);

        show_errmsg (err_msg);

        // Cleanup
        g_free (new_filename);

        return FALSE;
    }

    MenuBuildingData menu_building = {
        .line =                                                NULL, 
        .line_number =                                         1, 
        .tree_build =                                          NULL, 
        .current_path_depth =                                  1, 
        .previous_path_depth =                                 1, 
        .max_path_depth =                                      1, 
        .menu_ids =                                            NULL, 
        .toplevel_menu_ids =                                   { NULL }, 
        .menus_with_double_or_contradicting_attribute_values = NULL,
        .ignore_all_upcoming_errs =                            FALSE, 
        .current_elements =                                    { NULL }, 
        .open_option_element =                                 FALSE, 
        .previous_type =                                       NULL, 
        .deprecated_exe_cmd_cvtd =                             FALSE, 
        .loading_stage =                                       LM_MENUS, 
        .change_done_loading_proc =                            FALSE, 
        .root_menu_finished =                                  FALSE
    };

#if GLIB_CHECK_VERSION(2,56,0)
    /* This has to be in front of the "goto parsing abort;", otherwise, if for a reason the parsing is aborted, 
       the g_slist_free_full inserted by the compiler at the end of the function would have to deal with an 
       uninitialized GSList -> probable crash. */
    g_autoslist(GtkTreeRowReference) menus_and_items_with_inaccessible_icon_image = NULL;
#else
    GSList *menus_and_items_with_inaccessible_icon_image = NULL;
#endif

    g_autoptr(GDataInputStream) file_data_input_stream = g_data_input_stream_new (G_INPUT_STREAM (file_input_stream));

    // No passthrough and error functions -> NULL for both corresponding arguments.
    GMarkupParser parser = { start_tags_their_attr_and_val__empty_element_tags, end_tags, element_content, NULL, NULL };
    // No user data destroy notifier called when the parse context is freed -> NULL
    g_autoptr(GMarkupParseContext) parse_context = g_markup_parse_context_new (&parser, 0, &menu_building, NULL);
    gboolean no_parsing_error = TRUE; // Default

    guint8 ts_build_cnt;

    while ((menu_building.line = g_data_input_stream_read_line_utf8 (file_data_input_stream, NULL, NULL, &error))) {
        if (G_LIKELY (g_markup_parse_context_parse (parse_context, menu_building.line, strlen (menu_building.line), &error))) {
            ++(menu_building.line_number);
        }
        else {
            g_autofree gchar *err_msg_with_escaped_markup_part_line, *err_msg_with_escaped_markup_part_pls_correct_file;
            g_autofree gchar *pure_errmsg;
            g_autoptr(GString) full_errmsg = g_string_new (NULL);

            /*
                Remove leading and trailing (incl. newline) whitespace from line and 
                escape all special characters so that the markup is used properly.
            */
            err_msg_with_escaped_markup_part_line = g_markup_escape_text (g_strstrip (menu_building.line), -1);
            // Translation note: "Line" (German "Zeile") means a line in a text here, e.g. "Line 57"
            g_string_append_printf (full_errmsg, _("<b>Line %i:</b>\n<tt>%s</tt>\n\n"), 
                                    menu_building.line_number, err_msg_with_escaped_markup_part_line);

            /*
                Since the line number of the error message provided by GLib is often imprecise, it is removed. 
                Instead, the line number provided by the program has already been added before.
                The character position is removed, too, because displaying the line number should be sufficient for 
                menus used in practice.
            */

            if (g_regex_match_simple ("Error", error->message, 0, G_REGEX_MATCH_ANCHORED)) {
                // "Error on line 15 char 8: Element..." -> "Element..."
                pure_errmsg = extract_substring_via_regex (error->message, "(?<=: ).*"); // Regex with positive lookbehind.
            }
            else {
                pure_errmsg = g_strdup (error->message);
            }

            /*
                Escape the error message text so it is displayed correctly if it contains markup. 
                This program does not generate error messages that contain markup, but the GLib markup parser does.
                An example is "Document must begin with an element (e.g. <book>)".
            */
            err_msg_with_escaped_markup_part_pls_correct_file = g_markup_escape_text (pure_errmsg, -1);

            g_string_append_printf (full_errmsg, _("<b><span foreground='%s'>%s!</span>\n\n"
                                                   "Please correct the menu file</b>\n<tt>%s</tt>\n"
                                                   "<b>before reloading it.</b>"), 
                                                 ks.red_hue, err_msg_with_escaped_markup_part_pls_correct_file, new_filename);

            show_errmsg (full_errmsg->str);           
        }

        // Cleanup
        g_free (menu_building.line);

        if (G_UNLIKELY (error)) {
            break;
        }
    }

    if (G_UNLIKELY (error)) {
        if (!menu_building.line) { // This will only be called if for example the file is of the wrong type.
            g_autofree gchar *err_msg = g_strdup_printf (_("<b>An error occurred while parsing the menu file</b> "
                                                           "<tt>%s</tt> <b>:</b>\n\n%s"), 
                                                         new_filename, error->message);

            show_errmsg (err_msg);
        }

        // Cleanup
        g_free (new_filename);
        g_slist_free_full (menu_building.menu_ids, (GDestroyNotify) g_free);

        no_parsing_error = FALSE;

        goto parsing_abort;
    }


    // --- Menu file loaded without erros, now (re)set global variables. ---


    clear_global_data ();
    ks.loading_process_edit_types[ICON_INFO_CORRECTED]            = menu_building.icon_info_corrected;
    ks.loading_process_edit_types[INVALID_OPTION_VALUE_CORRECTED] = menu_building.invalid_option_value_corrected;
    ks.loading_process_edit_types[DEPRECATED_EXE_CMD_CVTD]        = menu_building.deprecated_exe_cmd_cvtd;
    ks.change_done = menu_building.change_done_loading_proc;
    ks.menu_ids = menu_building.menu_ids;
    set_filename_and_window_title (new_filename);


    // --- Fill treestore. ---


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

    GtkTreeIter *levels = g_malloc (sizeof (GtkTreeIter) * menu_building.max_path_depth);

    GSList *invisible_elements_lists[LM_NUMBER_OF_INVISIBLE_ELEMENTS_LISTS] = { NULL };

    guint number_of_toplevel_menu_ids = 0;
    guint number_of_used_toplevel_root_menus = 0; 

    gboolean root_menu_stage = FALSE;
    gboolean add_row = TRUE; // Default
    gboolean menu_or_item_or_separator_at_root_toplevel;

    gint row_number = 0;

    GSList *tree_build_loop;
    gpointer *tree_data;

    GSList *menus_and_items_with_inaccessible_icon_image_loop;
    GtkTreeIter iter_loop;
    gboolean valid;

    menu_building.tree_build = g_slist_reverse (menu_building.tree_build);

    FOREACH_IN_LIST (menu_building.tree_build, tree_build_loop) {
        tree_data = tree_build_loop->data;

        if (STREQ (tree_data[LM_TS_BUILD_MENU_ID], "root-menu")) {
            root_menu_stage = TRUE;

            // Parentheses avoid gcc warning.
            if ((number_of_toplevel_menu_ids = gtk_tree_model_iter_n_children (ks.ts_model, NULL))) {
                /* g_autoslist with gchar is not possible. Despite the necessity of freeing the list manually at the end, 
                   a GSList is kept here because in this case, after adding new elements in the preferred way, by prepending 
                   them for speed reasons, the list is already in the desired order; it is not subsequently reversed. */
                GSList *menu_ids_of_root_menus_defined_outside_root_first = NULL;

                guint invisible_menu_outside_root_index;

                GtkTreeIter iter_swap;

                guint root_menus_cnt;

                GSList *menu_ids_of_root_menus_defined_outside_root_first_loop, *toplevel_menu_ids_root_menu_loop; 

                /*
                    Generate a list that contains all toplevel menus defined first outside the root menu that are visible.

                    menu_building.toplevel_menu_ids[LM_ROOT_MENU] is in reverse order, 
                    so looping through it and prepending all matches with the treeview to the new list 
                    menu_ids_of_root_menus_defined_outside_root_first results in a correct order for the latter.

                    menu_ids_of_root_menus_defined_outside_root_first is a subset of menu_building.toplevel_menu_ids[LM_ROOT_MENU], 
                    if the number of root menus defined outside root first is smaller than the number of root menus, 
                    or it is identical, if both numbers are equal.

                    The outer loop has to iterate through menu_building.toplevel_menu_ids[LM_ROOT_MENU] whilst the inner one 
                    has to iterate through the treeview, because if it would be done vice versa the order of 
                    menu_ids_of_root_menus_defined_outside_root_first would be according to the treeview, 
                    but it has to be according to the order of the root menu inside the menu file. 

                    Example menu:

                    <openbox_menu xmlns="http://openbox.org/3.4/menu">

                    // Only the menu definitions here have been added to the treeview so far.

                    <menu id="menu3" label="menu3" />
                    <menu id="menu5" label="menu5" />
                    <menu id="menu1" label="menu1" />
                    <menu id="menu2" label="menu2" />
                    <menu id="menu6" label="menu6" />

                    <menu id="root-menu" label="Openbox 3">

                    // This is the current position inside the menu file up to which its elements have been added to 
                    // the treeview yet, elements inside the root menu that have not been defined before haven't been 
                    // added yet; regarding this example these are the separator, item, and menu4.
                    // The menus 1-4 are part of the menu_building.toplevel_menu_ids[LM_ROOT_MENU] list.

                    <menu id="menu1" />
                    <separator />
                    item label="item" />
                    <menu id="menu2" />
                    <menu id="menu3" />
                    <menu id="menu4" label="menu4" />
                    </menu>

                    </openbox_menu>

                    menu_building.toplevel_menu_ids[LM_ROOT_MENU] contains:

                    menu4
                    menu3
                    menu2
                    menu1

                    This is the reverse order in comparison to the root menu of the menu file.

                    menu_ids_of_root_menus_defined_outside_root_first will contain:

                    menu1
                    menu2
                    menu3

                    menu4 has not been defined outside first -> no part of menu_ids_of_root_menus_defined_outside_root_first.
                */

                FOREACH_IN_LIST (menu_building.toplevel_menu_ids[LM_ROOT_MENU], toplevel_menu_ids_root_menu_loop) {
                    valid = gtk_tree_model_get_iter_first (ks.ts_model, &iter_loop);
                    while (valid) {
                        g_autofree gchar *menu_id_txt_loop;

                        gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ID, &menu_id_txt_loop, -1);

                        if (STREQ (toplevel_menu_ids_root_menu_loop->data, menu_id_txt_loop)) {
                            g_autofree gchar *menu_element_txt_loop;

                            gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ELEMENT, &menu_element_txt_loop, -1);
                            gtk_tree_store_set (ks.treestore, &iter_loop, TS_ELEMENT_VISIBILITY, 
                                                (G_LIKELY (menu_element_txt_loop)) ? "visible" : "invisible menu", 
                                                -1);
                            menu_ids_of_root_menus_defined_outside_root_first = 
                                g_slist_prepend (menu_ids_of_root_menus_defined_outside_root_first, g_strdup (menu_id_txt_loop));

                            break;
                        }

                        valid = gtk_tree_model_iter_next (ks.ts_model, &iter_loop);
                    }
                }

                /*
                    Move menus that don't show up inside the root menu to the bottom, 
                    keeping their original order, and mark them as invisible. 

                    Example menu:

                    <openbox_menu>

                    // Only the menu definitions here have been added to the treeview so far.

                    <menu id="menu3" label="menu3" />
                    <menu id="menu5" label="menu5" />
                    <menu id="menu1" label="menu1" />
                    <menu id="menu2" label="menu2" />
                    <menu id="menu6" label="menu6" />

                    <menu id="root-menu" label="Openbox 3">

                    // This is the current position inside the menu file up to which its elements have been added to 
                    // the treeview yet, elements inside the root menu that have not been defined before haven't been 
                    // added yet, regarding this example these are the separator, item, and menu4.

                    <menu id="menu1" />
                    <separator />
                    <item label="item" />
                    <menu id="menu2" />
                    <menu id="menu3" />
                    <menu id="menu4" label="menu4" />
                    </menu>

                    </openbox_menu>

                    Treeview looks like this so far:

                    menu3
                    menu5
                    menu1
                    menu6
                    menu2

                    After the orphaned menus have been moved to the end:

                    menu3
                    menu1
                    menu2
                    menu5 (invisible orphaned menu, sorted to the end)
                    menu6                     ""
                */

                invisible_menu_outside_root_index = number_of_toplevel_menu_ids - 1;
                valid = gtk_tree_model_iter_nth_child (ks.ts_model, &iter_loop, NULL, invisible_menu_outside_root_index);
                while (valid) {
                    g_autofree gchar *element_visibility_txt_loop;

                    gtk_tree_model_get (ks.ts_model, &iter_loop, TS_ELEMENT_VISIBILITY, &element_visibility_txt_loop, -1);

                    if (G_UNLIKELY (!element_visibility_txt_loop)) {
                        gtk_tree_store_set (ks.treestore, &iter_loop, TS_ELEMENT_VISIBILITY, "invisible orphaned menu", -1);
                        gtk_tree_model_iter_nth_child (ks.ts_model, &iter_swap, NULL, invisible_menu_outside_root_index--);
                        gtk_tree_store_swap (ks.treestore, &iter_loop, &iter_swap);
                        iter_loop = iter_swap;
                    }

                    valid = gtk_tree_model_iter_previous (ks.ts_model, &iter_loop);
                }

                /*
                    The order of the toplevel menus depends on the order inside the root menu, 
                    so the menus are sorted accordingly to it. 

                    Toplevel menus defined inside the root menu as well as toplevel items and separators have not been added yet, 
                    so the number of elements of the menu_ids_of_root_menus_defined_first_outside_root list 
                    is equal to the number of menus inside the root menu that are not invisible orphaned menus 
                    (=element visibilty "visible" or "invisible menu"). 

                    Example menu:

                    <openbox_menu>

                    // Only the menu definitions here have been added to the treeview so far.

                    <menu id="menu3" label="menu3" />
                    <menu id="menu5" label="menu5" />
                    <menu id="menu1" label="menu1" />
                    <menu id="menu2" label="menu2" />
                    <menu id="menu6" label="menu6" />

                    <menu id="root-menu" label="Openbox 3">

                    // This is the current position inside the menu file up to which its elements have been added to 
                    // the treeview yet, elements inside the root menu that have not been defined before haven't been 
                    // added yet, regarding this example these are the separator, item, and menu4.
                    // Since the menu processing hasn't gone beyond this position yet, the sorting of the toplevel menus is  
                    // done by using the menu_ids_of_root_menus_defined_outside_root_first list as reference.

                    <menu id="menu1" />
                    <separator />
                    <item label="item" />
                    <menu id="menu2" />
                    <menu id="menu3" />
                    <menu id="menu4" label="menu4" />
                    </menu>

                    </openbox_menu>

                    Treeview looks like this so far:

                    menu3 (order according to the menu definitions done prior to the root menu)
                    menu1                                ""            
                    menu2                                ""
                    menu5 (invisible orphaned menu)
                    menu6                                ""

                    After the sorting has been done:

                    menu1 (order according to root menu of the menu file)
                    menu2                    ""
                    menu3                    ""
                    menu5 (invisible orphaned menu)
                    menu6                    ""

                    Invisible orphaned menus are unaffected by the sorting.
                */

                gtk_tree_model_get_iter_first (ks.ts_model, &iter_loop);
                FOREACH_IN_LIST (menu_ids_of_root_menus_defined_outside_root_first, 
                                 menu_ids_of_root_menus_defined_outside_root_first_loop) {
                    g_autofree gchar *menu_id_txt_loop;

                    gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ID, &menu_id_txt_loop, -1);

                    gchar *menu_id_of_root_menu_defined_outside_root_first_loop = 
                        menu_ids_of_root_menus_defined_outside_root_first_loop->data;

                    if (!STREQ (menu_id_of_root_menu_defined_outside_root_first_loop, menu_id_txt_loop)) {
                        for (root_menus_cnt = number_of_used_toplevel_root_menus + 1; // = 1 at first time.
                            root_menus_cnt <= invisible_menu_outside_root_index; 
                            ++root_menus_cnt) {
                            g_autofree gchar *menu_id_txt_subloop;
                            
                            gtk_tree_model_iter_nth_child (ks.ts_model, &iter_swap, NULL, root_menus_cnt);
                            gtk_tree_model_get (ks.ts_model, &iter_swap, TS_MENU_ID, &menu_id_txt_subloop, -1);

                            if (STREQ (menu_id_of_root_menu_defined_outside_root_first_loop, menu_id_txt_subloop)) {
                                break;
                            }
                        }

                        gtk_tree_store_swap (ks.treestore, &iter_loop, &iter_swap);
                    }

                    gtk_tree_model_iter_nth_child (ks.ts_model, &iter_loop, NULL, ++number_of_used_toplevel_root_menus);
                }

                // Cleanup
                g_slist_free_full (menu_ids_of_root_menus_defined_outside_root_first, (GDestroyNotify) g_free);
            }

            continue; // Nothing to add for a "root-menu" menu ID. 
        }

        menu_or_item_or_separator_at_root_toplevel = FALSE; // Default

        guint current_level = GPOINTER_TO_UINT (tree_data[LM_TS_BUILD_PATH_DEPTH]) - 1;

        if (root_menu_stage) {
            --current_level;

            if (current_level == 0) { // toplevel -> menu, pipe menu, item, or separator.
                gchar *type_txt_loop = tree_data[LM_TS_BUILD_TYPE];

                menu_or_item_or_separator_at_root_toplevel = TRUE;
                /*
                    Toplevel root menus are only added if they have not yet been defined before, 
                    since the question whether to add a menu element or not only arises in this case, 
                    a default setting is only done here and not for every menu element.
                */
                add_row = TRUE; // Default

                if (streq_any (type_txt_loop, "menu", "pipe menu", NULL)) {
                    /*
                        If the current row inside menu_building.tree_build is a menu with an icon, look for a corresponding 
                        toplevel menu inside the treeview (it will exist if it has already been defined outside the root menu),
                        and if one exists, add the icon data to this toplevel menu.
                    */
                    if (tree_data[LM_TS_BUILD_ICON_IMG]) {
                        valid = gtk_tree_model_get_iter_first (ks.ts_model, &iter_loop);
                        while (valid) {
                            g_autofree gchar *menu_id_txt_loop;

                            gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ID, &menu_id_txt_loop, -1);

                            if (STREQ (tree_data[LM_TS_BUILD_MENU_ID], menu_id_txt_loop)) {
                                for (ts_build_cnt = 0; ts_build_cnt <= LM_TS_BUILD_ICON_PATH; ++ts_build_cnt) {
                                    gtk_tree_store_set (ks.treestore, &iter_loop, ts_build_cnt, tree_data[ts_build_cnt], -1);
                                }

                                break;
                            }

                            valid = gtk_tree_model_iter_next (ks.ts_model, &iter_loop);
                        }
                    }

                    if (!tree_data[LM_TS_BUILD_MENU_ELEMENT] && 
                        g_slist_find_custom (menu_building.toplevel_menu_ids[LM_MENUS], 
                                             tree_data[LM_TS_BUILD_MENU_ID], (GCompareFunc) strcmp)) {
                        add_row = FALSE; // Is a menu defined outside root that is already inside the treestore.
                    }
                }
            }
        }

        if (add_row) {
            g_autoptr(GtkTreePath) path;

            gtk_tree_store_insert (ks.treestore, &levels[current_level], 
                                   (current_level == 0) ? NULL : &levels[current_level - 1], 
                                   (menu_or_item_or_separator_at_root_toplevel) ? row_number : -1);

            ks.iter = levels[current_level];
            path = gtk_tree_model_get_path (ks.ts_model, &ks.iter);

            for (ts_build_cnt = 0; ts_build_cnt < LM_TS_BUILD_PATH_DEPTH; ++ts_build_cnt) {
                gtk_tree_store_set (ks.treestore, &ks.iter, ts_build_cnt, tree_data[ts_build_cnt], -1);
            }

            if (GPOINTER_TO_UINT (tree_data[TS_ICON_IMG_STATUS]) && gtk_tree_path_get_depth (path) > 1) {
                /*
                    Add a row reference of a path of a menu, pipe menu or item that has an invalid icon path or 
                    a path that points to a file that contains no valid image data.
                */
                menus_and_items_with_inaccessible_icon_image = g_slist_prepend (menus_and_items_with_inaccessible_icon_image, 
                                                                                gtk_tree_row_reference_new (ks.ts_model, path));
            }
        }

        if (menu_or_item_or_separator_at_root_toplevel) {
            ++row_number;
        }       
    }

    g_signal_handler_block (selection, ks.handler_id_row_selected);

    if (G_UNLIKELY (menu_building.menus_with_double_or_contradicting_attribute_values)) {
        GSList *menus_loop;
        g_autoptr(GString) menu_ids_list = g_string_new (NULL);

        const guint list_length = g_slist_length (menu_building.menus_with_double_or_contradicting_attribute_values);

        menu_building.menus_with_double_or_contradicting_attribute_values = 
            g_slist_reverse (menu_building.menus_with_double_or_contradicting_attribute_values);

        FOREACH_IN_LIST (menu_building.menus_with_double_or_contradicting_attribute_values, menus_loop) {
            g_string_append_printf (menu_ids_list, "<b>%s</b>%s", (gchar *) menus_loop->data, (list_length == 1) ? "" : "\n");
        }

        gchar *dialog_title_txt = ngettext ("Double or Contradicting Attribute Values for Menu", 
                                            "Double or Contradicting Attribute Values for Menus",
                                            (list_length == 1));
        g_autofree gchar *dialog_txt1 = g_strdup_printf (ngettext ("The menu file contains a menu with the ID %s "
                                                                   "for which values have been set both inside and outside the root menu.", 
                                                                   "The menu file contains the menus with the IDs\n%s"
                                                                   "for which values have been set both inside and outside the root menu.", 
                                                                   (list_length == 1)),
                                                         menu_ids_list->str);
        
        g_autofree gchar *dialog_txt = g_strconcat (dialog_txt1, 
                                                    // Translation note: Do not translate "icon"
                                                    _(" In this case, for double or contradicting attribute values Kickshaw follows "
                                                      "the handling of this situation by Openbox and uses the icon attribute value "
                                                      "inside the root menu, whereas for all other attribute values the ones outside "
                                                      "the root menu are used."), 
                                                    NULL);

        // Translation note: the verb "Close"
        show_message_dialog (dialog_title_txt, "gtk-info", dialog_txt, _("_Close"));

        // Cleanup
        g_slist_free_full (menu_building.menus_with_double_or_contradicting_attribute_values, (GDestroyNotify) g_free);
    }

    // Show a message if there are invisible menus outside root.
    if (G_UNLIKELY (number_of_used_toplevel_root_menus < number_of_toplevel_menu_ids)) {
        create_dialogs_for_invisible_menus_and_items (LM_ORPHANED_MENUS, selection, invisible_elements_lists);
    }

    /*
        Set element visibility status for all those (pipe) menus, items and separators that don't already have one.
        If invisible orphaned menus have been visualized and they had descendant (pipe) menus, items or separators, 
        readjust the visibility status of the latter.
    */
    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) elements_visibility, invisible_elements_lists);

    // Show a message if there are menus and items without a label (=invisible).
    if (G_UNLIKELY (invisible_elements_lists[LM_MENUS_LIST] || invisible_elements_lists[LM_ITEMS_LIST])) {
        create_dialogs_for_invisible_menus_and_items (LM_MISSING_LABELS, selection, invisible_elements_lists);
        /*
            If (pipe) menus and/or items without label have received a label now and they had descendant 
            (pipe) menus, items or separators, readjust the visibility status of the latter.
        */
        gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) elements_visibility, NULL);
    }

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    gtk_tree_view_collapse_all (GTK_TREE_VIEW (ks.treeview));


    // --- Finalization ---


    // Pre-sort options of Execute action and startupnotify, if autosorting is activated.
    if (ks.settings.autosort_options) {
        gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) sort_loop_after_sorting_activation, NULL);
    }

    // Expand nodes that contain a broken icon.
    if (G_UNLIKELY (menus_and_items_with_inaccessible_icon_image)) {
        FOREACH_IN_LIST (menus_and_items_with_inaccessible_icon_image, menus_and_items_with_inaccessible_icon_image_loop) {
            g_autoptr(GtkTreePath) path_loop = gtk_tree_row_reference_get_path (menus_and_items_with_inaccessible_icon_image_loop->data);

            gtk_tree_view_expand_to_path (GTK_TREE_VIEW (ks.treeview), path_loop);
            gtk_tree_view_collapse_row (GTK_TREE_VIEW (ks.treeview), path_loop);
        }
    }

    // Notify about a conversion of deprecated execute to command options.
    if (G_UNLIKELY (menu_building.deprecated_exe_cmd_cvtd && ks.settings.always_notify_about_opts_conv)) {
        GtkWidget *dialog, *content_area;
        GtkWidget *chkbt_exe_opt_conversion_notification;

        // Translation note: do not translate "Execute"
        chkbt_exe_opt_conversion_notification = gtk_check_button_new_with_label (_("Always notify about Execute option conversions."));

        content_area = create_dialog (&dialog,
		                              // Translation note: Do not translate "Execute"
                                      _("Conversion of Deprecated Execute Option"), 
                                      "dialog-information", 
									  // Translation note: Do not translate "execute", "command"
                                      _("This menu contained at least one deprecated \"execute\" option; "
                                        "they have been converted to \"command\" options. "
                                        "These conversions have not been written back to the menu file yet; "
                                        "to do so, simply save the menu from inside the program."), 
                                      _("_OK"), NULL, NULL, DONT_SHOW_IMMEDIATELY);

        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chkbt_exe_opt_conversion_notification), TRUE);

        gtk_container_add (GTK_CONTAINER (content_area), chkbt_exe_opt_conversion_notification);

        gtk_widget_show_all (dialog);

        gtk_dialog_run (GTK_DIALOG (dialog));

        if (ks.settings.show_menu_button) {
            const gchar *new_state = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chkbt_exe_opt_conversion_notification))) ? 
                                     "true" : "false";

            change_view_and_options_GMenu (ks.options_actions[M_NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS], NULL, (gpointer) new_state);
        }
        else {
            gboolean new_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chkbt_exe_opt_conversion_notification));        

            gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.MBar_options_menu_items[M_NOTIFY_ABOUT_EXECUTE_OPT_CONVERSIONS]), 
                                            new_state);
        }

        gtk_widget_destroy (dialog);
    }

    gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ks.treeview));

    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) add_icon_occurrence_to_list, NULL);
    if (!ks.timeout_id) {
        ks.timeout_id = g_timeout_add_seconds (1, (GSourceFunc) check_for_external_file_and_settings_changes, "timeout");
    }

    // ### Cleanup ###

    g_free (levels);

    parsing_abort:

    // tree_build

    FOREACH_IN_LIST (menu_building.tree_build, tree_build_loop) {
        tree_data = tree_build_loop->data;
        if (tree_data[LM_TS_BUILD_ICON_IMG]) {
            g_object_unref (tree_data[LM_TS_BUILD_ICON_IMG]);
        }
        for (ts_build_cnt = LM_TS_BUILD_ICON_MODIFICATION_TIME; ts_build_cnt <= LM_TS_BUILD_ELEMENT_VISIBILITY; ++ts_build_cnt) {
            g_free (tree_data[ts_build_cnt]);
        }
        g_free (tree_data);
    }
    g_slist_free (menu_building.tree_build);

    // Other menu_building lists and variables

    g_slist_free_full (menu_building.toplevel_menu_ids[LM_MENUS], (GDestroyNotify) g_free);
    g_slist_free_full (menu_building.toplevel_menu_ids[LM_ROOT_MENU], (GDestroyNotify) g_free);

    g_free (menu_building.current_elements[LM_ITEM]);
    g_free (menu_building.current_elements[LM_ACTION]);
    g_free (menu_building.previous_type);

#if !(GLIB_CHECK_VERSION(2,56,0))
    // Cleanup
    g_slist_free_full (menus_and_items_with_inaccessible_icon_image, (GDestroyNotify) gtk_tree_row_reference_free);
#endif

    return no_parsing_error;
}

/* 

    Lets the user choose a menu xml file for opening.

*/

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

    GtkWidget *dialog;

    create_file_dialog (&dialog, OPEN_FILE);

    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
        gchar *new_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));

        gtk_widget_destroy (dialog);

        if (get_menu_elements_from_file (new_filename)) {
            push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
            row_selected ();                              /* ... that the toolbar buttons and 
                                                             application menu's undo/redo items get the correct sensitivity settings. */
        }
    }
    else {
        gtk_widget_destroy (dialog);
    }
}
