/*
   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 Kicks.haw. 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 "selecting.h"

static void all_options_have_been_set_msg (const gchar *action_option_txt);
static gboolean avoid_overlapping (G_GNUC_UNUSED gpointer user_data);
static gboolean check_for_selected_dsct (              GtkTreeModel *filter_model,
                                                       GtkTreePath  *filter_path, 
                                         G_GNUC_UNUSED GtkTreeIter  *filter_iter, 
                                                       gboolean     *selected_row_has_selected_dsct);

/* 

    Displays a message if all options for execute or startupnotify have been set.

*/

static void all_options_have_been_set_msg (const gchar *msg_label_txt)
{
    gtk_widget_hide (ks.add_image);
    gtk_widget_hide (ks.bt_add[ACTION_OR_OPTION]);

    gtk_label_set_text (GTK_LABEL (ks.bt_bar_label), msg_label_txt);
}

/* 

    Checks if a selected node has a descendant that has also been selected.

*/

static gboolean check_for_selected_dsct (              GtkTreeModel *filter_model,
                                                       GtkTreePath  *filter_path, 
                                         G_GNUC_UNUSED GtkTreeIter  *filter_iter, 
                                                       gboolean     *selected_row_has_selected_dsct)
{
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
    // The path of the model, not filter model, is needed to check whether the row is selected.
    g_autoptr(GtkTreePath) model_path = gtk_tree_model_filter_convert_path_to_child_path ((GtkTreeModelFilter *) filter_model, 
                                                                                          filter_path);

    if (gtk_tree_selection_path_is_selected (selection, model_path)) {
        *selected_row_has_selected_dsct = TRUE;
    }

    return *selected_row_has_selected_dsct; // Stop iteration if a selected descendant has been found.
}

/* 

    If one or more rows have been selected, all (in)appropriate actions for them are (de)activated according to 
    criteria as e.g. their type (menu, item, etc.), expansion and visibility status, position, and the number of 
    selected rows.
    If there is currently no selected row left after an unselection, 
    all actions that would be eligible in case of a selection are deactivated.

*/

void row_selected (void)
{
    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);
#if GLIB_CHECK_VERSION(2,56,0)
    g_autolist(GtkTreePath) selected_rows = gtk_tree_selection_get_selected_rows (selection, &ks.ts_model);
#else
    GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, &ks.ts_model);
#endif

    gboolean dragging_enabled = TRUE; // Default

    // Defaults
    gboolean at_least_one_selected_row_has_no_children = FALSE;
    gboolean at_least_one_descendant_is_invisible = FALSE;

    GList *selected_rows_loop;
    GtkTreeIter iter_loop;
    GtkTreePath *path_loop;

    guint8 menu_items_cnt, buttons_cnt, entry_fields_cnt;

    const gboolean treestore_is_empty = !gtk_tree_model_get_iter_first (ks.ts_model, &iter_loop); // using iter_loop spares one v.

    // Resets
    if (ks.statusbar_msg_shown) {
        gtk_statusbar_remove_all (GTK_STATUSBAR (ks.statusbar), 1); // Only one context (indicated by 1) with one message.
        ks.statusbar_msg_shown = FALSE;
    }
    gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (ks.treeview), GDK_BUTTON1_MASK, ks.enable_list, 1, GDK_ACTION_MOVE);

    if (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]) {
        g_signal_handler_disconnect (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX], ks.handler_id_action_option_combo_box);
        gtk_list_store_clear (ks.action_option_combo_box_liststore);
        gtk_widget_destroy (ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX]);
        ks.new_action_option_widgets[NEW_ACTION_OPTION_COMBO_BOX] = NULL;
    }

    // Check if dragging has to be blocked.
    if (number_of_selected_rows > 1) {
        // Defaults
        gboolean selected_row_has_selected_dsct = FALSE;
        gboolean menu_pipemenu_item_separator_selected = FALSE;
        gboolean at_least_one_prompt_command_startupnotify_selected = FALSE;
        gboolean at_least_one_enabled_name_wmclass_icon_selected = FALSE;
        gboolean action_selected = FALSE;
        gboolean option_selected = FALSE;
        gboolean at_least_one_option_more_than_once_selected = FALSE;

        gchar *all_options[] = { "prompt", "command", "startupnotify", "enabled", "name", "wmclass", "icon" }; 
        const guint NUMBER_OF_ALL_OPTIONS = G_N_ELEMENTS (all_options);
        G_GNUC_EXTENSION guint number_of_options_selected_per_opt[] = { [0 ... NUMBER_OF_ALL_OPTIONS - 1] = 0 };

        FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
            g_autofree gchar *menu_element_txt_loop, 
                             *type_txt_loop;
        
            path_loop = selected_rows_loop->data;
            gtk_tree_model_get_iter (ks.ts_model, &iter_loop, path_loop);
            gtk_tree_model_get (ks.ts_model, &iter_loop, 
                                TS_MENU_ELEMENT, &menu_element_txt_loop,
                                TS_TYPE, &type_txt_loop, 
                                -1);

            // Check if a selected row has a selected child.
            if (gtk_tree_model_iter_has_child (ks.ts_model, &iter_loop)) {
                g_autoptr(GtkTreeModel) filter_model = gtk_tree_model_filter_new (ks.ts_model, path_loop);

                gtk_tree_model_foreach (filter_model, (GtkTreeModelForeachFunc) check_for_selected_dsct, 
                                        &selected_row_has_selected_dsct);
            }
            if (streq_any (type_txt_loop, "menu", "pipe menu", "item", "separator", NULL)) {
                menu_pipemenu_item_separator_selected = TRUE;
            }
            else if (STREQ (type_txt_loop, "action")) {
                action_selected = TRUE;
            }
            else { // Option or option block
                option_selected = TRUE;

                for (guint8 options_cnt = 0; options_cnt < NUMBER_OF_ALL_OPTIONS; ++options_cnt) {
                    if (STREQ (menu_element_txt_loop, all_options[options_cnt])) {
                        if (++number_of_options_selected_per_opt[options_cnt] > 1) {
                            at_least_one_option_more_than_once_selected = TRUE;
                        }
                        if (streq_any (menu_element_txt_loop, "prompt", "command", "startupnotify", NULL)) {
                            at_least_one_prompt_command_startupnotify_selected = TRUE;
                        }
                        else {
                            at_least_one_enabled_name_wmclass_icon_selected = TRUE;
                        }
                    }
                }
            }
        }

        if ((menu_pipemenu_item_separator_selected && (action_selected || option_selected)) || 
            (action_selected && option_selected) || 
            (at_least_one_prompt_command_startupnotify_selected && at_least_one_enabled_name_wmclass_icon_selected) || 
            at_least_one_option_more_than_once_selected || 
            selected_row_has_selected_dsct) { // Dragging is blocked
            g_autoptr(GString) statusbar_msg = g_string_new (NULL);

            dragging_enabled = FALSE;
            if (menu_pipemenu_item_separator_selected && (action_selected || option_selected)) {
                g_string_append (statusbar_msg, _("Menu/item/separator and action/option selected   "));
            }
            if (action_selected && option_selected) {
                g_string_append (statusbar_msg, _("Action and option selected   "));
            }
            if (at_least_one_prompt_command_startupnotify_selected && at_least_one_enabled_name_wmclass_icon_selected) {
                g_string_append (statusbar_msg, _("Action option and startupnotify option selected   "));
            }
            if (at_least_one_option_more_than_once_selected) {
                g_string_append (statusbar_msg, _("More than one option of a kind selected   "));
            }
            // This message is only shown if the other cases don't apply.
            if (!(*statusbar_msg->str)) { // -> selected_row_has_selected_dsct = TRUE
                g_string_append (statusbar_msg, _("Row and descendant of it selected   "));
            }
            g_string_prepend (statusbar_msg, _("Dragging disabled --- "));
            show_msg_in_statusbar (statusbar_msg->str);

            // Cleanup
            gtk_tree_view_unset_rows_drag_source (GTK_TREE_VIEW (ks.treeview));
        }
    }

    if (dragging_enabled) {
        /*
            Create a list of row references of the selected paths for possible later drag and drop usage.
            An existing list is cleared first.
        */
#if GLIB_CHECK_VERSION(2,64,0)
        g_clear_slist (&ks.source_paths, (GDestroyNotify) gtk_tree_row_reference_free);
#else
        g_slist_free_full (ks.source_paths, (GDestroyNotify) gtk_tree_row_reference_free);
        ks.source_paths = NULL;
#endif
        FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
            ks.source_paths = g_slist_prepend (ks.source_paths, gtk_tree_row_reference_new (ks.ts_model, selected_rows_loop->data));
        }
        ks.source_paths = g_slist_reverse (ks.source_paths);
    }

    // Default settings

    if (gtk_widget_get_visible (ks.action_option_grid) || gtk_widget_get_visible (ks.enter_values_box)) {
        if (gtk_widget_get_visible (ks.action_option_grid)) {
            hide_action_option_grid ("selection");
        }

        if (gtk_widget_get_visible (ks.enter_values_box)) {
#if GLIB_CHECK_VERSION(2,64,0)
            g_clear_slist (&ks.enter_values_user_settings, (GDestroyNotify) g_free);
#else
            g_slist_free_full (ks.enter_values_user_settings, (GDestroyNotify) g_free);
            ks.enter_values_user_settings = NULL;
#endif
            gtk_box_reorder_child (GTK_BOX (ks.sub_box), ks.entry_grid, -1);
            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");
            if (gtk_widget_get_visible (ks.entry_fields[MENU_ID_ENTRY])) {
                GtkStyleContext *context = gtk_widget_get_style_context (ks.entry_fields[MENU_ID_ENTRY]);
                if (gtk_style_context_has_class (context, "double_menu_ID")) {
                    gtk_style_context_remove_class (context, "double_menu_ID");
                }
                /* This if statement is necessary, since row_selected() is called at startup, 
                   when ks.enter_values_box is still visible, but the signal for ks.entry_fields[MENU_ID_ENTRY] not yet connected. */
                if (g_signal_handler_is_connected (ks.entry_fields[MENU_ID_ENTRY], ks.handler_id_double_menu_id)) {
                    g_signal_handler_disconnect (ks.entry_fields[MENU_ID_ENTRY], ks.handler_id_double_menu_id);
                }
                gtk_widget_set_visible (ks.double_menu_id_label, FALSE);
            }                              
            for (entry_fields_cnt = 0; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_fields_cnt) {
                if (!g_signal_handler_is_connected (ks.entry_fields[entry_fields_cnt], ks.handler_id_entry_fields[entry_fields_cnt])) {
                    ks.handler_id_entry_fields[entry_fields_cnt] = g_signal_connect_swapped (ks.entry_fields[entry_fields_cnt], 
                                                                                             "activate", 
                                                                                             G_CALLBACK (change_row), 
                                                                                             GUINT_TO_POINTER (entry_fields_cnt));
                }
            }
            gtk_widget_hide (ks.enter_values_box);
            gtk_widget_hide (ks.new_action_option_widgets[INSIDE_MENU_LABEL]);
            gtk_widget_hide (ks.new_action_option_widgets[INSIDE_MENU_CHECK_BUTTON]);
            gtk_widget_hide (ks.new_action_option_widgets[INCLUDING_ACTION_LABEL]);
            gtk_widget_hide (ks.new_action_option_widgets[INCLUDING_ACTION_CHECK_BUTTON]);
            gtk_widget_show (ks.new_action_option_widgets[ACTION_OPTION_DONE]);
            gtk_widget_show (ks.new_action_option_widgets[ACTION_OPTION_CANCEL]);
            gtk_widget_show (ks.remove_icon);
            gtk_label_set_markup (GTK_LABEL (ks.entry_labels[MENU_ID_ENTRY]), _(" Menu ID: "));
            gtk_widget_hide (ks.mandatory);
            gtk_label_set_markup (GTK_LABEL (ks.mandatory), ks.mandatory_label_txt->str);
            gtk_widget_set_margin_top (ks.mandatory, 0);
            gtk_widget_set_margin_bottom (ks.mandatory, 0);
            gtk_widget_hide (ks.separator);
            gtk_widget_hide (ks.enter_values_buttons_grid);
            gtk_widget_show (ks.button_grid);
        }
    }

    if (ks.settings.show_menu_button) {
        guint8 menus_cnt;

        g_menu_remove_all (ks.popup_menu);

        FOREACH_IN_ARRAY (ks.MBut_menu_items, menus_cnt) {
            g_menu_append_item (ks.popup_menu, ks.MBut_menu_items[menus_cnt]);
        }
        
        g_action_map_remove_action (G_ACTION_MAP (ks.app), "visualize");
    }
    else {
        // Defaults
        gtk_widget_set_sensitive (ks.MBar_menu_items[EDIT_MENU], TRUE);
        gtk_widget_set_sensitive (ks.MBar_menu_items[SEARCH_MENU], TRUE);
        gtk_widget_set_sensitive (ks.MBar_menu_items[OPTIONS_MENU], TRUE);
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_VISUALIZE], FALSE);
    }

    gtk_widget_show (ks.add_image);
    gtk_label_set_text (GTK_LABEL (ks.bt_bar_label), _("Add New:  "));
    for (buttons_cnt = 0; buttons_cnt < ACTION_OR_OPTION; ++buttons_cnt) {
        gtk_widget_show (ks.bt_add[buttons_cnt]);
    }
    gtk_widget_hide (ks.bt_add[ACTION_OR_OPTION]);
    /* If there is no row that is currently selected, row_selected () will return before the iterator is set.
       That's why the sensitivity is set directly here instead of calling set_forward_and_back_buttons_of_find_grid (). */
    gtk_widget_set_sensitive (ks.find_entry_buttons[BACK], FALSE);
    gtk_widget_set_sensitive (ks.find_entry_buttons[FORWARD], FALSE);
    gtk_widget_hide (ks.entry_grid);
    gtk_widget_queue_draw (ks.treeview); // This lets the treeview fill the possible additional space left by a removed entry grid.

    if (ks.settings.show_menu_button) {
        if (!treestore_is_empty || ks.change_done) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.file_actions[M_NEW]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "new");        
        }

        if (ks.filename && ks.change_done) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.file_actions[M_SAVE]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "save");
        }
    }
    else {
        gtk_widget_set_sensitive (ks.MBar_file_menu_items[M_NEW], (!treestore_is_empty || ks.change_done));
        gtk_widget_set_sensitive (ks.MBar_file_menu_items[M_SAVE], (ks.filename && ks.change_done));
    }
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_NEW], (!treestore_is_empty || ks.change_done));
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_SAVE], (ks.filename && ks.change_done));

    g_autoptr(GString) save_menu_tooltip = g_string_new (_("Save Menu"));
    if (G_UNLIKELY (ks.pos_inside_undo_stack == 1 && ks.number_of_undo_stack_items == 1)) {
        gboolean edit_done_during_loading = (ks.loading_process_edit_types[ICON_INFO_CORRECTED] || 
                                             ks.loading_process_edit_types[INVALID_OPTION_VALUE_CORRECTED] || 
                                             ks.loading_process_edit_types[INVISIBLE_ELEMENT_VISUALIZED_OR_REMOVED] || 
                                             ks.loading_process_edit_types[DEPRECATED_EXE_CMD_CVTD]);

        guint8 edit_types_done = 0;
        guint8 corrections_cnt;

        gchar *save_menu_tooltip_txts[] = { N_("At least one incorrect icon path or format has been corrected"), 
                                            N_("At least one incorrect option value has been corrected"), 
                                            N_("At least one invisible menu element has been visualized or removed"), 
                                            N_("At least one \"execute\" option has been converted") };

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

        if (edit_done_during_loading) {
            g_string_append_printf (save_menu_tooltip, "\n— — — — —\n%s<span size=\"3000\"> </span>\n", 
                                                       _("Although the tree view has not yet been edited, the following changes "
                                                         "have already been made during the loading process. These have not yet "
                                                         "been written back.\n"));

            for (corrections_cnt = 0; corrections_cnt < NUMBER_OF_LOADING_PROCESS_EDIT_TYPES; corrections_cnt++) {
                if (ks.loading_process_edit_types[corrections_cnt]) {
                    g_string_append_printf (save_menu_tooltip, "• %s%s", 
                                            _(save_menu_tooltip_txts[corrections_cnt]), (edit_types_done > 1) ? "\n" : "");
                }
            }

            g_string_append_printf (save_menu_tooltip, "%s<span size=\"3000\"> </span>\n%s", 
                                                       (edit_types_done == 1) ? "\n" : "", 
                                                       _("(This notice is only shown if no edit has yet been done on the tree view.)"));
        }
    }
    gtk_widget_set_tooltip_markup (ks.tb_buttons[TB_BUTTON_SAVE], save_menu_tooltip->str);

    FOREACH_IN_LIST (selected_rows, selected_rows_loop) {
        g_autofree gchar *element_visibility_txt_loop;

        path_loop = selected_rows_loop->data;
        gtk_tree_model_get_iter (ks.ts_model, &iter_loop, path_loop);
        gtk_tree_model_get (ks.ts_model, &iter_loop, TS_ELEMENT_VISIBILITY, &element_visibility_txt_loop, -1);

        if (G_UNLIKELY (element_visibility_txt_loop && strstr (element_visibility_txt_loop, "invisible"))) {
            if (ks.settings.show_menu_button) {
                g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_VISUALIZE]));
            }
            else {
                gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_VISUALIZE], TRUE);
            }
        }
        if (gtk_tree_model_iter_has_child (ks.ts_model, &iter_loop)) {
            g_autoptr(GtkTreeModel) filter_model = gtk_tree_model_filter_new (ks.ts_model, path_loop);

            gtk_tree_model_foreach (filter_model,
                                    (GtkTreeModelForeachFunc) check_if_invisible_descendant_exists, 
                                    &at_least_one_descendant_is_invisible);
        }

        else {
            at_least_one_selected_row_has_no_children = TRUE;
        }
    }

    if (ks.settings.show_menu_button) {
        /*
            If there has been an error during the creation of the initial undo item stack file, 
            ks.pos_inside_undo_stack will remain set to 0.
        */
        if (ks.pos_inside_undo_stack > 1) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_UNDO]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "undo");
        }

        if (ks.pos_inside_undo_stack < ks.number_of_undo_stack_items) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_REDO]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "redo");
        }

        if (number_of_selected_rows > 0) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_REMOVE]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "remove");
        }

        if (!at_least_one_selected_row_has_no_children) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_REMOVE_ALL_CHILDREN]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "removeallch");
        }

        if (g_action_map_lookup_action (G_ACTION_MAP (ks.app), "visualize") && at_least_one_descendant_is_invisible) {
            g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[M_VISUALIZE_RECURSIVELY]));
        }
        else {
            g_action_map_remove_action (G_ACTION_MAP (ks.app), "visualizerec");
        }

        if (treestore_is_empty) {
            g_menu_remove (ks.popup_menu, SEARCH_MENU);
        }
    }
    else {
        /*
            If there has been an error during the creation of the initial undo item stack file, 
            ks.pos_inside_undo_stack will remain set to 0.
        */
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_UNDO], (ks.pos_inside_undo_stack > 1));
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_REDO], (ks.pos_inside_undo_stack < ks.number_of_undo_stack_items));
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_REMOVE], (number_of_selected_rows > 0));
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_REMOVE_ALL_CHILDREN], (!at_least_one_selected_row_has_no_children));
        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[M_VISUALIZE_RECURSIVELY], 
                                  (gtk_widget_get_sensitive (ks.MBar_edit_menu_items[M_VISUALIZE]) && 
                                   at_least_one_descendant_is_invisible));
        gtk_widget_set_sensitive (ks.MBar_menu_items[SEARCH_MENU], (!treestore_is_empty));
    }
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_UNDO], (ks.pos_inside_undo_stack > 1));
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_REDO], (ks.pos_inside_undo_stack < ks.number_of_undo_stack_items));
    gtk_widget_set_sensitive (ks.tb_buttons[TB_BUTTON_FIND], (!treestore_is_empty));
    gtk_widget_set_sensitive (ks.bt_remove, (number_of_selected_rows > 0));

    set_status_of_expand_and_collapse_buttons_and_menu_items ();

    // Show/activate or hide/deactivate certain widgets depending on if there is no or more than one selection.
    if (number_of_selected_rows != 1) {
        free_elements_of_static_string_array (ks.txt_fields, NUMBER_OF_TXT_FIELDS, TRUE);
        FOREACH_IN_ARRAY (ks.bt_movements, buttons_cnt) {
            gtk_widget_set_sensitive (ks.bt_movements[buttons_cnt], FALSE);
        }
        if (number_of_selected_rows == 0) {
            if (ks.number_of_undo_stack_items > 1) {
                for (menu_items_cnt = M_MOVE_TOP; menu_items_cnt <= M_VISUALIZE_RECURSIVELY; ++menu_items_cnt) {
                    if (ks.settings.show_menu_button) {
                        g_action_map_remove_action (G_ACTION_MAP (ks.app), ks.edit_simple_action_txts[menu_items_cnt]);
                    }
                    else {
                        gtk_widget_set_sensitive (ks.MBar_edit_menu_items[menu_items_cnt], FALSE);
                    }
                }
            }
            else {
                if (ks.settings.show_menu_button) { 
                    g_menu_remove (ks.popup_menu, EDIT_MENU);
                }
                else {
                    gtk_widget_set_sensitive (ks.MBar_menu_items[EDIT_MENU], FALSE);
                }
            }
        }
        else { // number of selected rows > 1
            for (menu_items_cnt = M_MOVE_TOP; menu_items_cnt <= M_MOVE_BOTTOM; ++menu_items_cnt) {  
                if (ks.settings.show_menu_button) { 
                    g_action_map_remove_action (G_ACTION_MAP (ks.app), ks.edit_simple_action_txts[menu_items_cnt]);
                }
                else {
                    gtk_widget_set_sensitive (ks.MBar_edit_menu_items[menu_items_cnt], FALSE);
                }
            }
            gtk_widget_hide (ks.add_image);
            for (buttons_cnt = 0; buttons_cnt < NUMBER_OF_ADD_BUTTONS; ++buttons_cnt) {
                gtk_widget_hide (ks.bt_add[buttons_cnt]);
            }
            gtk_label_set_text (GTK_LABEL (ks.bt_bar_label), 
                                _(" Multiple Selections – No Addition of New Menu Elements"));
        }

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

        return;
    }

    // --- Everything from here is for the case that just one row is selected. ---


    GtkTreeIter iter_next, iter_previous;

    GtkTreePath *path = selected_rows->data;
    const gint path_depth = gtk_tree_path_get_depth (path);
    GtkTreeIter parent;
    g_autofree gchar *menu_element_txt_parent = NULL; // Default
    const gchar *preset_choice = NULL; // Default

    gtk_tree_model_get_iter (ks.ts_model, &ks.iter, path);

    if (path_depth > 1) {
        gtk_tree_model_iter_parent (ks.ts_model, &parent, &ks.iter);
        gtk_tree_model_get (ks.ts_model, &parent, TS_MENU_ELEMENT, &menu_element_txt_parent, -1);
    }

    iter_previous = iter_next = ks.iter;

    const gboolean not_at_top = gtk_tree_model_iter_previous (ks.ts_model, &iter_previous);
    const gboolean not_at_bottom = gtk_tree_model_iter_next (ks.ts_model, &iter_next);

    const gboolean option_and_autosort = ks.settings.autosort_options && streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL);

    repopulate_txt_fields_array ();

    for (menu_items_cnt = M_MOVE_TOP; menu_items_cnt <= M_MOVE_BOTTOM; ++menu_items_cnt) {
        if (((menu_items_cnt < M_MOVE_DOWN) ? not_at_top : not_at_bottom) && !option_and_autosort) {
            if (ks.settings.show_menu_button) { 
                g_action_map_add_action (G_ACTION_MAP (ks.app), G_ACTION (ks.edit_actions[menu_items_cnt]));
            }
            else {
                gtk_widget_set_sensitive (ks.MBar_edit_menu_items[menu_items_cnt], TRUE);
            }
        }
        else {
            if (ks.settings.show_menu_button) { 
                g_action_map_remove_action (G_ACTION_MAP (ks.app), ks.edit_simple_action_txts[menu_items_cnt]);
            }
            else {
                gtk_widget_set_sensitive (ks.MBar_edit_menu_items[menu_items_cnt], FALSE);
            }
        }
    }

    gtk_widget_set_sensitive (ks.bt_movements[MOVE_TOP], (not_at_top && !option_and_autosort));
    gtk_widget_set_sensitive (ks.bt_movements[MOVE_UP], (not_at_top && !option_and_autosort));
    gtk_widget_set_sensitive (ks.bt_movements[MOVE_DOWN], (not_at_bottom && !option_and_autosort));
    gtk_widget_set_sensitive (ks.bt_movements[MOVE_BOTTOM], (not_at_bottom && !option_and_autosort));

    set_forward_and_back_buttons_of_find_grid (); // Can't be done in the beginning, since the iterator as not been set yet.

    // Item selected?
    if (STREQ (ks.txt_fields[TYPE_TXT], "item")) {
        gtk_widget_show (ks.bt_add[ACTION_OR_OPTION]);
        gtk_label_set_text_with_mnemonic (GTK_LABEL (ks.bt_add_action_option_label), _("_Action"));
    }

    // Action or option of it selected?
    if (streq_any (ks.txt_fields[TYPE_TXT], "action", "option", "option block", NULL)) {
        const gint number_of_children_of_parent = gtk_tree_model_iter_n_children (ks.ts_model, &parent);
        const gint number_of_children_of_iter = gtk_tree_model_iter_n_children (ks.ts_model, &ks.iter);

        // Defaults
        gboolean prompt_or_command_selected_and_action_is_Execute = FALSE;
        gboolean startupnotify_or_option_of_it_selected = FALSE;

        for (buttons_cnt = 0; buttons_cnt < ACTION_OR_OPTION; ++buttons_cnt) {
            gtk_widget_hide (ks.bt_add[buttons_cnt]);
        }
        gtk_widget_show (ks.bt_add[ACTION_OR_OPTION]);

        // Action selected
        if (STREQ (ks.txt_fields[TYPE_TXT], "action")) {
            gtk_label_set_text_with_mnemonic (GTK_LABEL (ks.bt_add_action_option_label), 
                                              (!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 == 0)) ? 
                                              _("_Action/Option") : _("_Action"));
        }

        // "prompt" or "command" option selected and action is "Execute"?
        if (STREQ (ks.txt_fields[TYPE_TXT], "option") && 
            streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", NULL) && 
            STREQ (menu_element_txt_parent, "Execute")) {
            prompt_or_command_selected_and_action_is_Execute = TRUE;
        }

        // Startupnotify or option of it selected?
        if (streq_any (ks.txt_fields[TYPE_TXT], "option", "option block", NULL) && 
            !streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", NULL)) {
            startupnotify_or_option_of_it_selected = TRUE;
        }

        // Default button text for a selected option
        if (prompt_or_command_selected_and_action_is_Execute || startupnotify_or_option_of_it_selected) {
            gtk_label_set_text_with_mnemonic (GTK_LABEL (ks.bt_add_action_option_label), _("O_ption"));
        }

        if (prompt_or_command_selected_and_action_is_Execute || 
            (STREQ (ks.txt_fields[TYPE_TXT], "option block") && number_of_children_of_iter == NUMBER_OF_STARTUPNOTIFY_OPTS)) {
            // All options of "Execute" are set.
            if (number_of_children_of_parent == NUMBER_OF_EXECUTE_OPTS) {
                all_options_have_been_set_msg (_(" All Options for This Execute Action Have Been Set"));
            }
            // One option of "Execute" is not set.
            else if (number_of_children_of_parent == NUMBER_OF_EXECUTE_OPTS - 1) {
                gboolean execute_options_status[NUMBER_OF_EXECUTE_OPTS] = { 0 };
                const gchar *execute_opts_with_mnemonic[] = { N_("_Prompt"), N_("_Command"), _("Startup_notify") };

                check_for_existing_options (&parent, NUMBER_OF_EXECUTE_OPTS, ks.execute_options, execute_options_status);

                for (guint8 execute_opts_cnt = 0; execute_opts_cnt < NUMBER_OF_EXECUTE_OPTS; ++execute_opts_cnt) {
                    if (!execute_options_status[execute_opts_cnt]) {
                        preset_choice = ks.execute_displayed_txts[execute_opts_cnt];
                        gtk_label_set_text_with_mnemonic (GTK_LABEL (ks.bt_add_action_option_label), 
                                                          _(execute_opts_with_mnemonic[execute_opts_cnt]));

                        break;
                    }
                }
            }
        }

        if (startupnotify_or_option_of_it_selected) {
            // "startupnotify" option block or option of it selected and all options of "startupnotify" are set.
            if ((STREQ (ks.txt_fields[TYPE_TXT], "option block") && 
                number_of_children_of_iter == NUMBER_OF_STARTUPNOTIFY_OPTS && 
                number_of_children_of_parent == NUMBER_OF_EXECUTE_OPTS) || 
                (STREQ (ks.txt_fields[TYPE_TXT], "option") && 
                number_of_children_of_parent == NUMBER_OF_STARTUPNOTIFY_OPTS)) {
                all_options_have_been_set_msg (_(" All Options for This Startupnotify Option Block Have Been Set"));
            }
            // "startupnotify" option block or option of it selected and one option of "startupnotify" not set.
            else if ((STREQ (ks.txt_fields[TYPE_TXT], "option block") && 
                     number_of_children_of_iter == NUMBER_OF_STARTUPNOTIFY_OPTS - 1 && 
                     number_of_children_of_parent == NUMBER_OF_EXECUTE_OPTS) || 
                     (STREQ (ks.txt_fields[TYPE_TXT], "option") && 
                     number_of_children_of_parent == NUMBER_OF_STARTUPNOTIFY_OPTS - 1)) {
                gboolean startupnotify_options_status[NUMBER_OF_STARTUPNOTIFY_OPTS] = { 0 };
                const gchar *startupnotify_opts_with_mnemonic[] = { N_("E_nabled"), N_("_Name"), N_("_WM__CLASS"), N_("_Icon") };

                if (STREQ (ks.txt_fields[TYPE_TXT], "option block")) {
                    parent = ks.iter;
                }

                check_for_existing_options (&parent, NUMBER_OF_STARTUPNOTIFY_OPTS, 
                                            ks.startupnotify_options, startupnotify_options_status);

                for (guint8 snotify_opts_cnt = 0; snotify_opts_cnt < NUMBER_OF_STARTUPNOTIFY_OPTS; ++snotify_opts_cnt) {
                    if (!startupnotify_options_status[snotify_opts_cnt]) {
                        preset_choice = ks.startupnotify_displayed_txts[snotify_opts_cnt];
                        gtk_label_set_text_with_mnemonic (GTK_LABEL (ks.bt_add_action_option_label), 
                                                          _(startupnotify_opts_with_mnemonic[snotify_opts_cnt]));

                        break;
                    }
                }
            }
        }

        /*
            "prompt" option selected and action is "Exit" or "SessionLogout" or
            "command" option selected and action is "Restart".
        */
        if (STREQ (ks.txt_fields[TYPE_TXT], "option") && streq_any (ks.txt_fields[MENU_ELEMENT_TXT], "prompt", "command", NULL) && 
            streq_any (menu_element_txt_parent, "Exit", "Restart", "SessionLogout", NULL)) { 
            const gchar *msg_label_txt;

            if (STREQ (menu_element_txt_parent, "Exit")) {
                msg_label_txt = _(" All Options for This Exit Action Have Been Set");
            }
            else if (STREQ (menu_element_txt_parent, "Restart")) {
                msg_label_txt = _(" All Options for This Restart Action Have Been Set");
            }
            else { // SessionLogout
                msg_label_txt = _(" All Options for This SessionLogout Action Have Been Set");
            }

            all_options_have_been_set_msg (msg_label_txt);
        }
    }

    // Set signal for action/option button.
    if (gtk_widget_get_visible (ks.bt_add[ACTION_OR_OPTION])) {
        if (ks.handler_id_action_option_button_clicked) { // Uninitialized at the beginning = 0.
            g_signal_handler_disconnect (ks.bt_add[ACTION_OR_OPTION], ks.handler_id_action_option_button_clicked);
        }
        ks.handler_id_action_option_button_clicked = g_signal_connect_swapped (ks.bt_add[ACTION_OR_OPTION], "clicked", 
                                                                               G_CALLBACK (generate_items_for_action_option_combo_box), 
                                                                               (gpointer) preset_choice);
    }

    // Entry fields empty by default
    for (entry_fields_cnt = 0; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_fields_cnt) {
        gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[entry_fields_cnt]), "");
    }

    // Set entry fields if not one of the conditions below applies.
    if (!(streq_any (ks.txt_fields[TYPE_TXT], "action", "option block", NULL) || 
        (STREQ (ks.txt_fields[TYPE_TXT], "option") && 
        (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "enabled") || 
        streq_any (menu_element_txt_parent, "Exit", "SessionLogout", NULL))))) {
        set_entry_fields ();
    }

    /* Set the correct treestore position argument for the changed signal of the menu_element_or_value entry.
       It depends on whether the entry is for a menu element or for a value. */ 
    if (gtk_widget_get_visible (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY])) {
        g_signal_handler_disconnect (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], ks.handler_id_entry_field_changed);
        ks.handler_id_entry_field_changed = g_signal_connect_swapped (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], "changed", 
                                                                      G_CALLBACK (build_entry_liststore), 
                                                                      GUINT_TO_POINTER ((!STREQ (ks.txt_fields[TYPE_TXT], "option")) ? 
                                                                                        TS_MENU_ELEMENT : TS_VALUE));
    }

    gtk_widget_queue_draw (ks.treeview); // Force redrawing of treeview.

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

/* 

    Avoids overlapping of a row by the entry grid.

*/

static gboolean avoid_overlapping (G_GNUC_UNUSED gpointer user_data)
{
    g_autoptr(GtkTreePath) path;

    // A row is supposed to be currently selected and thus it should be possible to retrieve a path;
    // If for some reason it isn't possible to do so (anymore), do not attempt to scroll to that row.
    if (G_LIKELY ((path = gtk_tree_model_get_path (ks.ts_model, &ks.iter)))) {
        // There is no horizontal move to a certain column (NULL). Row and column alignments (0,0) are not used (FALSE).
        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ks.treeview), path, NULL, FALSE, 0, 0);
    }

    return G_SOURCE_REMOVE; // After scrolling to path, this function is no longer needed until set_entry_fields() is called again.
}

/* 

    Sets the entry fields that appear below the treeview when a modifiable menu element has been selected.

*/

void set_entry_fields (void)
{
    gtk_widget_show (ks.entry_grid);

    // Default settings
    gtk_label_set_text (GTK_LABEL (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY]), _(" Label: "));
    gtk_label_set_text (GTK_LABEL (ks.entry_labels[EXECUTE_ENTRY]), " Execute: ");
    gtk_widget_set_sensitive (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], TRUE);
    gtk_widget_hide (ks.icon_chooser);
    gtk_widget_hide (ks.remove_icon);
    for (guint8 entry_fields_cnt = ICON_PATH_ENTRY; entry_fields_cnt < NUMBER_OF_ENTRY_FIELDS; ++entry_fields_cnt) {
        gtk_widget_hide (ks.entry_labels[entry_fields_cnt]);
        gtk_widget_hide (ks.entry_fields[entry_fields_cnt]);
    }

    if (ks.txt_fields[ELEMENT_VISIBILITY_TXT]) { // = menu, pipe menu, item or separator
        if (NOT_NULL_AND_NOT_EMPTY (ks.txt_fields[MENU_ELEMENT_TXT])) {
            gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), ks.txt_fields[MENU_ELEMENT_TXT]);
        }
        if (!STREQ (ks.txt_fields[TYPE_TXT], "separator")) {
            g_autoptr(GFile) icon_path = (ks.txt_fields[ICON_PATH_TXT]) ? g_file_new_for_path (ks.txt_fields[ICON_PATH_TXT]) : NULL;

            if (G_UNLIKELY (!ks.txt_fields[MENU_ELEMENT_TXT])) {
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), _("(No Label)"));
                gtk_widget_set_sensitive (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY], FALSE);
            }

            gtk_widget_show (ks.icon_chooser);
            gtk_widget_show (ks.remove_icon);
            gtk_widget_show (ks.entry_labels[ICON_PATH_ENTRY]);
            gtk_widget_show (ks.entry_fields[ICON_PATH_ENTRY]);
            gtk_widget_set_sensitive (ks.remove_icon, ks.txt_fields[ICON_PATH_TXT] != NULL);
            if (ks.txt_fields[ICON_PATH_TXT]) { // No check for empty string necessary, as there can't be a path "".
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[ICON_PATH_ENTRY]), ks.txt_fields[ICON_PATH_TXT]);
            }

            if (!ks.txt_fields[ICON_PATH_TXT] || g_file_query_exists (icon_path, NULL)) {
                GtkStyleContext *entry_context = gtk_widget_get_style_context (ks.entry_fields[ICON_PATH_ENTRY]);

                gtk_style_context_remove_class (entry_context, "user_intervention_requested");
            }
            else {
                visually_indicate_request_for_user_intervention (ks.entry_fields[ICON_PATH_ENTRY], ks.icon_path_entry_css_provider);
            }

            if (!STREQ (ks.txt_fields[TYPE_TXT], "item")) {
                gtk_widget_show (ks.entry_labels[MENU_ID_ENTRY]);
                gtk_widget_show (ks.entry_fields[MENU_ID_ENTRY]);
                gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ID_ENTRY]), ks.txt_fields[MENU_ID_TXT]);
                if (STREQ (ks.txt_fields[TYPE_TXT], "pipe menu")) {
                    gtk_widget_show (ks.entry_labels[EXECUTE_ENTRY]);
                    gtk_widget_show (ks.entry_fields[EXECUTE_ENTRY]);
                    if (NOT_NULL_AND_NOT_EMPTY (ks.txt_fields[EXECUTE_TXT])) {
                        gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[EXECUTE_ENTRY]), ks.txt_fields[EXECUTE_TXT]);
                    }
                }
            }
        }
    }
    // Option other than "startupnotify" and "enabled".
    else {
        g_autofree gchar *label_txt = g_strdup_printf (" %s: ", 
                                                       (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "wmclass")) ? 
                                                       "WM_CLASS" : ks.txt_fields[MENU_ELEMENT_TXT]);
        label_txt[1] = g_ascii_toupper (label_txt[1]);

        gtk_label_set_text (GTK_LABEL (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY]), label_txt);
        if (NOT_NULL_AND_NOT_EMPTY (ks.txt_fields[VALUE_TXT])) {
            gtk_entry_set_text (GTK_ENTRY (ks.entry_fields[MENU_ELEMENT_OR_VALUE_ENTRY]), ks.txt_fields[VALUE_TXT]);
        }
    }

    // If the last row is clicked, an overlapping of it by entry fields is avoided by this. No data is passed.
    gdk_threads_add_idle ((GSourceFunc) avoid_overlapping, NULL);
}
