/*
   Kickshaw - A Menu Editor for Openbox

   Copyright (c) 2010–2025        Marcus Schätzle

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

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

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

#include <gtk/gtk.h>

#include <string.h> // May need to be included explicitly in some environments.

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

/*

    Deactivates or hides widgets whose actions could interfere while entering 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);    

        // A menu exists with at least one node, and at least one is 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: the menu is empty, but an edit has been made.
           case 2: the menu is not empty and no edit has been made. */
        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
        }
       /* No menu exists and there have been no edits 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);
}

/*

    Executes actions initiated from the “Enter values” screen.

*/

void one_of_the_enter_values_buttons_pressed (const gchar *origin)
{
    GSList *enter_values_user_settings_loop;
    // AD = Adding and Deleting; these enums are only used in this file.
    enum { AD_FIELD, AD_VALUE };

    // Not const; this might 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, reset 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 value.
        gboolean missing_entry_or_error = FALSE; // Default value.

        // Missing label for the 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");
        }

        // Missing “Execute” for a 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 an 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 an error message if an error occurs; FALSE = not during undo/redo.
        if (*icon_path && !set_icon (&ks.iter, icon_path, TRUE, FALSE)) {
            missing_entry_or_error = TRUE;
        }

        // Prevent duplicate menu IDs.
        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);
        /* If an action is subsequently added to a new item, to_be_selected_iter will be 
           used to select the added item because ks.iter might have changed. */
        ks.iter = to_be_selected_iter;

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

        // Select first so the selection is saved in the new undo stack item created afterward.
        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 correct sensitivity settings. */
    }
}

/*

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

    Actions and multiple options are not added by this function; action_option_insert() handles those.

*/

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 value.
    g_autofree gchar *icon_modification_time_txt = NULL; // Default value.
    g_autoptr(GString) icon_path_txt = g_string_new (NULL); // Default value.
    const gchar *element_visibility_txt = NULL; // Default value.

    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 value.
    GtkTreeIter new_iter;

    gboolean path_is_on_toplevel = TRUE; // Default value.


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

    // Prevent the default selection-change handler from interfering.
    g_signal_handler_block (selection, ks.handler_id_row_selected);

    gtk_tree_selection_unselect_all (selection); // Replace the old selection with the selection 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: expand only 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 for 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 the new menu element is added at the top level;
            such a menu element is always visible after 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);

    /*
        If autosorting is enabled and the new row is
        - a prompt or command option of Execute, or
        - an option of startupnotify,
        then sort all options under 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 may point to an item that will be selected after an additional action is added.
    if (to_be_selected_iter) {
        *to_be_selected_iter = new_iter;
    }
}

/*

    Highlights the menu ID entry field if the currently typed menu ID already exists.

*/

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

/* 

    Preparations for adding a new menu element and displaying the “Enter values” screen.

*/

void add_new (const gchar *new_menu_element_plus_opt_suppl)
{
    // Ensure static analyzers do not assume 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 value.
    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 = the iter of the added menu element is not needed later.
        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 correct sensitivity settings. */
    }
    else {
        gchar *field_value_pair;

        /*
            If the selection currently points to a menu, decide whether to insert 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 combo box is set in case the user changes their 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]);
                    }
                }
                // Must be after possibly setting the combo box, because that 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 a (pipe) menu or item without label is currently 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 setting.

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

                // Menu IDs must be unique; check against the existing list.
                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))) {
                    // Translation note: If the target language distinguishes between definite and indefinite forms, 
                    // please use the indefinite form (“Menu ID”), not the definite one (“The Menu ID”).
                    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 = the iter of the added menu element is not needed later.
            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. Recreates it every time, because a combo box with formerly longer items
    does not shrink accordingly, which would otherwise leave a bulky-looking widget.

*/

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 highlighted with 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);

    // Translation note: Ignore this; it must be set by a developer in the PO file.
    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, 
                                     // Translation note: Ignore this; it must be set by a developer in the PO file.
                  "background-rgba", (g_regex_match_simple (_("Add"), action_option_combo_item, G_REGEX_CASELESS, 0)) ? 
                                     &((GdkRGBA) { 0.0, 0.0, 0.0, 1.0 }) : 
                                     // Translation note: Ignore this; it must be set by a developer in the PO file.
                                     ((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 available options for Execute or startupnotify to the combo box.

*/

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

/* 

    Displays the action/option combo box with a dynamically generated list of items.

*/

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 value.

    guint8 number_of_added_actions = 0, number_of_added_action_opts = 0, number_of_added_snotify_opts = 0; // Default values.


    // --- Create the combo box and build the list. ---


    create_combo_box ();

    // Generate the “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 whether there are two kinds of addable entries: 
        1) inside the current selection, and 
        2) next to the current selection.
        If so, add headlines to separate them in the list.
    */
    if (number_of_added_snotify_opts && number_of_added_action_opts) {
        // Translation note: Do not translate the word "Startupnotify". Context menu label.
        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")) {
		    // Translation note: Do not translate the word "Execute". Context menu label.
            add_inside_txt = _("Add Inside Currently Selected Execute Action");
        }
        else if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Exit")) {
		    // Translation note: Do not translate the word "Exit". Context menu label.
            add_inside_txt = _("Add Inside Currently Selected Exit Action");
        }
        else if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "Restart")) {
		    // Translation note: Do not translate the word "Restart". Context menu label.
            add_inside_txt = _("Add Inside Currently Selected Restart Action");
        }
        else { // SessionLogout
		    // Translation note: Do not translate the word "SessionLogout". Context menu label.
            add_inside_txt = _("Add Inside Currently Selected SessionLogout 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) ? 
                                           // Translation note: Context menu label.
                                           _("Add Additional Action") : 
										   // Translation note: Translate "next to" as a phrase. A "Next" is not added to Startupnotify.
										   _("Add Next to Startupnotify"), 
                                           -1);
    }

    // Show the 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 fields that can be changed depending on the selected 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]);
    const gchar *right_to_left = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) ? "&#8207;" : "";

    guint8 options_cnt, snotify_opts_cnt;

    clear_entries ();

    // Default settings.
    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]);
    }
    g_autofree gchar *prompt_label_text = g_strdup_printf  ("%s Prompt: ", right_to_left);
    gtk_label_set_markup (GTK_LABEL (ks.options_labels[PROMPT]), prompt_label_text);
    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_strdup_printf ("%s%s: ", 
                                                                      right_to_left, 
                                                                      ks.startupnotify_displayed_txts[snotify_opts_cnt]) : 
                                                     g_strdup_printf ("%s%s (<span foreground='%s'>*</span>): ", 
                                                                      right_to_left, 
                                                                      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_printf ("%s Command: ", right_to_left) : 
                                      g_strdup_printf ("%s Command (<span foreground='%s'>*</span>): ", right_to_left, ks.red_hue);

        gtk_widget_show (ks.options_labels[COMMAND]);
        gtk_label_set_markup (GTK_LABEL (ks.options_labels[COMMAND]), label_txt);
        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_markup (GTK_LABEL (ks.options_labels[SN_OR_PROMPT]), prompt_label_text);
            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 ("%s Prompt (<span foreground='%s'>*</span>): ", right_to_left, 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 or hides startupnotify options depending on the checkbox state.

*/

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, pressing Enter is sufficient; clicking Done is unnecessary.

*/

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 “command” option.

        If action fields are shown together with item fields, this 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 UI after Cancel has been clicked or after an insertion has been made.

*/

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 value.

    // Default values.
    gboolean action = FALSE;
    gboolean execute_done = FALSE;
    gboolean startupnotify_done = FALSE;
    const gchar *option_of_execute = NULL, *option_of_startupnotify = NULL;

    // Default values.
    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, ensure 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 its option 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 css_providers; hence - 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 whether the insertion will be performed 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); // Replace the old selection with the selection 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 options; they should always be 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 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;
                }
            }
        }
    }

    // Set parent iterator for startupnotify options if 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 as 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; used 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 under Execute 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);
        }

        // Do 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 was added together with an action, this function was called by 
       one_of_the_enter_values_buttons_pressed() after adding the item. 
       The item is selected after this function returns, and only then is row_selected() 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, pass their menu IDs to the function that removes those IDs from the list.

*/

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 to the end for consistent style, even if it means the gpointer assignment has to be added above.
       Also avoids false positives from static analyzers about dereferencing a null pointer. */
    g_free (data);
}

/* 

    Removes all children of a node.

    Children are removed from bottom to top so the paths of other children and nodes remain valid.
    After removing the children, the function checks for the existence of each parent
    before reselecting them, because if subnodes of the selected nodes were also selected,
    they can no longer be reselected since they were removed.

*/

void remove_all_children (void)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    /*
        Reverse the list of selected rows. If we processed from top to bottom and the children 
        of selected nodes were also selected, those children would be removed first. However, 
        since their own children are also intended for removal, any children of user-selected 
        nodes that have their own children would be inadvertently unselected again.

        Example:

        User selection:

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

        First loop run (select children):
        The children of the user-selected nodes are selected, while the user-selected nodes themselves are unselected — this is correct.

        - menu
            - menu ::: selected
            - menu ::: selected
                - menu
            - menu ::: selected

        Second loop run:
        If the selected rows have children, the selection state of those rows is reversed — this is incorrect.

        - menu
            - menu ::: selected
            - menu (incorrect selection state!)
                - 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));
    // Reverse the list so that paths remain valid (remove from bottom to top).
#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;

    // Prevent the default selection-change handler from interferi
    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")) { // Drag & drop just moves rows; there are no new, deleted, or changed menu IDs.
            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 in sync with 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 row to be deleted is a menu that contains other menus as children,
                    their menu IDs must be deleted as well.
                */
                if (gtk_tree_model_iter_has_child (ks.ts_model, &iter_loop)) { // Must be a menu; 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 were deleted and the search UI was visible before, hide 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
}
