/*
   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 "adding_and_deleting.h"

static void add_new_execution (const gchar       *new_menu_element, 
                                     GtkTreeIter *to_be_selected_iter, 
                               const gboolean     same_level);
static gboolean check_for_menus (              GtkTreeModel *filter_model, 
                                 G_GNUC_UNUSED GtkTreePath  *filter_path, 
                                               GtkTreeIter  *filter_iter,
                                 G_GNUC_UNUSED gpointer      user_data);
static void clear_entries (void);
static void create_combo_box (void);
static void hide_or_deactivate_widgets (void);
static void highlight_menu_ID_entry_field_for_double_menu_IDs (G_GNUC_UNUSED gpointer user_data);
static void option_list_with_headlines (G_GNUC_UNUSED GtkCellLayout   *cell_layout, 
                                                      GtkCellRenderer *action_option_combo_box_renderer, 
                                                      GtkTreeModel    *action_option_combo_box_model, 
                                                      GtkTreeIter     *action_option_combo_box_iter, 
                                        G_GNUC_UNUSED gpointer         user_data);
static void show_action_options (void);

/*

    Deactivate or hide those widgets whose actions could interfere during the entry of new values.

*/

static void hide_or_deactivate_widgets (void)
{
    if (ks.settings.show_menu_button) {
        GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
        const gint number_of_selected_rows = gtk_tree_selection_count_selected_rows (selection);
        GtkTreeIter iter_test;
        const gboolean treeview_is_not_empty = gtk_tree_model_get_iter_first (ks.ts_model, &iter_test);    

        // Menu exists with at least one node and at least one of them selected
        if (number_of_selected_rows) {
            g_menu_remove (ks.popup_menu, 4); // Options Menu
            g_menu_remove (ks.popup_menu, 2); // Search Menu
            g_menu_remove (ks.popup_menu, 1); // Edit Menu
        }
        /* No selection and...
           case 1: menu is empty, but an edit has been done
           case 2: menu is not empty and no edit has been done */
        else if ((!treeview_is_not_empty && ks.change_done) || 
                 (treeview_is_not_empty && !ks.change_done)) {
            g_menu_remove (ks.popup_menu, 3); // Options Menu
            g_menu_remove (ks.popup_menu, 1); // Case 1: Edit Menu, Case 2: Search Menu
        }
        /* Menu does not exist and there has been no edit done because
           1. Kickshaw has not loaded a menu after startup or 
           2. a new empty menu has been created */
        else {
            g_menu_remove (ks.popup_menu, 2); // Options Menu
        }
    }
    else {
        gtk_widget_set_sensitive (ks.MBar_menu_items[EDIT_MENU], FALSE);
        gtk_widget_set_sensitive (ks.MBar_menu_items[SEARCH_MENU], FALSE);
        gtk_widget_set_sensitive (ks.MBar_menu_items[OPTIONS_MENU], FALSE);
    }

    for (guint8 tb_cnt = TB_BUTTON_UNDO; tb_cnt <= TB_BUTTON_FIND; ++tb_cnt) {
        gtk_widget_set_sensitive ((GtkWidget *) ks.tb_buttons[tb_cnt], FALSE);
    }

    gtk_widget_hide (ks.button_grid);
    gtk_widget_hide (ks.find_grid);
}

/*

    Execute the actions that can be initiated from the "enter values" input screen.

*/

void one_of_the_enter_values_buttons_pressed (const gchar *origin)
{
    GSList *enter_values_user_settings_loop;
    // AD = adding and deleting, indicating that the enums are only used here
    enum { AD_FIELD, AD_VALUE };

    // Not const; this might be subject to change.
    gboolean including_action_check_button_active = 
        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]));
    g_autofree gchar *new_menu_element = NULL;

    FOREACH_IN_LIST (ks.enter_values_user_settings, enter_values_user_settings_loop) {
        g_auto(GStrv) parameter = g_strsplit (enter_values_user_settings_loop->data, ":", -1);

        if (STREQ (origin, "done") && STREQ (parameter[AD_FIELD], "new menu element")) {
            // "value" exists only temporary
            new_menu_element = g_strdup (parameter[AD_VALUE]);
        }
        else if (STREQ (origin, "reset")) {
            if (STREQ (parameter[AD_FIELD], "new menu element")) {
                const gchar *label_txt = (STREQ (parameter[AD_VALUE], "menu")) ? _("New Menu") : 
                                         ((STREQ (parameter[AD_VALUE], "pipe menu")) ? _("New Pipe Menu") : 
                                          _("New Item"));

                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), label_txt);

                // To keep the code simple, the rest of this if-clause resets fields even if they are currently not visible.
                gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), 
                                                "user_intervention_requested");
                gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[EXECUTE_ENTRY]), 
                                                "user_intervention_requested");

                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), "");
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]), "");
            }
            else if (STREQ (parameter[AD_FIELD], "menu ID field")) {
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]), parameter[AD_VALUE]);
            }
            else if (STREQ (parameter[AD_FIELD], "inside Menu")) {
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]), 
                                              STREQ (parameter[AD_VALUE], "true"));
            }
            else if (STREQ (parameter[AD_FIELD], "incl. action")) {
                including_action_check_button_active = (STREQ (parameter[AD_VALUE], "true"));
                // Prevent recursion
                g_signal_handler_block (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON], 
                                        ks.handler_id_including_action_check_button);
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]), 
                                              including_action_check_button_active);
                g_signal_handler_unblock (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON], 
                                          ks.handler_id_including_action_check_button);
                gtk_widget_set_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], including_action_check_button_active);
                gtk_widget_set_visible (ks.options_grid, including_action_check_button_active);
            }
            else { // action
                gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), parameter[AD_VALUE]);
                if (streq_any (parameter[AD_VALUE], "Execute", "Restart", NULL)) {
                    clear_entries ();
                }
                else if (streq_any (parameter[AD_VALUE], "Exit", "SessionLogout", NULL)) {
                    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
                }
            }
        }
        else if (STREQ (origin, "incl. action") && STREQ (parameter[AD_FIELD], "action")) {
            gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), parameter[AD_VALUE]);
            if (!including_action_check_button_active) {
                clear_entries ();
            }
            else if (streq_any (parameter[AD_VALUE], "Exit", "SessionLogout", NULL)) {
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
            }
            gtk_widget_set_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], including_action_check_button_active);
            gtk_widget_set_visible (ks.options_grid, including_action_check_button_active);
        }
    }

    if (STREQ (origin, "done")) {
        const gboolean add_action = (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX] && 
                                    gtk_widget_get_visible (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]));
        gboolean same_level = TRUE; // Default
        gboolean missing_entry_or_error = FALSE; // Default

        // Missing label for new menu element.
        if (!(*(gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]))))) {
            visually_indicate_request_for_user_intervention (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], 
                                                             ks.menu_element_or_value_entry_css_provider);
            missing_entry_or_error = TRUE;
        }
        else {
            gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), 
                                            "user_intervention_requested");
        }

        // "Execute" missing of new pipe menu.
        if (STREQ (new_menu_element, "pipe menu") && 
            !(*(gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]))))) {
            visually_indicate_request_for_user_intervention (ks.entry_fields[EXECUTE_ENTRY], ks.execute_entry_css_provider);
            missing_entry_or_error = TRUE;
        }
        else {
            gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[EXECUTE_ENTRY]), 
                                            "user_intervention_requested");
        }

        // Prevent empty command field for "Execute".
        if (add_action &&
            STREQ (gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])), 
                   "Execute") && 
            !(*(gtk_entry_get_text (GTK_ENTRY (ks.options_fields[COMMAND]))))) {
            visually_indicate_request_for_user_intervention (ks.options_fields[COMMAND], ks.execute_options_css_providers[COMMAND]);
            missing_entry_or_error = TRUE;
        }
        else {
            gtk_style_context_remove_class (gtk_widget_get_style_context (ks.options_fields[COMMAND]), "user_intervention_requested");
        }

        // Invalid icon file or path
        const gchar *icon_path = gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]));
        // TRUE = display error message if error occurs, FALSE = not during undo/redo
        if (*icon_path && !set_icon (&ks.iter, icon_path, TRUE, FALSE)) {
            missing_entry_or_error = TRUE;
        }

        // Prevent double menu ID.
        if (!STREQ (new_menu_element, "item")) { // menu or pipe menu
            const gchar *menu_id = gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]));

            if (G_UNLIKELY (g_slist_find_custom (ks.menu_ids, menu_id, (GCompareFunc) strcmp))) {
                show_errmsg (_("The chosen menu ID already exists. Please choose another one."));
                missing_entry_or_error = TRUE;
            }
            else if (!missing_entry_or_error) {
                ks.menu_ids = g_slist_prepend (ks.menu_ids, g_strdup (menu_id));
            }
        }

        if (missing_entry_or_error) {
            return;
        }

        if (gtk_widget_get_visible (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON])) {
            same_level = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]));
        }

        GtkTreeIter to_be_selected_iter;
        add_new_execution (new_menu_element, &to_be_selected_iter, same_level);
        /* In case that an action is subsequently added to a new item, to_be_selected_iter will be 
           chosen later to select the added item because ks.iter might have changed. */
        ks.iter = to_be_selected_iter;

        if (add_action) {
            repopulate_txt_fields_array (); // This is usually called by row_selected () and needed by the following function.
            action_option_insert ("enter values screen");
        }

        // Selection is done first so it is saved by the new undo stack item that is created afterwards.
        GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
        gtk_tree_selection_select_iter (selection, &to_be_selected_iter);

        push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
        activate_change_done ();
        row_selected ();                              /* ... the toolbar buttons and 
                                                         application menu's undo/redo items get the correct sensitivity settings. */
    }
}

/*

    Adds one of the following menu elements: a menu, item, separator or a _single_ option.

    Actions and _several options_ are not added by this function; this is done by action_option_insert ().

*/

static void add_new_execution (const gchar       *new_menu_element, 
                                     GtkTreeIter *to_be_selected_iter, 
                               const gboolean     same_level)
{
    g_autoptr(GdkPixbuf) icon = NULL; // Default
    g_autofree gchar *icon_modification_time_txt = NULL; // Default
    g_autoptr(GString) icon_path_txt = g_string_new (NULL); // Default
    const gchar *element_visibility_txt = NULL; // Default

    const gboolean is_not_option = (streq_any (new_menu_element, "menu", "pipe menu", "item", "separator", NULL));

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

    g_autoptr(GtkTreePath) path = NULL; // Default
    GtkTreeIter new_iter;

    gboolean path_is_on_toplevel = TRUE; // Default


    // --- Add the new row. ---


    if (gtk_tree_selection_count_selected_rows (selection) && 
        gtk_tree_path_get_depth (path = gtk_tree_model_get_path (ks.ts_model, &ks.iter)) > 1) {
        path_is_on_toplevel = FALSE;
    }

    // Prevents that the default check for change of selection(s) gets in the way.
    g_signal_handler_block (selection, ks.handler_id_row_selected);

    gtk_tree_selection_unselect_all (selection); // The old selection will be replaced by the one of the new row.

    if (!path || !same_level) {
        gtk_tree_store_append (ks.treestore, &new_iter, (!path) ? NULL : &ks.iter);

        if (path) {
            gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path, FALSE); // FALSE = just expand immediate children.
        }
    }
    else {
        GtkTreeIter parent;

        if (!path_is_on_toplevel) {
            gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
        }
        gtk_tree_store_insert_after (ks.treestore, &new_iter, (path_is_on_toplevel) ? NULL : &parent, &ks.iter);
    }


    // --- Set the content of the new row.


    if (streq_any (new_menu_element, "menu", "pipe menu", "item", NULL)) {
        g_string_assign (icon_path_txt, gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY])));

        if (*icon_path_txt->str) {
            g_autoptr(GdkPixbuf) icon_in_original_size = gdk_pixbuf_new_from_file (icon_path_txt->str, NULL);

            icon = gdk_pixbuf_scale_simple (icon_in_original_size, ks.font_size + 10, ks.font_size + 10, 
                                            GDK_INTERP_BILINEAR);
            icon_modification_time_txt = get_modification_time_for_icon (icon_path_txt->str);
        }
    }

    // Set element visibility of menus, pipe menus, items, and separators.

    if (g_regex_match_simple ("menu|pipe menu|item.*|separator", new_menu_element, 0, 0)) {
        element_visibility_txt = "visible";
        /*
            path == NULL means that the new menu element has been added at toplevel;
            such a menu element is always visible after its addition, since it can't have an invisible ancestor.
        */
        if (path) {
            g_autoptr(GtkTreePath) new_path = gtk_tree_model_get_path (ks.ts_model, &new_iter);
            guint8 invisible_ancestor;

            if ((invisible_ancestor = check_if_invisible_ancestor_exists (ks.ts_model, new_path))) { // Parentheses avoid gcc warning.
                element_visibility_txt = (invisible_ancestor == INVISIBLE_ANCESTOR) ?
                "invisible dsct. of invisible menu" : "invisible dsct. of invisible orphaned menu";       
            }
        }
    }

    gtk_tree_store_set (ks.treestore, &new_iter, 
                        TS_ICON_IMG, icon, 
                        TS_ICON_IMG_STATUS, NONE_OR_NORMAL, 
                        TS_ICON_MODIFICATION_TIME, icon_modification_time_txt, 
                        TS_ICON_PATH, (NOT_NULL_AND_NOT_EMPTY (icon_path_txt->str)) ? icon_path_txt->str : NULL, 
                        TS_MENU_ELEMENT, (streq_any (new_menu_element, "menu", "pipe menu", "item", NULL)) ? 
                                         gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY])) : 
                                         ((STREQ (new_menu_element, "separator")) ? NULL : new_menu_element), 
                        TS_TYPE, (is_not_option) ? new_menu_element : "option", 
                        TS_VALUE, (STREQ (new_menu_element, "enabled") || // ...or new_menu_element = "prompt"
                                  (!is_not_option && streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "SessionLogout", NULL))) ? 
                                  "yes" : NULL, 
                        TS_MENU_ID, (streq_any (new_menu_element, "menu", "pipe menu", NULL)) ? 
                                     gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY])) : NULL, 
                        TS_EXECUTE, (STREQ (new_menu_element, "pipe menu")) ? 
                                     gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY])) : NULL, 
                        TS_ELEMENT_VISIBILITY, element_visibility_txt, 
                        -1);

    gtk_tree_selection_select_iter (selection, &new_iter);

    /*
        In case that autosorting is activated:
        If the type of the new row is...
        ...a prompt or command option of Execute, ... OR:
        ...an option of startupnotify, ...
        ...sort all options of the parent row and move the selection to the new option.
    */
    if (ks.settings.autosort_options && !is_not_option) {
        GtkTreeIter parent;

        gtk_tree_model_iter_parent (ks.ts_model, &parent, &new_iter);
        if (gtk_tree_model_iter_n_children (ks.ts_model, &parent) > 1) {
            sort_execute_or_startupnotify_options_after_insertion (selection, &parent, 
                                                                   (streq_any (new_menu_element, "prompt", "command", NULL)) ? 
                                                                   "Execute" : "startupnotify", 
                                                                   new_menu_element);
        }
    }

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    // The new iter might point to a item that will be selected after an addition of an additional action.
    if (to_be_selected_iter) {
        *to_be_selected_iter = new_iter;
    }
}

/*

    If the currently typed in menu ID already exists in the menu, the menu ID entry field is highlighted.

*/

static void highlight_menu_ID_entry_field_for_double_menu_IDs (G_GNUC_UNUSED gpointer user_data)
{
    GtkStyleContext *context = gtk_widget_get_style_context (ks.entry_fields[MENU_ID_ENTRY]);

    if (g_slist_find_custom (ks.menu_ids, gtk_entry_get_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY])), (GCompareFunc) strcmp)) {
        gtk_style_context_add_class (context, "double_menu_ID");
        gtk_css_provider_load_from_data (ks.menu_id_css_provider, 
#if GTK_CHECK_VERSION(3,20,0)
                                         ".double_menu_ID { color:#000000; caret-color:#000000; background:#ffd3d3; }", 
#else
                                         ".double_menu_ID { color:#000000; -GtkWidget-cursor-color:#000000; background:#ffd3d3; }", 
#endif
                                         -1, NULL);

        gtk_widget_set_visible (ks.double_menu_id_label, TRUE);
    }
    else if (gtk_style_context_has_class (context, "double_menu_ID")) {
        gtk_style_context_remove_class (context, "double_menu_ID");
        gtk_widget_set_visible (ks.double_menu_id_label, FALSE);
    }
}

/* 

    Preperations for the addition of a new menu element and display of the "enter values" screen.

*/

void add_new (const gchar *new_menu_element_plus_opt_suppl)
{
    // This makes sure that static analyzers don't assume that new_menu_element_plus_opt_suppl can be NULL.
#ifndef G_DISABLE_ASSERT
    g_assert (new_menu_element_plus_opt_suppl);
#endif

    gboolean same_level = TRUE; // Default
    const gchar *new_menu_element = (!g_regex_match_simple ("item.*", new_menu_element_plus_opt_suppl, 0, 0)) ? 
                                    new_menu_element_plus_opt_suppl : "item";

    if (!streq_any (new_menu_element, "menu", "pipe menu", "item", "separator", NULL)) {
        // Adding options inside an action or an option block.
        if (STREQ (ks.txt_fields[TYPE_TXT], "action") || 
            (STREQ (ks.txt_fields[TYPE_TXT], "option block") && !streq_any (new_menu_element, "prompt", "command", NULL))) {
            same_level = FALSE;
        }

        // NULL = iter of added menu element is not needed for later use.
        add_new_execution (new_menu_element, NULL, same_level);

        push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
        activate_change_done ();
        row_selected ();                              /* ... the toolbar buttons and 
                                                         application menu's undo/redo items get the correct sensitivity settings. */
    }
    else {
        gchar *field_value_pair;

        /*
            If the selection currently points to a menu a decision has to be made to 
            either put the new element inside that menu or next to it on the same level.
        */
        if (STREQ (ks.txt_fields[TYPE_TXT], "menu")) {
            GtkWidget *dialog;
            const char *label_txt;

            gint result;

            enum { CANCEL = 1, SAME_LEVEL, INSIDE_MENU };

            if (STREQ (new_menu_element, "menu")) {
                label_txt = _("Insert new menu on the same level or into the currently selected menu?");
            }
            else if (STREQ (new_menu_element, "pipe menu")) {
                label_txt = _("Insert new pipe menu on the same level or into the currently selected menu?");
            }
            else if (STREQ (new_menu_element, "item")) {
                label_txt = _("Insert new item on the same level or into the currently selected menu?");
            }
            else { // separator
                label_txt = _("Insert new separator on the same level or into the currently selected menu?");
            }

            create_dialog (&dialog, _("Same Level or Inside Menu?"), "dialog-question", label_txt, 
                           C_("Cancel|No File Dialogue", "_Cancel"), _("_Same Level"), _("_Inside Menu"), SHOW_IMMEDIATELY);

            result = gtk_dialog_run (GTK_DIALOG (dialog));
            gtk_widget_destroy (dialog);
            switch (result) {
                case INSIDE_MENU:
                    same_level = FALSE;
                    break;
                case CANCEL:
                case GTK_RESPONSE_DELETE_EVENT:
                    return;
            }       

            if (!STREQ (new_menu_element, "separator")) {
                gtk_widget_show (ks.action_option_grid);
                gtk_widget_hide (ks.options_grid);
                gtk_widget_show (ks.new_action_option_widgets[INSIDE_MENU_LABEL]);
                gtk_widget_show (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]);
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]), !same_level);
                field_value_pair = g_strdup_printf ("inside Menu:%s", (same_level) ? "false" : "true");
                ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, field_value_pair);
            }
        }

        if (!STREQ (new_menu_element, "separator")) {
            const gchar *headline;
            g_autofree gchar *enter_values_markup;
            const gchar *default_label_txt;
            const gboolean menu_or_pipe_menu = (!STREQ (new_menu_element, "item"));


            gtk_box_reorder_child (GTK_BOX (ks.sub_box), ks.entry_grid, 0);

            if (STREQ (new_menu_element, "item")) {
                GtkTreeIter action_option_combo_box_iter;

                const gboolean incl_action = !STREQ (new_menu_element_plus_opt_suppl, "item w/o action");


                gtk_widget_show (ks.new_action_option_widgets[INCLUDING_ACTION_LABEL]);
                gtk_widget_show (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]);
                gtk_widget_show (ks.action_option_grid);
                // Prevent callback
                g_signal_handler_block (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON], 
                                        ks.handler_id_including_action_check_button);
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.new_action_option_widgets [INCLUDING_ACTION_CHECK_BUTTON]), 
                                              incl_action);
                g_signal_handler_unblock (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON], 
                                          ks.handler_id_including_action_check_button);
                field_value_pair = g_strdup_printf ("incl. action:%s", (incl_action) ? "true" : "false");
                ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, field_value_pair);

                create_combo_box ();

                for (guint8 action_cnt = 0; action_cnt < NUMBER_OF_ACTIONS; ++action_cnt) {
                    gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1, 
                                                       ACTION_OPTION_COMBO_ITEM, ks.actions[action_cnt], -1);
                }

                if (g_regex_match_simple ("item\\+.*", new_menu_element_plus_opt_suppl, 0, 0)) {
                    g_autofree gchar *action = extract_substring_via_regex (new_menu_element_plus_opt_suppl, "(?<=\\+).*");

                    gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), action);
                    field_value_pair = g_strdup_printf ("action:%s", action);
                    ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, field_value_pair);
                }
                else { // "item" or "item w/o action". The action_option combo box is set just in case the user changes his/her mind.
                    ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, g_strdup ("action:Execute"));
                    gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), "Execute");
                    if (!STREQ (new_menu_element_plus_opt_suppl, "item")) {
                        gtk_widget_hide (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]);
                    }
                }
                // Has to be after a possible setting of the action_option combo box, because this triggers showing the grid.
                gtk_widget_set_visible (ks.options_grid, incl_action);
            }
            else { // menu or pipe menu
                gtk_widget_hide (ks.options_grid);
            }

            hide_or_deactivate_widgets ();

            if (STREQ (new_menu_element, "menu")) {
                headline = _("Enter Values for New Menu");
            }
            else if (STREQ (new_menu_element, "pipe menu")) {
                headline = _("Enter Values for New Pipe Menu");
            }
            else { // item
                headline = _("Enter Values for New Item");
            }
            enter_values_markup = g_strdup_printf ("<span font_desc='%i'>%s</span>", 
                                                   ks.font_size + 2, headline);
            gtk_label_set_markup (GTK_LABEL (ks.enter_values_label), enter_values_markup);

            gtk_widget_show (ks.enter_values_box);
            gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
            gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_CANCEL]);
            gtk_widget_show (ks.mandatory);
            if (!STREQ (new_menu_element, "item") && !STREQ (ks.txt_fields[TYPE_TXT], "menu")) {
                gtk_widget_set_margin_top (ks.mandatory, 5);
            }
            gtk_widget_show (ks.separator);
            gtk_widget_show (ks.enter_values_buttons_grid);
            for (guint8 entry_cnt = 0; entry_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_cnt) {
                g_signal_handler_disconnect (ks.entry_fields[entry_cnt], ks.handler_id_entry_fields[entry_cnt]);
            }
            gtk_label_set_markup (GTK_LABEL (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY]), ks.label_field_txt->str);
            gtk_label_set_markup (GTK_LABEL (ks.entry_labels[EXECUTE_ENTRY]), ks.execute_field_txt->str);
            gtk_widget_show (ks.icon_chooser);
            gtk_widget_hide (ks.remove_icon);
            gtk_widget_show (ks.entry_labels[ICON_PATH_ENTRY]);
            gtk_widget_show (ks.entry_fields[ICON_PATH_ENTRY]);
            // In case that currently a (pipe) menu or item with an incorrect iconpath is selected.
            gtk_style_context_remove_class (gtk_widget_get_style_context (ks.entry_fields[ICON_PATH_ENTRY]), "user_intervention_requested");
            gtk_widget_set_visible (ks.entry_labels[MENU_ID_ENTRY], menu_or_pipe_menu);
            gtk_widget_set_visible (ks.entry_fields[MENU_ID_ENTRY], menu_or_pipe_menu);
            gtk_widget_set_visible (ks.entry_labels[EXECUTE_ENTRY], STREQ (new_menu_element, "pipe menu"));
            gtk_widget_set_visible (ks.entry_fields[EXECUTE_ENTRY], STREQ (new_menu_element, "pipe menu"));

            field_value_pair = g_strdup_printf ("new menu element:%s", new_menu_element);
            ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, field_value_pair);
            default_label_txt = (STREQ (new_menu_element, "menu")) ? _("New Menu") : 
                                ((STREQ (new_menu_element, "pipe menu")) ? _("New Pipe Menu") : 
                                 _("New Item"));
            gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), default_label_txt);
            // In case that currently a (pipe) menu or item without label is selected.
            gtk_widget_set_sensitive (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], TRUE);

            gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), "");

            gtk_label_set_markup (GTK_LABEL (ks.mandatory), ks.mandatory_label_txt->str); // Default

            if (menu_or_pipe_menu) {
                g_autofree gchar *menu_ID;
                guint menu_id_index = 1;

                // Menu IDs have to be unique, so the list of menu IDs has to be checked for existing values.
                while (TRUE) {
                    g_autofree gchar *menu_ID_loop = g_strdup_printf (_("new menu %i"), menu_id_index++);

                    if (!g_slist_find_custom (ks.menu_ids, menu_ID_loop, (GCompareFunc) strcmp)) {
                        menu_ID = g_strdup (menu_ID_loop);

                        break;
                    }
                }

                field_value_pair = g_strdup_printf ("menu ID field:%s", menu_ID);
                ks.enter_values_user_settings = g_slist_prepend (ks.enter_values_user_settings, field_value_pair);

                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]), menu_ID);
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]), "");

                if (G_UNLIKELY (g_slist_find_custom (ks.menu_ids, "", (GCompareFunc) strcmp))) {
                    g_string_printf (ks.menu_ID_field_txt, _(" Menu ID (<span foreground='%s'>*</span>): "), ks.blue_hue);

                    gtk_label_set_markup (GTK_LABEL (ks.entry_labels[MENU_ID_ENTRY]), ks.menu_ID_field_txt->str);
                    gtk_label_set_markup (GTK_LABEL (ks.mandatory), ks.mandatory_empty_string_txt->str);
                }

                ks.handler_id_double_menu_id = g_signal_connect_swapped (ks.entry_fields[MENU_ID_ENTRY], "changed", 
                                                                         G_CALLBACK (highlight_menu_ID_entry_field_for_double_menu_IDs), 
                                                                         NULL);
            }

            gtk_widget_grab_focus (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]);

            gtk_widget_show (ks.entry_grid);
        }
        else { // Add separator
            // NULL = iter of added menu element is not needed for later use.
            add_new_execution ("separator", NULL, same_level);

            push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
            activate_change_done ();
            row_selected ();                              /* ... the toolbar buttons and 
                                                             application menu's undo/redo items get the correct sensitivity settings. */
        }
    }
}

/*

    Creates the action/option combo box. This is done every time from scratch, since a combo box that contains new shorter items 
    doesn't shrink down in size, resulting in a bulky look of the combo box if the size change of the items is considerable.

*/

static void create_combo_box (void)
{
    GtkCellRenderer *action_option_combo_box_renderer;

    ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX] = gtk_combo_box_new_with_model (ks.action_option_combo_box_model);
    action_option_combo_box_renderer = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 
                                action_option_combo_box_renderer, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 
                                    action_option_combo_box_renderer, "text", 0, NULL);

    gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 
                                        action_option_combo_box_renderer, option_list_with_headlines, 
                                        NULL, NULL); // No user data, no destroy notify for user data.*/

    gtk_combo_box_set_id_column (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 0);

    gtk_grid_attach (GTK_GRID (ks.new_action_option_grid_or_table), ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], 
                     4, 0, 1, 1);

    gtk_widget_show (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]);

    ks.handler_id_action_option_combo_box = g_signal_connect (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], "changed", 
                                                              G_CALLBACK (show_action_options), NULL);
}

/* 

    Sets up a cell layout that includes headlines that are highlighted in color.

*/

static void option_list_with_headlines (G_GNUC_UNUSED GtkCellLayout   *cell_layout, 
                                                      GtkCellRenderer *action_option_combo_box_renderer, 
                                                      GtkTreeModel    *action_option_combo_box_model, 
                                                      GtkTreeIter     *action_option_combo_box_iter, 
                                        G_GNUC_UNUSED gpointer         user_data)
{
    g_autofree gchar *action_option_combo_item;

    gtk_tree_model_get (action_option_combo_box_model, action_option_combo_box_iter, 
                        ACTION_OPTION_COMBO_ITEM, &action_option_combo_item, 
                        -1);

    const gboolean headline = g_regex_match_simple (_("Choose|Add"), action_option_combo_item, G_REGEX_CASELESS, 0);

    g_object_set (action_option_combo_box_renderer, 
                  "foreground-rgba", (headline) ? &((GdkRGBA) { 1.0, 1.0, 1.0, 1.0 }) : NULL, 
                  "background-rgba", (g_regex_match_simple (_("Add"), action_option_combo_item, G_REGEX_CASELESS, 0)) ? 
                                     &((GdkRGBA) { 0.0, 0.0, 0.0, 1.0 }) : 
                                     ((g_regex_match_simple (_("Choose"), action_option_combo_item, G_REGEX_CASELESS, 0)) ?  
                                     &((GdkRGBA) { 0.31, 0.31, 0.79, 1.0 }) : NULL),
                  "sensitive", !headline, 
                  NULL);
}

/* 

    Appends possible options for Execute or startupnotify to combobox.

*/

static guint8 generate_action_option_combo_box_items_for_Exe_and_snotify_opts (const gchar *options_type)
{
    const gboolean execute = STREQ (options_type, "Execute");
    GtkTreeIter parent, action_option_combo_box_new_iter;
    const guint8 number_of_opts = (execute) ? NUMBER_OF_EXECUTE_OPTS : NUMBER_OF_STARTUPNOTIFY_OPTS;
    g_autofree gboolean *opts_exist = g_malloc0 (sizeof (gboolean) * number_of_opts); // Initialize all elements to FALSE.

    guint8 number_of_added_opts = 0; // Start value

    if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], options_type)) { // "Execute" or "startupnotify"
        parent = ks.iter;
    }
    else {
        gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
    }

    check_for_existing_options (&parent, number_of_opts, (execute) ? ks.execute_options : ks.startupnotify_options, opts_exist);

    for (guint8 opts_cnt = 0; opts_cnt < number_of_opts; ++opts_cnt) {
        if (!opts_exist[opts_cnt]) {
            gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_new_iter, -1, 
                                               ACTION_OPTION_COMBO_ITEM, (execute) ? 
                                               ks.execute_displayed_txts[opts_cnt] : ks.startupnotify_displayed_txts[opts_cnt],
                                               -1);
            ++number_of_added_opts;
        }
    }

    return number_of_added_opts;
}

/* 

    Shows the action combo box, to which a dynamically generated list of options has been added.

*/

void generate_items_for_action_option_combo_box (const gchar *preset_choice)
{
    GtkTreeIter action_option_combo_box_iter;

    const gint number_of_children_of_iter = gtk_tree_model_iter_n_children (ks.ts_model, &ks.iter);
    const gchar *add_inside_txt = NULL; // Default

    guint8 number_of_added_actions = 0, number_of_added_action_opts = 0, number_of_added_snotify_opts = 0; // Defaults


    // --- Creating combo box and building combo list. ---


    create_combo_box ();

    // Generate "Choose..." headline according to the type of the selected menu element.
    gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1, 
                                       ACTION_OPTION_COMBO_ITEM, 
                                       (STREQ (ks.txt_fields[TYPE_TXT], "item") || 
                                       ((STREQ (ks.txt_fields[TYPE_TXT], "action")) && 
                                        ((STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Reconfigure")) || 
                                        (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Execute") && number_of_children_of_iter == NUMBER_OF_EXECUTE_OPTS) || 
                                        number_of_children_of_iter == 1))) // Exit, Restart, or SessionLogout
                                       ? _("Choose an Action") : 
                                       ((streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL)) ? _("Choose an Option") : _("Choose an Action/Option")), 
                                       -1);

    // Startupnotify options: enabled, name, wmclass, icon
    if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) && 
        streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "startupnotify", "enabled", "name", "wmclass", "icon", NULL)) {
        number_of_added_snotify_opts = generate_action_option_combo_box_items_for_Exe_and_snotify_opts ("startupnotify");
    }

    // Execute options: prompt, command, and startupnotify
    if ((STREQ (ks.txt_fields[TYPE_TXT], "action") && STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Execute")) ||
        (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) && 
        streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", "startupnotify", NULL))) {
        number_of_added_action_opts = generate_action_option_combo_box_items_for_Exe_and_snotify_opts ("Execute");
    }

    // Exit and SessionLogout option: prompt, Restart option: command
    if (STREQ (ks.txt_fields[TYPE_TXT], "action") && 
        streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "Restart", "SessionLogout", NULL) && 
        !gtk_tree_model_iter_has_child (ks.ts_model, &ks.iter)) {
        gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1, 
                                           ACTION_OPTION_COMBO_ITEM, 
                                           (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")) ? "Command" : "Prompt", 
                                           -1);
        number_of_added_action_opts = 1;
    }

    // Actions
    if (streq_any (ks.txt_fields[TYPE_TXT], "item", "action", NULL)) { 
        for (; number_of_added_actions < NUMBER_OF_ACTIONS; ++number_of_added_actions) {
            gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, -1, 
                                               ACTION_OPTION_COMBO_ITEM, ks.actions[number_of_added_actions], 
                                              -1);
        }
    }

    /*
        Check if there are two kinds of addable actions/options: the first one inside the current selection, 
        the second one next to the current selection. If so, add headlines to the list to separate them from each other.
    */
    if (number_of_added_snotify_opts && number_of_added_action_opts) {
        add_inside_txt = _("Add Inside Startupnotify");
    }
    else if (number_of_added_action_opts && number_of_added_actions) {
        if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Execute")) {
            add_inside_txt = _("Add Inside Currently Selected Execute Action");
        }
        else if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Exit")) {
            add_inside_txt = _("Add Inside Currently Selected Exit Action");
        }
        else if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")) {
            add_inside_txt = _("Add Inside Currently Selected Restart Action");
        }
        else { // SessionLogout
            add_inside_txt = _("Add Inside Currently Selected SessionLogut Action");
        }
    }

    if (add_inside_txt) {
        gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, 1, 
                                           ACTION_OPTION_COMBO_ITEM, add_inside_txt, 
                                           -1);

        /*
            Position of "Add additional action" headline:
            "Add inside currently selected xxx action" + number of added action options + "Add additional action" == 
            1 + number_of_added_action_opts + 1 -> number_of_added_action_opts + 2 
            Position of "Add next to startupnotify" headline:
            "Add inside startupnotify" + number of added startupnotify options + "Add next to startupnotify" == 
            1 + number_of_added_snotify_opts + 1 -> number_of_added_snotify_opts + 2
        */
        gtk_list_store_insert_with_values (ks.action_option_combo_box_liststore, &action_option_combo_box_iter, 
                                           ((number_of_added_actions) ? 
                                           number_of_added_action_opts : number_of_added_snotify_opts) + 2, 
                                           ACTION_OPTION_COMBO_ITEM, (number_of_added_actions) ? 
                                           _("Add Additional Action") : _("Add Next to Startupnotify"), 
                                           -1);
    }

    // Show the action combo box and deactivate or hide all events and widgets that could interfere.
    hide_or_deactivate_widgets ();

    gtk_widget_show (ks.action_option_grid);
    gtk_widget_hide (ks.options_grid);

    /*
        If this function was called by the context menu or a "Startupnotify" button, 
        preselect the appropriate combo box item.
    */
    if (preset_choice) {
        gtk_combo_box_set_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), preset_choice);
        // If there is only one choice, there is nothing to choose from the combo box.
        if (gtk_tree_model_iter_n_children (ks.action_option_combo_box_model, NULL) == 2) {
            gtk_widget_set_sensitive (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], FALSE);
        }
    }
    else {
        gtk_combo_box_set_active (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]), 0);
        gtk_widget_hide (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
    }

    if (!gtk_widget_get_visible (ks.enter_values_box)) {
        gtk_widget_set_margin_top (ks.new_action_option_grid_or_table, 0);
    }

    gtk_widget_queue_draw (GTK_WIDGET (ks.treeview)); // Force redrawing of treeview (if a search was active).
}

/* 

    Shows the fields that may be changed according to the chosen action/option.

*/

static void show_action_options (void)
{
    const gchar *combo_choice = gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]));
    const gboolean with_new_item = gtk_widget_get_visible (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]);

    guint8 options_cnt, snotify_opts_cnt;

    clear_entries ();

    // Defaults
    gtk_widget_set_margin_bottom (ks.action_option_grid, 5);
    gtk_widget_set_visible (ks.options_grid, !STREQ (combo_choice, "Reconfigure"));
    for (options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; ++options_cnt) {
        gtk_widget_hide (ks.options_labels[options_cnt]);
        gtk_widget_hide (ks.options_fields[options_cnt]);
    }
    gtk_widget_hide (ks.suboptions_grid);
    for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
        gtk_widget_show (ks.suboptions_labels[snotify_opts_cnt]);
        gtk_widget_show (ks.suboptions_fields[snotify_opts_cnt]);
    }
    gtk_label_set_text (GTK_LABEL (ks.options_labels[PROMPT]), " Prompt: ");
    if (!with_new_item) {
        gtk_widget_show (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
        gtk_widget_hide (ks.mandatory);
    }
    gtk_widget_set_margin_bottom (ks.mandatory, (with_new_item) ? 0 : 5);
    gtk_widget_grab_focus ((with_new_item) ? ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY] : ks.options_fields[PROMPT]);

    if (streq_any (combo_choice, "Execute", "Startupnotify", "Enabled", "Name", "WM_CLASS", "Icon", NULL)) {
        for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
            g_autofree gchar *suboptions_label_txt = (streq_any (combo_choice, "Execute", "Startupnotify", "Enabled", NULL)) ? 
                                                     g_strconcat (ks.startupnotify_displayed_txts[snotify_opts_cnt], ": ", NULL) : 
                                                     g_strdup_printf ("%s (<span foreground='%s'>*</span>): ", 
                                                                      ks.startupnotify_displayed_txts[snotify_opts_cnt], 
                                                                      ks.red_hue);

            gtk_label_set_markup (GTK_LABEL (ks.suboptions_labels[snotify_opts_cnt]), suboptions_label_txt);
            gtk_label_set_markup (GTK_LABEL (ks.mandatory), ks.mandatory_label_txt->str);
        }
    }

    // "Execute" action
    if (STREQ (combo_choice, "Execute")) {
        for (options_cnt = 0; options_cnt < NUMBER_OF_EXECUTE_OPTS; ++options_cnt) {
            gtk_label_set_markup (GTK_LABEL (ks.options_labels[options_cnt]), (options_cnt != COMMAND) ? 
                                                                              ks.options_label_txts[options_cnt] : 
                                                                              ks.command_field_txt->str);

            gtk_widget_show (ks.options_labels[options_cnt]);
            gtk_widget_show (ks.options_fields[options_cnt]);
        }
        gtk_widget_show (ks.mandatory);
        if (!g_signal_handler_is_connected (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options)) {
            ks.handler_id_show_or_hide_startupnotify_options = g_signal_connect (ks.options_fields[SN_OR_PROMPT], "toggled", 
                                                                                 G_CALLBACK (show_or_hide_startupnotify_options), NULL);
        }
    }

    // "Restart" action or "command" option (the latter for "Restart" and "Execute" actions)
    if (streq_any (combo_choice, "Restart", "Command", NULL)) {
        g_autofree gchar *label_txt = (STREQ (combo_choice, "Restart")) ? 
                                      g_strdup (" Command: ") : 
                                      g_strdup_printf (" Command (<span foreground='%s'>*</span>): ", ks.red_hue);

        gtk_label_set_markup (GTK_LABEL (ks.options_labels[COMMAND]), label_txt);
        gtk_widget_show (ks.options_labels[COMMAND]);
        gtk_widget_show (ks.options_fields[COMMAND]);
        if (STREQ (combo_choice, "Command")) { 
            gtk_widget_show (ks.mandatory);
        }
        gtk_label_set_markup (GTK_LABEL (ks.mandatory), ks.mandatory_label_txt->str);
        gtk_widget_grab_focus (ks.options_fields[COMMAND]);
    }

    // "Exit" or "SessionLogout" action or "prompt" option (the latter for "Execute", "Exit", and "SessionLogout" actions)
    if (streq_any (combo_choice, "Exit", "Prompt", "SessionLogout", NULL)) {
         if (!STREQ (combo_choice, "Prompt") || 
            (STREQ (combo_choice, "Prompt") && streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "Exit", "SessionLogout", NULL))) {
            gtk_label_set_text (GTK_LABEL (ks.options_labels[SN_OR_PROMPT]), " Prompt: ");
            gtk_widget_show (ks.options_labels[SN_OR_PROMPT]);
            if (g_signal_handler_is_connected (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options)) {
                g_signal_handler_disconnect (ks.options_fields[SN_OR_PROMPT], ks.handler_id_show_or_hide_startupnotify_options);
            }
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), TRUE);
            gtk_widget_show (ks.options_fields[SN_OR_PROMPT]);
            gtk_widget_grab_focus (ks.options_fields[SN_OR_PROMPT]);
        }
        else { // Option of "Execute" action.
            g_autofree gchar *prompt_label_txt = g_strdup_printf (" Prompt (<span foreground='%s'>*</span>): ", ks.red_hue);

            gtk_widget_show (ks.options_labels[PROMPT]);
            gtk_label_set_markup (GTK_LABEL (ks.options_labels[PROMPT]), prompt_label_txt);
            gtk_widget_show (ks.options_fields[PROMPT]);
            gtk_widget_show (ks.mandatory);

        }
    }

    // "startupnotify" option and its suboptions
    if (streq_any (combo_choice, "Startupnotify", "Enabled", "Name", "WM_CLASS", "Icon", NULL)) {
        gtk_widget_show (ks.suboptions_grid);

        if (STREQ (combo_choice, "Startupnotify")) {
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
            gtk_widget_grab_focus (ks.suboptions_fields[ENABLED]);
        }
        else { // Enabled, Name, WM_CLASS & Icon
            for (snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
                if (!STREQ (combo_choice, ks.startupnotify_displayed_txts[snotify_opts_cnt])) {
                    gtk_widget_hide (ks.suboptions_labels[snotify_opts_cnt]);
                    gtk_widget_hide (ks.suboptions_fields[snotify_opts_cnt]);
                }
                else {
                    gtk_widget_grab_focus (ks.suboptions_fields[snotify_opts_cnt]);
                }
            }
            if (!STREQ (combo_choice, "Enabled")) {
                gtk_widget_show (ks.mandatory);
            }
        }
    }
}

/* 

    Shows the options for startupnotify after the corresponding check box has been clicked.

*/

void show_or_hide_startupnotify_options (void)
{
    const gboolean show_startupnotify_options = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]));

    gtk_widget_set_visible (ks.suboptions_grid, show_startupnotify_options);
    if (!show_startupnotify_options) {
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
        gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[NAME]), "");
        gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[WM_CLASS]), "");
        gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[ICON]), "");
    }
}

/* 

    If only a single entry field is shown, "Enter" is enough, clicking on "Done" is not necessary.

*/

void single_field_entry (void)
{
    /*
        Applies to 
        - the _single_ "Execute" options "command" and "prompt"
        - the _single_ "startupnotify" options "name", "wmclass", and "icon"
        - the "Restart" action and its option "command".

        If action fields are shown in conjunction with item fields, single field entry is not executed.
    */
    if (!gtk_widget_get_visible (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]) && 
        !streq_any (gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])), 
                    "Execute", "Startupnotify", NULL)) {
        action_option_insert ("by combo box");
    }
}

/* 

   Resets everything after the cancel button has been clicked or an insertion has been done.

*/

void hide_action_option_grid (const gchar *origin)
{
    clear_entries ();
    gtk_widget_hide (ks.action_option_grid);
    gtk_widget_set_margin_bottom (ks.action_option_grid, 0);
    gtk_widget_set_margin_top (ks.new_action_option_grid_or_table, 5);

    gtk_widget_show (ks.button_grid);
    if (*ks.search_term->str) {
        gtk_widget_show (ks.find_grid);
    }
    gtk_widget_hide (ks.mandatory);
    gtk_widget_set_margin_bottom (ks.mandatory, 0);

    if (STREQ (origin, "cancel button")) {
        row_selected (); // Reset sensitivity of application menu items and toolbar buttons.
    }
}

/*

    Clears all entries for actions/options.

*/

static void clear_entries (void)
{
    for (guint8 options_cnt = PROMPT; options_cnt <= COMMAND; ++options_cnt) {
        gtk_entry_set_text (GTK_ENTRY (ks.options_fields[options_cnt]), "");
        gtk_style_context_remove_class (gtk_widget_get_style_context (ks.options_fields[options_cnt]), "user_intervention_requested");
    }
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]), FALSE);
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]), TRUE);
    for (guint8 snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
        gtk_entry_set_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt]), "");
        gtk_style_context_remove_class (gtk_widget_get_style_context (ks.suboptions_fields[snotify_opts_cnt]), 
                                        "user_intervention_requested");
    }
}

/* 

    Inserts an action and/or its option(s) with the entered values.

*/

void action_option_insert (const gchar *origin)
{
    const gchar *combo_choice = (streq_any (origin, "by combo box", "enter values screen", NULL)) ? 
                                gtk_combo_box_get_active_id (GTK_COMBO_BOX (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX])) : 
                                "Reconfigure";
    const gboolean options_check_button_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.options_fields[SN_OR_PROMPT]));
    const gboolean enabled_check_button_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.suboptions_fields[ENABLED]));
    const gchar *options_entries[] = { gtk_entry_get_text (GTK_ENTRY (ks.options_fields[PROMPT])), 
                                       gtk_entry_get_text (GTK_ENTRY (ks.options_fields[COMMAND])) };
    const gchar *suboption_entry = NULL; // Initialization avoids compiler warning.

    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    GtkTreeIter new_iter, new_iter2, parent;
    GtkTreeIter execute_parent = ks.iter; // Initialization avoids compiler warning.
    GtkTreeIter execute_iter;

    gint insertion_position = -1; // Default

    // Defaults
    gboolean action = FALSE;
    gboolean execute_done = FALSE;
    gboolean startupnotify_done = FALSE;
    const gchar *option_of_execute = NULL, *option_of_startupnotify = NULL;

    // Defaults
    gboolean used_Execute_opts[NUMBER_OF_EXECUTE_OPTS] = { FALSE };
    guint8 snotify_start = NAME;

    guint8 snotify_opts_cnt;


    // If only one entry or a mandatory field was displayed, check if it has been filled out.
    if (gtk_widget_get_visible (ks.action_option_grid)) {
        if (streq_any (combo_choice, "Execute", "Command", NULL) && !(*options_entries[COMMAND])) {
            visually_indicate_request_for_user_intervention (ks.options_fields[COMMAND], ks.execute_options_css_providers[COMMAND]);
            return;
        }
        else if (STREQ (combo_choice, "Prompt") && // "Execute" action or option of it currently selected.
                 gtk_widget_get_visible (ks.options_fields[PROMPT]) && !(*options_entries[PROMPT])) {
            visually_indicate_request_for_user_intervention (ks.options_fields[PROMPT], ks.execute_options_css_providers[PROMPT]);
            return;
        }
        else {
             for (snotify_opts_cnt = NAME; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
                if (STREQ (combo_choice, ks.startupnotify_displayed_txts[snotify_opts_cnt]) && 
                    !(*gtk_entry_get_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt])))) {
                    // "Enabled" is not part of the css_providers, thus - 1.
                    visually_indicate_request_for_user_intervention (ks.suboptions_fields[snotify_opts_cnt], 
                                                                     ks.suboptions_fields_css_providers[snotify_opts_cnt - 1]);
                    return;
                }
            }
        }
    }

    gtk_widget_hide (ks.mandatory);
    gtk_widget_set_margin_bottom (ks.mandatory, 0);

    /*
        Check if the insertion will be done at the current level and before the last child of the current parent. 
        If so, set the insertion position.
    */
    if (!(STREQ (ks.txt_fields[TYPE_TXT], "item") || 
          (STREQ (ks.txt_fields[TYPE_TXT], "action") && 
           streq_any (combo_choice, "Prompt", "Command", "Startupnotify", NULL)) || 
          (STREQ (ks.txt_fields[TYPE_TXT], "option block") && 
           !streq_any (combo_choice, "Prompt", "Command", NULL)))) {
        g_autoptr(GtkTreePath) path = gtk_tree_model_get_path (ks.ts_model, &ks.iter);
        gint last_path_index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];

        gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
        if (gtk_tree_model_iter_n_children (ks.ts_model, &parent) > 1 && 
            last_path_index < gtk_tree_model_iter_n_children (ks.ts_model, &parent) - 1) {
            insertion_position = last_path_index + 1;
        }
    }


    // --- Create the new row(s). ---


    // Prevents that the default check for change of selection(s) gets in the way.
    g_signal_handler_block (selection, ks.handler_id_row_selected);
    gtk_tree_selection_unselect_all (selection); // The old selection will be replaced by the one of the new row.

    // Execute
    if (STREQ (combo_choice, "Execute")) {
        if (STREQ (ks.txt_fields[TYPE_TXT], "action")) {
            gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
            ks.iter = parent; // Move up one level and start from there.
        }

        gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
                                           TS_MENU_ELEMENT, "Execute",
                                           TS_TYPE, "action",
                                           -1);

        execute_parent = ks.iter;
        execute_iter = new_iter;
        ks.iter = new_iter; // New base level

        action = TRUE;
        execute_done = TRUE;
        insertion_position = -1; // Reset for the options, since they should be always appended.
    }

    // Prompt - only if it is an Execute option and not empty.
    if ((execute_done || STREQ (combo_choice, "Prompt")) && *options_entries[PROMPT]) {
        used_Execute_opts[PROMPT] = TRUE;
    }

    // Command - only if it is an Execute option.
    if ((execute_done || 
        (STREQ (combo_choice, "Command") && !STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")))) {
        used_Execute_opts[COMMAND] = TRUE;
    }

    // Startupnotify
    if ((execute_done && options_check_button_state) || STREQ (combo_choice, "Startupnotify")) {
        used_Execute_opts[STARTUPNOTIFY] = TRUE;
    }

    // Insert Execute option, if used.
    for (guint8 execute_opts_cnt = 0; execute_opts_cnt < NUMBER_OF_EXECUTE_OPTS; ++execute_opts_cnt) {
        if (used_Execute_opts[execute_opts_cnt]) {
            if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL)) {
                gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
                ks.iter = parent; // Move up one level and start from there.
            }
            gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
                                               TS_MENU_ELEMENT, ks.execute_options[execute_opts_cnt], 
                                               TS_TYPE, (execute_opts_cnt != STARTUPNOTIFY) ? "option" : "option block",
                                               TS_VALUE, (execute_opts_cnt != STARTUPNOTIFY) ? 
                                                          options_entries[execute_opts_cnt] : NULL, 
                                               -1);

            if (!execute_done) { // Single option, not via an insertion of an "Execute" action.
                option_of_execute = ks.execute_options[execute_opts_cnt];
                expand_row_from_iter (&ks.iter);
                gtk_tree_selection_select_iter (selection, &new_iter);
                if (execute_opts_cnt == STARTUPNOTIFY) {
                    startupnotify_done = TRUE;
                }
            }
        }
    }

    // Setting parent iterator for startupnotify options, if they are added as single elements.
    if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) && 
        !streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", NULL)) {
        if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "startupnotify")) {
            new_iter = ks.iter;
        }
        else {
            gtk_tree_model_iter_parent (ks.ts_model, &new_iter, &ks.iter);
        }
    }

    // Enabled
    if ((execute_done && options_check_button_state) || streq_any (combo_choice, "Startupnotify", "Enabled", NULL)) {
        snotify_start = ENABLED;
    }

    // Insert startupnotify option, if used.
    for (snotify_opts_cnt = snotify_start; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
        if (snotify_opts_cnt == ENABLED || 
            *(suboption_entry = gtk_entry_get_text (GTK_ENTRY (ks.suboptions_fields[snotify_opts_cnt])))) {
            gtk_tree_store_insert_with_values (ks.treestore, &new_iter2, &new_iter, insertion_position,
                                               TS_MENU_ELEMENT, ks.startupnotify_options[snotify_opts_cnt],
                                               TS_TYPE, "option", 
                                               TS_VALUE, (snotify_opts_cnt == ENABLED) ? 
                                                         ((enabled_check_button_state) ? "yes" : "no") : 
                                                          suboption_entry,
                                              -1);

            expand_row_from_iter (&new_iter);
            if (!execute_done && !startupnotify_done) { // Single option.
                option_of_startupnotify = ks.startupnotify_options[snotify_opts_cnt];
                gtk_tree_selection_select_iter (selection, &new_iter2);    
            }
        }
    }

    // Action other than Execute
    if (streq_any (combo_choice, "Exit", "Reconfigure", "Restart", "SessionLogout", NULL)) {
        if (STREQ (ks.txt_fields[TYPE_TXT], "action")) {
            gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
            ks.iter = parent; // Move up one level and start from there.
        }

        gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, insertion_position,
                                           TS_MENU_ELEMENT, combo_choice,
                                           TS_TYPE, "action",
                                           -1);

        if (!STREQ (combo_choice, "Reconfigure") && !(STREQ (combo_choice, "Restart") && !(*options_entries[COMMAND]))) {
            gtk_tree_store_insert_with_values (ks.treestore, &new_iter2, &new_iter, -1, 
                                               TS_MENU_ELEMENT, (STREQ (combo_choice, "Restart")) ? "command" : "prompt", 
                                               TS_TYPE, "option",
                                               TS_VALUE, (STREQ (combo_choice, "Restart")) ? options_entries[COMMAND] : 
                                                          ((options_check_button_state) ? "yes" : "no"),
                                               -1);
        }

        action = TRUE;
    }

    // Exit & SessionLogout option (prompt) or Restart option (command)
    if ((STREQ (combo_choice, "Prompt") && gtk_widget_get_visible (ks.options_fields[SN_OR_PROMPT])) || 
        (STREQ (combo_choice, "Command") && STREQ (ks.txt_fields [MENU_ELEMENT_TXT], "Restart"))) {
        gtk_tree_store_insert_with_values (ks.treestore, &new_iter, &ks.iter, -1, 
                                           TS_MENU_ELEMENT, (STREQ (combo_choice, "Prompt")) ? "prompt" : "command", 
                                           TS_TYPE, "option",
                                           TS_VALUE, (STREQ (combo_choice, "Command")) ? options_entries[COMMAND] : 
                                                      ((options_check_button_state) ? "yes" : "no"),
                                           -1);

        expand_row_from_iter (&ks.iter);
        gtk_tree_selection_select_iter (selection, &new_iter);
    }

    /*
        Show all children of the new action. If the parent row had not been expanded, expand it is well, 
        but leave all preceding nodes (if any) of the new action collapsed.
    */
    if (action) {
        if (execute_done) {
            ks.iter = execute_parent;
        }
        g_autoptr(GtkTreePath) parent_path = gtk_tree_model_get_path (ks.ts_model, &ks.iter);
        g_autoptr(GtkTreePath) path = gtk_tree_model_get_path (ks.ts_model, (execute_done) ? &execute_iter : &new_iter);

        if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (ks.treeview), parent_path)) {
            gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), parent_path, FALSE);
        }
        // TRUE = expand recursively; this is for a new Execute action with startupnotify options, so the latter are shown.
        gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path, TRUE);

        if (!gtk_widget_get_visible (ks.enter_values_box)) {
            gtk_tree_selection_select_path (selection, path);
        }
    }

    if (ks.settings.autosort_options) {
        // If the new row is an option of Execute, sort all options of the latter and move the selection to the new option.
        if (option_of_execute && gtk_tree_model_iter_n_children (ks.ts_model, &ks.iter) > 1) {
             sort_execute_or_startupnotify_options_after_insertion (selection, &ks.iter, "Execute", option_of_execute);
        }

        // The same for startupnotify. new_iter always points to an "option block" = startupnotify.
        if (option_of_startupnotify && gtk_tree_model_iter_n_children (ks.ts_model, &new_iter) > 1) {
            sort_execute_or_startupnotify_options_after_insertion (selection, &new_iter, 
                                                                   "startupnotify", option_of_startupnotify);
        }
    }

    hide_action_option_grid ("action option insert");

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    /* If an item has been added together with an action, this function was called by 
       one_of_the_enter_values_buttons_pressed () after the addition of the item. 
       The item is selected after this function has returned to one_of_the_enter_values_buttons_pressed (), 
       and only after that row_selected () is called from there. */
    if (!gtk_widget_get_visible (ks.enter_values_box)) {
        push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
        activate_change_done ();
        row_selected ();                              /* ... the toolbar buttons and 
                                                         application menu's undo/redo items get the correct sensitivity settings. */
    }
}

/* 

    Checks for menus or pipe menus that are descendants of the currently processed row.
    If found, their menu ID is passed to a function that removes this ID from the list of menu IDs.

*/

static gboolean check_for_menus (              GtkTreeModel *filter_model, 
                                 G_GNUC_UNUSED GtkTreePath  *filter_path, 
                                               GtkTreeIter  *filter_iter,
                                 G_GNUC_UNUSED gpointer      user_data)
{
    g_autofree gchar *type_txt_filter, *menu_id_txt_filter = NULL;

    gtk_tree_model_get (filter_model, filter_iter, TS_TYPE, &type_txt_filter, -1);

    if (streq_any (type_txt_filter, "menu", "pipe menu", NULL)) {
        gtk_tree_model_get (filter_model, filter_iter, TS_MENU_ID, &menu_id_txt_filter, -1);

        remove_menu_id (menu_id_txt_filter);
    }

    return FALSE;
}

/* 

    Removes a menu ID from the list of menu IDs.

*/

void remove_menu_id (const gchar *menu_id)
{
    GSList *to_be_removed_element = g_slist_find_custom (ks.menu_ids, menu_id, (GCompareFunc) strcmp);
    gpointer data = to_be_removed_element->data;

    ks.menu_ids = g_slist_remove (ks.menu_ids, to_be_removed_element->data);

    /* Cleanup. Moved that to the end for a consistent code style, even if it means to have the gpointer assignment above.
       Also avoids false positives reported by static analyzers for dereferencing a null pointer. */ 
    g_free (data);
}

/* 

    Removes all children of a node.

    Children are removed from bottom to top so the paths of the other children and nodes are kept.
    After the removal of the children the function checks for the existence of each parent
    before it reselects them, because if subnodes of the selected nodes had also been selected,
    they can't be reselected again since they got removed.

*/

void remove_all_children (void)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    /*
        The list of rows is reverted, since if a top to bottom direction was kept and 
        children of selected nodes was also selected, these children would be selected for removal first, 
        but since their own children are also intended for removal, they are unselected again.

        Example:

        Selection done by user:

        -menu ::: selected for removal of children
            -menu
            -menu ::: selected for removal of child
                -menu
            -menu

        First selection loop run:

        -menu 1. element whose children are selected
            -menu ::: selected
            -menu ::: selected
                -menu
            -menu ::: selected

        Second selection loop run:

        -menu
            -menu ::: selected
            -menu -> 2. element whose children are selected, unintended unselection
                -menu ::: selected
            -menu ::: selected
    */
#if GLIB_CHECK_VERSION(2,56,0)
    g_autolist(GtkTreePath) selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.ts_model));
#else
    GList *selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.ts_model));
#endif

    GList *selected_rows_loop;
    GtkTreeIter iter_loop;

    // Prevents that the default check for change of selection(s) gets in the way.
    g_signal_handler_block (selection, ks.handler_id_row_selected); 

    FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
        GtkTreePath *path_loop = selected_rows_loop->data;

        // (Note: Rows are not selected if they are not visible.)
        gtk_tree_view_expand_row (GTK_TREE_VIEW (ks.treeview), path_loop, FALSE);
        gtk_tree_selection_unselect_path (selection, path_loop);
        gtk_tree_path_down (path_loop);
        gtk_tree_model_get_iter (ks.ts_model, &iter_loop, path_loop);
        do {
            gtk_tree_selection_select_iter (selection, &iter_loop);
        } while (gtk_tree_model_iter_next (ks.ts_model, &iter_loop));
        gtk_tree_path_up (path_loop);
    }

    remove_rows ("rm. all chldn.");

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
        // Might be a former subnode that was selected and got removed.
        if (gtk_tree_model_get_iter (ks.ts_model, &iter_loop, selected_rows_loop->data)) {
            gtk_tree_selection_select_iter (selection, &iter_loop);
        }
    }

#if !(GLIB_CHECK_VERSION(2,56,0))
    // Cleanup
    g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
#endif
}

/* 

    Removes all currently selected rows.

*/

void remove_rows (const gchar *origin)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    // The list of rows is reverted, since only with a bottom to top direction the paths will stay the same.
#if GLIB_CHECK_VERSION(2,56,0)
    g_autolist(GtkTreePath) selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.ts_model));
#else
    GList *selected_rows = g_list_reverse (gtk_tree_selection_get_selected_rows (selection, &ks.ts_model));
#endif

    GList *selected_rows_loop;
    GtkTreeIter iter_loop;

    // Prevents that the default check for change of selection(s) gets in the way.
    g_signal_handler_block (selection, ks.handler_id_row_selected);

    FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
        GtkTreePath *path_loop = selected_rows_loop->data;

        gtk_tree_model_get_iter (ks.ts_model, &iter_loop, path_loop);

        if (!STREQ (origin, "dnd")) { // A drag & drop just moves rows; there are no new, deleted or changed menu IDs in this case.
            g_autofree gchar *type_txt_loop;

            gtk_tree_model_get (ks.ts_model, &iter_loop, TS_TYPE, &type_txt_loop, -1);

            if (streq_any (type_txt_loop, "menu", "pipe menu", NULL)) {
                g_autofree gchar *menu_id_txt_loop;

                // Keep menu IDs in the GSList equal to the menu IDs of the treestore.
                gtk_tree_model_get (ks.ts_model, &iter_loop, TS_MENU_ID, &menu_id_txt_loop, -1);

                remove_menu_id (menu_id_txt_loop);

                /*
                    If the to be deleted row is a menu that contains other menus as children, 
                    their menu IDs have to be deleted, too.
                */
                if (gtk_tree_model_iter_has_child (ks.ts_model, &iter_loop)) { // Has to be a menu, because pipe menus don't have children.
                    g_autoptr(GtkTreeModel) filter_model = gtk_tree_model_filter_new (ks.ts_model, path_loop);

                    gtk_tree_model_foreach (filter_model, (GtkTreeModelForeachFunc) check_for_menus, NULL);
                }
            }
        }

        gtk_tree_store_remove (GTK_TREE_STORE (ks.ts_model), &iter_loop);
    }

    // If all rows have been deleted and the search functionality had been activated before, deactivate the latter.
    if (!gtk_tree_model_get_iter_first (ks.ts_model, &iter_loop) && gtk_widget_get_visible (ks.find_grid)) {
        show_or_hide_find_grid ();
    }

    g_signal_handler_unblock (selection, ks.handler_id_row_selected);

    if (!streq_any (origin, "dnd", "load menu", NULL)) { // There might be further changes during Drag & Drop / loading a menu.
        push_new_item_on_undo_stack (NOT_AFTER_SAVE); // First update undo stack so ...
        activate_change_done ();
        row_selected ();                              /* ... the toolbar buttons and 
                                                         application menu's undo/redo items get the correct sensitivity settings. */
    }

#if !(GLIB_CHECK_VERSION(2,56,0))
    // Cleanup
    g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
#endif
}
