/*
   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 Kicks.haw. 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 "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 whether a selected node has a descendant that is also 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 underlying model, not the 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 is found.
}

/* 

    If one or more rows are selected, all applicable or inapplicable actions for them 
    are activated or deactivated based on criteria such as their type (menu, item, etc.), 
    expansion and visibility status, position, and the number of selected rows.

    If no rows remain selected after an unselection, all actions that would otherwise 
    be available in the 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 value.

    // Default values.
    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) {
        // Default values.
        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) {
				// Translation note: Do not translate the word "startupnotify".
                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 none of the other cases apply.
            if (!(*statusbar_msg->str)) { // -> selected_row_has_selected_dsct = TRUE
                g_string_append (statusbar_msg, _("Row and descendant of it selected   "));
            }
            // Translation note: This refers to a drag-and-drop operation.
            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 for the selected paths, for possible later use 
            in drag-and-drop operations. Any 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 because row_selected() is called at startup, 
                  when ks.enter_values_box is still visible but the signal for ks.entry_fields[MENU_ID_ENTRY] 
                  has not yet been 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);
            // 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”).
            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 {
        // Default settings.
        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));

    // Translation note: Tooltip.
    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"), 
											// Translation note: "Visualize" in the sense of making something visible.
                                            N_("At least one invisible menu element has been visualized or removed"), 
											// Translation note: Do not translate the word "execute".
                                            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 is an error during creation of the initial undo item stack file,
            ks.pos_inside_undo_stack remains 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 is an error during creation of the initial undo item stack file,
            ks.pos_inside_undo_stack remains 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 whether there is no selection 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 value.
    const gchar *preset_choice = NULL; // Default value.

    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 at the beginning, since the iterator has not yet been set.

    // Item selected?
    if (STREQ (ks.txt_fields[TYPE_TXT], "item")) {
        gtk_widget_show (ks.bt_add[ACTION_OR_OPTION]);
        // Translation note: If the target language distinguishes between definite and indefinite forms, 
        // please use the indefinite form (“Action”), not the definite one (“The Action”).
        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);

        // Default value.
        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)) ? 
                                              // Translation note: If the target language distinguishes between definite and indefinite forms, 
                                              // please use the indefinite form (“Action/Option”), not the definite one (“The Action/The Option”).
                                              _("_Action/Option") : 
                                              // Translation note: If the target language distinguishes between definite and indefinite forms, 
                                              // please use the indefinite form (“Action”), not the definite one (“The Action”).
                                              _("_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) {
            // Translation note: This is for a context menu. If the target language distinguishes between definite and indefinite forms, 
            // please use the indefinite form (“Option”), not the definite one (“The Option”).
            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) {
				// Translation note: Do not translate the word "Execute".
                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[] = { // Translation note: Do not translate this.
				                                              N_("_Prompt"),
															  // Translation note: Do not translate this.
				                                              N_("_Command"), 
															  // Translation note: Do not translate this.
															  _("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)) {
				// Translation note: Do not translate the word "Startupnotify".
                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[] = { // Translation note: Do not translate this.
				                                                    N_("E_nabled"), 
																	// Translation note: Do not translate this.
				                                                    N_("_Name"), 
																	// Translation note: Do not translate this.
																	N_("_WM__CLASS"), 
																	// Translation note: Do not translate this.
																	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 with action "Exit" or "SessionLogout",
            or "command" option selected with action "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")) {
				// Translation note: Do not translate the word "Exit".
                msg_label_txt = _(" All Options for This Exit Action Have Been Set");
            }
            else if (STREQ (menu_element_txt_parent, "Restart")) {
				// Translation note: Do not translate the word "Restart".
                msg_label_txt = _(" All Options for This Restart Action Have Been Set");
            }
            else { // SessionLogout
			    // Translation note: Do not translate the word "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 the tree view.

#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 should currently be selected, so a path should be retrievable.
    // If, for some reason, it is no longer possible, 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)
{
    const gchar *right_to_left = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) ? "&#8207;" : "";

    gtk_widget_show (ks.entry_grid);

    // Default settings.
    // Translation note: If the target language distinguishes between definite and indefinite forms, 
    // please use the indefinite form (“Label”), not the definite one (“The Label”).
    gtk_label_set_text (GTK_LABEL (ks.entry_labels[MENU_ELEMENT_OR_VALUE_ENTRY]), _(" Label: "));

    g_autofree gchar *execute_label_text = g_strdup_printf  ("%s Execute: ", right_to_left);
    gtk_label_set_markup (GTK_LABEL (ks.entry_labels[EXECUTE_ENTRY]), execute_label_text);
    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 an empty string is necessary here, as the path cannot be "".
                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 = NULL;

        if (STREQ (ks.txt_fields[MENU_ELEMENT_TXT], "wmclass")) {
            label_txt = g_strdup_printf ("%s WM_CLASS: ", right_to_left);
        }
        else {
            g_autofree gchar *capitalized_field_name = g_strdup (ks.txt_fields[MENU_ELEMENT_TXT]);
            capitalized_field_name[0] = g_ascii_toupper (capitalized_field_name[0]);
            label_txt = g_strdup_printf ("%s %s: ", right_to_left, capitalized_field_name);
        }

        gtk_label_set_markup (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, this avoids it being overlapped by the entry fields.
    // No data is passed.
    gdk_threads_add_idle ((GSourceFunc) avoid_overlapping, NULL);
}
