/*
   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> // Sometimes this might have to be included explicitly.

#include "declarations_definitions_and_enumerations.h"
#include "find_and_replace.h"

typedef struct {
    GSList *menu_IDs_before_and_after;
    guint replacements_done_cnt;
    gboolean no_invalid_replacements;
    gboolean menu_item_or_sep_has_been_visualized;
} FRData;

static gchar *compute_hypothetical_replacement (const gchar *value);
static void find_and_replace_intialization (G_GNUC_UNUSED gpointer user_data);
static gboolean find_and_replace_iteration (              GtkTreeModel *model,  
                                            G_GNUC_UNUSED GtkTreePath  *path, 
                                                          GtkTreeIter  *iter, 
                                                          gpointer      fr_data_pnt);
static void fr_buttons_management (const gchar *column_check_button_clicked);
static void fr_entry_field_content_changed (G_GNUC_UNUSED gpointer user_data);
static gboolean set_all_menus_items_and_seps_to_visible (              GtkTreeModel *model, 
                                                         G_GNUC_UNUSED GtkTreePath  *path, 
                                                                       GtkTreeIter  *iter, 
                                                         G_GNUC_UNUSED gpointer      user_data);
static gboolean validity_check (              GtkTreeModel *model, 
                                G_GNUC_UNUSED GtkTreePath  *path, 
                                              GtkTreeIter  *iter, 
                                              gpointer      fr_data_pnt);

/* 

    Simply calls find_in_columns_management () with the necessary arguments.

*/

static void fr_buttons_management (const gchar *column_check_button_clicked)
{
    find_in_columns_management (TRUE, ks.fr_in_columns, ks.fr_in_all_columns, 
                                ks.handler_id_fr_in_columns, column_check_button_clicked);
}

/*

    Activates the "Replace" button if something is entered in one of the entry fields and 
    deactivates it if both entry fields no longer contain any text.

*/

static void fr_entry_field_content_changed (G_GNUC_UNUSED gpointer user_data)
{
    gtk_dialog_set_response_sensitive (GTK_DIALOG (ks.fr_dialog), 1, 
                                       *(gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[REPLACE_ENTRY]))) || 
                                       *(gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[WITH_ENTRY]))));
}

/*

    If a find and replace is possible for a certain value, the new value is returned. If it is not possible NULL is returned. 

*/

static gchar *compute_hypothetical_replacement (const gchar *value)
{
    const gchar *replace_entry_str = gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[REPLACE_ENTRY]));
    const gchar *with_entry_str = gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[WITH_ENTRY]));

    g_autofree gchar *replace_entry_str_escaped = (gtk_toggle_button_get_active 
                                                      (GTK_TOGGLE_BUTTON (ks.fr_special_options[REGULAR_EXPRESSION]))) ? 
                                                  NULL : g_regex_escape_string (replace_entry_str, -1);
    const gboolean whole_word = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[WHOLE_WORD]));
    g_autofree gchar *final_replace_str = g_strconcat ((whole_word) ? "\\b(" : "", 
                                                       (replace_entry_str_escaped) ? replace_entry_str_escaped : replace_entry_str, 
                                                       (whole_word) ? ")\\b" : "", 
                                                       NULL);
    gchar *new_value = NULL; // Default, no match found.

    if (g_regex_match_simple (final_replace_str, value, 
                              (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[MATCH_CASE]))) ? 
                              0 : G_REGEX_CASELESS, 
                              G_REGEX_MATCH_NOTEMPTY)) {
        g_autoptr(GRegex) regex = g_regex_new (final_replace_str, 
                                               (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[MATCH_CASE]))) ? 
                                               0 : G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY, NULL);

        new_value = g_regex_replace (regex, value, -1, 0, with_entry_str, 0, NULL);
    }

    return new_value;
}

/* 

    Makes sure that there will be no...
    1. ...values for the "enabled" option other than "yes" and "no"...
    2. ...double menu IDs...
    3. ...empty (pipe) menu labels...
    ...after a find and replace.

*/

static gboolean validity_check (              GtkTreeModel *model,  
                                G_GNUC_UNUSED GtkTreePath  *path, 
                                              GtkTreeIter  *iter,
                                              gpointer      fr_data_pnt)
{
    FRData *fr_data = (FRData *) fr_data_pnt;

    const gchar *with_entry_str = gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[WITH_ENTRY]));
          gboolean *no_invalid_replacements = &(fr_data->no_invalid_replacements);

    gchar *not_executed_str = _("Find and replace action not executed.");

    g_autofree gchar *menu_element_txt, 
                     *type_txt;

    gtk_tree_model_get (model, iter, 
                        TS_MENU_ELEMENT, &menu_element_txt, 
                        TS_TYPE, &type_txt,
                        -1);

    // Make sure that there will be no values for the "enabled" option other than "yes" and "no" after a find and replace.

    if (STREQ (type_txt, "option") && STREQ (menu_element_txt, "enabled") &&
        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[COL_VALUE]))) {
        g_autofree gchar *value, *new_value = NULL;

        gtk_tree_model_get (model, iter, TS_VALUE, &value, -1);
        if (G_UNLIKELY ((new_value = compute_hypothetical_replacement (value)) && 
                        !streq_any (new_value, "yes", "no", NULL))) {
            // Translation note: Do not translate "enabled", "yes", "no"
            g_autofree gchar *err_msg = g_strdup_printf (_("An <b>\"enabled\"</b> option can't have a value other than "
                                                           "<b>\"yes\"</b> or <b>\"no\"</b> and thus a replacement of "
                                                           "\"%s\" with \"%s\" won't be done.\n\n"
                                                           "<b><span foreground='%s'>%s</span></b>"), 
                                                         value, with_entry_str, ks.red_hue, not_executed_str);
            show_errmsg (err_msg);

            *no_invalid_replacements = FALSE;
        }

        if (!(*no_invalid_replacements)) {
            return STOP_ITERATING;
        }
    }

    // Make sure that there will be no double menu IDs after a find and replace.

    if (streq_any (type_txt, "menu", "pipe menu", NULL) && 
        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[COL_MENU_ID]))) {
        g_autofree gchar *menu_id_txt, *new_menu_id_txt;

        gtk_tree_model_get (model, iter, TS_MENU_ID, &menu_id_txt, -1);

        if ((new_menu_id_txt = compute_hypothetical_replacement (menu_id_txt))) {
            GSList *menu_ids_loop;

            FOREACH_IN_LIST (ks.menu_ids, menu_ids_loop) {
                if (G_UNLIKELY (STREQ (menu_ids_loop->data, new_menu_id_txt) && !STREQ (new_menu_id_txt, menu_id_txt))) {
                    g_autofree gchar *err_msg = g_strdup_printf (_("The menu ID <b>\"%1$s\"</b> won't be replaced with <b>\"%2$s\"</b>, "
                                                                   "because this would create a double menu ID.\n\n"
                                                                   "<b><span foreground='%3$s'>%4$s</span></b>"), 
                                                                 menu_id_txt, new_menu_id_txt, ks.red_hue, not_executed_str);

                    show_errmsg (err_msg);

                    *no_invalid_replacements = FALSE;
                }
            }

            /* 
               Double menu IDs can also occur if for example for both menu IDs "menu1" and "menu2" the trailing numbers are removed.
               All hypothetical new menu IDs are stored inside a list, and then sorted, because with that it is enough to compare a 
               menu ID with the following one inside the list. If they are identical, there would be a double menu ID after the 
               execution of the "find and replace" action.
            */
            if (*no_invalid_replacements) {
                gchar *menu_IDs_before_and_after_txt = g_strdup_printf ("%s%c%s", new_menu_id_txt, 
                                                                        31, menu_id_txt); // 31 = unit separator

                fr_data->menu_IDs_before_and_after = g_slist_prepend (fr_data->menu_IDs_before_and_after, menu_IDs_before_and_after_txt);
                fr_data->menu_IDs_before_and_after = g_slist_sort (fr_data->menu_IDs_before_and_after, (GCompareFunc) strcmp);

                if (g_slist_length (fr_data->menu_IDs_before_and_after) > 1) {
                    FOREACH_IN_LIST (fr_data->menu_IDs_before_and_after, menu_ids_loop) {
                        if (menu_ids_loop->next) {
                            // x1F (dec. 31) = unit separator
                            g_auto(GStrv) menu_IDs_before_and_after_pair1 = g_strsplit (menu_ids_loop->data, "\x1F", -1);
                            // x1F (dec. 31) = unit separator
                            g_auto(GStrv) menu_IDs_before_and_after_pair2 = g_strsplit (menu_ids_loop->next->data, "\x1F", -1);

                            if (G_UNLIKELY (STREQ (menu_IDs_before_and_after_pair1[0], menu_IDs_before_and_after_pair2[0]))) {
                                g_autofree gchar *err_msg = g_strdup_printf (_("A find and replace with these values "
                                                                               "would replace both menu IDs "
                                                                               "<b>\"%1$s\"</b> and <b>\"%2$s\"</b> with <b>\"%3$s\"</b>, "
                                                                               "thus creating a double menu ID.\n\n"
                                                                               "<b><span foreground='%4$s'>%5$s</span></b>"), 
                                                                             menu_IDs_before_and_after_pair1[1], 
                                                                             menu_IDs_before_and_after_pair2[1], 
                                                                             menu_IDs_before_and_after_pair1[0], 
                                                                             ks.red_hue, 
                                                                             not_executed_str);
                                show_errmsg (err_msg);

                                *no_invalid_replacements = FALSE;
                            }
                        }
                    }
                }
            }
        }

        if (!(*no_invalid_replacements)) {
            return STOP_ITERATING;
        }
    }

    if (!(*with_entry_str)) {

        // Make sure that there will be no empty (pipe) menu and item labels after a find and replace.

        if (streq_any (type_txt, "menu", "pipe menu", "item", NULL) && 
            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[COL_MENU_ELEMENT]))) {
            g_autofree gchar *new_menu_element_txt = compute_hypothetical_replacement (menu_element_txt);

            if (G_UNLIKELY (STREQ (new_menu_element_txt, ""))) {
                g_autofree gchar *substr;

                if (streq_any (type_txt, "menu", "pipe menu", NULL)) {
                    g_autofree gchar *menu_id_txt;

                    gtk_tree_model_get (model, iter, TS_MENU_ID, &menu_id_txt, -1); 

                    if (STREQ (type_txt, "menu")) {                    
                        substr = g_strdup_printf (_("The menu <b>\"%1$s\"</b> with the menu ID <b>\"%2$s\"</b> won't "
                                                   "receive an empty label, because this would render the menu invisible."), 
                                                    menu_element_txt, menu_id_txt);
                    }
                    else { // pipe menu
                        substr = g_strdup_printf (_("The pipe menu <b>\"%1$s\"</b> with the menu ID <b>\"%2$s\"</b> won't "
                                                    "receive an empty label, because this would render the pipe menu invisible."), 
                                                    menu_element_txt, menu_id_txt);
                    }
                }
                else { // item
                    substr = g_strdup_printf (_("The item with the label <b>\"%s\"</b> won't "
                                                "receive an empty label, because this would render the item invisible."), 
                                                menu_element_txt);
                }

                g_autofree gchar *err_msg = g_strdup_printf ("%s\n\n<b><span foreground='%s'>%s</span></b>", 
                                                             substr, ks.red_hue, not_executed_str);

                show_errmsg (err_msg);

                *no_invalid_replacements = FALSE;
            }
        }

        // Make sure that executes of pipe menus are not emptied.

        if (STREQ (type_txt, "pipe menu") && 
            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[COL_EXECUTE]))) {
            g_autofree gchar *execute_txt;

            gtk_tree_model_get (model, iter, TS_EXECUTE, &execute_txt, -1);

            g_autofree gchar *new_execute_txt = compute_hypothetical_replacement (execute_txt);

            if (G_UNLIKELY (STREQ (new_execute_txt, ""))) {
                g_autofree gchar *menu_id_txt;

                gtk_tree_model_get (model, iter, TS_MENU_ID, &menu_id_txt, -1);

                // Translation note: Do not translate "execute"
                g_autofree gchar *err_msg = g_strdup_printf (_("The execute instruction of the pipe menu <b>\"%1$s\"</b> "
                                                               "with the menu ID <b>\"%2$s\"</b> won't be deleted, "
                                                               "because this would render this pipe menu useless.\n\n"
                                                               "<b><span foreground='%3$s'>%4$s</span></b>"), 
                                                             menu_element_txt, menu_id_txt, ks.red_hue, not_executed_str);

                show_errmsg (err_msg);

                *no_invalid_replacements = FALSE;
            }
        }
    }

    return (!(*no_invalid_replacements)); // return value == TRUE -> Stop iterating through the tree store.
}

/*

    Execute a find and replace. "Type" and "Element visibilty" are excluded, since their values have to be unmodifiable.
    This, the uniqueness of each menu ID and the only two possible values for the "enabled" option ("yes" and "no") have already 
    been taken care of.

*/

static gboolean find_and_replace_iteration (              GtkTreeModel *model,  
                                            G_GNUC_UNUSED GtkTreePath  *path, 
                                                          GtkTreeIter  *iter,
                                                          gpointer      fr_data_pnt)
{
    FRData *fr_data = (FRData *) fr_data_pnt;

    const gchar *replace_entry_str = gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[REPLACE_ENTRY]));
    const gchar *with_entry_str = gtk_entry_get_text (GTK_ENTRY (ks.fr_entry_fields[WITH_ENTRY]));
    const gboolean whole_word = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[WHOLE_WORD]));
          guint *replacements_done_cnt = &(fr_data->replacements_done_cnt);
          gboolean *menu_item_or_sep_has_been_visualized = &(fr_data->menu_item_or_sep_has_been_visualized);

    for (guint8 columns_cnt = 0; columns_cnt < NUMBER_OF_COLUMNS - 1; ++columns_cnt) {
        g_autofree gchar *type_txt;

        gtk_tree_model_get (model, iter, TS_TYPE, &type_txt, -1);

        if (columns_cnt == COL_TYPE || !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[columns_cnt])) || 
            (columns_cnt == COL_MENU_ELEMENT && streq_any (type_txt, "action", "option block", "option", NULL))) {
            continue;
        }

        g_autofree gchar *current_column_txt;

        gtk_tree_model_get (model, iter, columns_cnt + TREEVIEW_COLUMN_OFFSET, &current_column_txt, -1);

        if (!(*replace_entry_str)) {
            // The program allows to find empty fields and fill them with a value, provided they can also be entered manually.
            if (!NOT_NULL_AND_NOT_EMPTY (current_column_txt) && 
                // The invalid types "action", "option block", and "option" have already been taken care of above.
                (columns_cnt == COL_MENU_ELEMENT || 
                 (columns_cnt == COL_VALUE && STREQ (type_txt, "option")) || 
                 (columns_cnt == COL_MENU_ID && STREQ (type_txt, "menu")) || 
                 (columns_cnt == COL_EXECUTE && STREQ (type_txt, "menu") && !gtk_tree_model_iter_has_child (model, iter)))) {
                gtk_tree_store_set (ks.treestore, iter, columns_cnt + TREEVIEW_COLUMN_OFFSET, with_entry_str, -1);

                /* It's simpler to overwrite the visibility status regardless of the previous status than to query the status first.
                   Only the status of the types "menu", "pipe menu", "item", and "separator" is overwritten, since the other types 
                   were already skipped above. */
                if (columns_cnt == COL_MENU_ELEMENT) {
                    gtk_tree_store_set (ks.treestore, iter, TS_ELEMENT_VISIBILITY, "visible", -1);
                    *menu_item_or_sep_has_been_visualized = TRUE;
                }

                if (columns_cnt == COL_MENU_ID) {
                    remove_menu_id ("");
                    ks.menu_ids = g_slist_prepend (ks.menu_ids, g_strdup (with_entry_str));
                }

                // By adding a text to the "Execute" field, a menu without children has been turned into a pipe menu.
                if (columns_cnt == COL_EXECUTE && STREQ (type_txt, "menu") && !gtk_tree_model_iter_has_child (model, iter)) {
                    gtk_tree_store_set (ks.treestore, iter, TS_TYPE, "pipe menu", -1);
                }

                ++(*replacements_done_cnt);
            }

            continue;
        }

        if (!current_column_txt) {
            continue;
        }

        g_autofree gchar *replace_entry_str_escaped = (gtk_toggle_button_get_active 
                                                          (GTK_TOGGLE_BUTTON (ks.fr_special_options[REGULAR_EXPRESSION]))) ? 
                                                       NULL : g_regex_escape_string (replace_entry_str, -1);
        g_autofree gchar *final_replace_str = g_strconcat ((whole_word) ? "\\b(" : "", 
                                                           (replace_entry_str_escaped) ? replace_entry_str_escaped : replace_entry_str, 
                                                           (whole_word) ? ")\\b" : "", 
                                                           NULL);

        g_autoptr(GRegex) regex = g_regex_new (final_replace_str, 
                                               (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[MATCH_CASE]))) ? 
                                               0 : G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY, NULL);
        g_autoptr(GMatchInfo) match_info;

        if (!g_regex_match (regex, current_column_txt, 0, &match_info)) {
            continue;
        }

        g_autofree gchar *new_column_txt = g_regex_replace (regex, current_column_txt, -1, 0, with_entry_str, 0, NULL);

        // The program won't store empty separator labels; a separator should be either with or without text.
        gtk_tree_store_set (ks.treestore, iter, columns_cnt + TREEVIEW_COLUMN_OFFSET, 
                            !(!(*new_column_txt) && STREQ (type_txt, "separator")) ? new_column_txt : NULL, -1);

        if (columns_cnt == COL_MENU_ID) {
            remove_menu_id (current_column_txt);
            ks.menu_ids = g_slist_prepend (ks.menu_ids, g_strdup (new_column_txt));
        }

        while (g_match_info_matches (match_info)) {
            ++(*replacements_done_cnt);
            g_match_info_next (match_info, NULL);
        }
    }

    return CONTINUE_ITERATING;
}

/*

    If by find and replace one or several labelless menus, items, and/or separators have received a label, 
    this means all menus, items, and separators will have a label now.
    Instead of checking if there are any menus/items/separators left for which the visibility would have 
    to be set to "visible", for simplicity visibility is set to "visible" for all menus, items, and separators.

*/

static gboolean set_all_menus_items_and_seps_to_visible (              GtkTreeModel *model, 
                                                         G_GNUC_UNUSED GtkTreePath  *path, 
                                                                       GtkTreeIter  *iter, 
                                                         G_GNUC_UNUSED gpointer      user_data)
{
    g_autofree gchar *type_txt;

    gtk_tree_model_get (model, iter, TS_TYPE, &type_txt, -1);

    if (streq_any (type_txt, "menu", "pipe menu", "item", "separator", NULL)) {
        gtk_tree_store_set (ks.treestore, iter, TS_ELEMENT_VISIBILITY, "visible", -1);
    }

    return CONTINUE_ITERATING;
}

/*

    Does a number of checks, then starts the actual find and replace action.

*/

static void find_and_replace_intialization (G_GNUC_UNUSED gpointer user_data)
{
    FRData fr_data = {
        .menu_IDs_before_and_after = NULL,
        .replacements_done_cnt = 0, // Default
        .no_invalid_replacements = TRUE, // Default
        .menu_item_or_sep_has_been_visualized = FALSE // Default
    };

    guint8 columns_cnt;

    if (!(check_if_regex_is_valid (ks.fr_special_options[REGULAR_EXPRESSION], ks.fr_entry_fields[REPLACE_ENTRY]))) {
        return;
    }

    gboolean no_find_in_columns_buttons_clicked = TRUE; // Default

    for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; ++columns_cnt) {
        if (columns_cnt == COL_TYPE) {
            continue;
        }
        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.fr_in_columns[columns_cnt]))) {
            no_find_in_columns_buttons_clicked = FALSE;
            break;
        }
    }

    if (no_find_in_columns_buttons_clicked) { // No column chosen = not a valid find & replace
        for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; ++columns_cnt) {
            if (columns_cnt == COL_TYPE) {
                continue;
            }
            visually_indicate_request_for_user_intervention (ks.fr_in_columns[columns_cnt], ks.fr_in_columns_css_providers[columns_cnt]);
        }
        visually_indicate_request_for_user_intervention (ks.fr_in_all_columns, ks.fr_in_all_columns_css_provider);

        return;
    }

    gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) validity_check, &fr_data);

    if (fr_data.no_invalid_replacements) {
        g_autofree gchar *replacements_done_label_txt;
        const gchar *small_numbers_spelled_out[] = { N_("once"), N_("twice"), N_("three times"), N_("four times"), N_("five times"), 
                                                     N_("six times"), N_("seven times"), N_("eight times"), N_("nine times") };

        fr_data.replacements_done_cnt = 0; // Default

        gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) find_and_replace_iteration, &fr_data);

        replacements_done_label_txt = (fr_data.replacements_done_cnt) ? 
                                      ((fr_data.replacements_done_cnt < 10) ? 
                                       // Translation note: The translation should work for %s getting replaced with phrases ranging from "once" to "nine times"
                                       g_strdup_printf (_("Search key replaced %s."), _(small_numbers_spelled_out[(fr_data.replacements_done_cnt) - 1])) : 
                                       // Translation note: The translation should work for %i getting replaced with positive numbers
                                       g_strdup_printf (ngettext ("Search key replaced %i times.", "Search key replaced %i times.", fr_data.replacements_done_cnt), fr_data.replacements_done_cnt)) : 
                                       g_strdup (_("Search key not found."));
        gtk_label_set_text (GTK_LABEL (ks.fr_replacements_done_label), replacements_done_label_txt);

        if (fr_data.replacements_done_cnt) {
            if (fr_data.menu_item_or_sep_has_been_visualized) {
                gtk_tree_model_foreach (ks.ts_model, (GtkTreeModelForeachFunc) set_all_menus_items_and_seps_to_visible, NULL);
            }

            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. */
        }
    }

    // Cleanup. Setting to NULL is unnecessary, since this is part of a local struct and always initialized to NULL in the beginning.
    g_slist_free_full (fr_data.menu_IDs_before_and_after, (GDestroyNotify) g_free);
}

/*

    Shows the find and replace dialog window.

*/

void show_find_and_replace_dialog (void)
{
    if (ks.fr_dialog) {
        gtk_window_present (GTK_WINDOW (ks.fr_dialog));

        return;
    }

    ks.fr_dialog = gtk_dialog_new_with_buttons (NULL, GTK_WINDOW (ks.window), GTK_DIALOG_DESTROY_WITH_PARENT, 
                                                _("_Replace"), 1, _("_Close"), 2, NULL);
    set_header_bar_for_dialog (ks.fr_dialog, _("Find and Replace"));
    gtk_widget_add_events (ks.fr_dialog, GDK_KEY_PRESS_MASK);
    g_signal_connect (G_OBJECT (ks.fr_dialog), "key-press-event", G_CALLBACK (close_window), "<ctrl>H");
    g_signal_connect (G_OBJECT (ks.fr_dialog), "destroy", G_CALLBACK (gtk_widget_destroyed), &ks.fr_dialog);

    ks.fr_entry_fields[REPLACE_ENTRY] = gtk_entry_new ();
    ks.fr_entry_fields[WITH_ENTRY] = gtk_entry_new ();
    ks.fr_replacements_done_label = gtk_label_new (NULL);
    gtk_label_set_xalign (GTK_LABEL (ks.fr_replacements_done_label), 0.0);

    GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (ks.fr_dialog));
    GtkWidget *grid = gtk_grid_new ();
    GtkWidget *columns_grid = gtk_grid_new ();
    GtkWidget *special_options_grid = gtk_grid_new ();
    enum { FR_REPLACE_LABEL, FR_IN_LABEL, FR_WITH_LABEL, NUMBER_OF_FR_LABEL_FIELDS };
    const gchar *fr_label_txts[] = { // Translation note: This is for a "Search and Replace" window, in the context of "Replace something in x with y"
                                     _("<b>Replace: </b>"), 
                                     // Translation note: This is for a "Search and Replace" window, in the context of "Replace something in x with y"
                                     // A translation of "In" as "Where" would also work
                                     _("<b>In:</b>"), 
                                     // Translation note: This is for a "Search and Replace" window, in the context of "Replace something in x with y"
                                     // A translation of "With" as "With what" would also work
                                     _("<b>With:</b>") };
    GtkWidget *fr_labels[NUMBER_OF_FR_LABEL_FIELDS] = { gtk_label_new (NULL), gtk_label_new (NULL), gtk_label_new (NULL) };
    const gchar *special_options_txts[] = { // This is for a search functionality. A translation of "case sensitive" or "distinguish between uppercase and lowercase letters" would also work.
                                            _("Match Case"), 
                                            _("Regular Expression (PCRE)"), 
                                            _("Whole Word") };

    gint8 columns_cnt;

    for (guint8 labels_cnt = 0; labels_cnt < NUMBER_OF_FR_LABEL_FIELDS; ++labels_cnt) {
        gtk_label_set_xalign (GTK_LABEL (fr_labels[labels_cnt]), 0.0);
        gtk_label_set_markup (GTK_LABEL (fr_labels[labels_cnt]), fr_label_txts[labels_cnt]);
    }

    gtk_grid_attach (GTK_GRID (grid), fr_labels[FR_REPLACE_LABEL], 0, 0, 1, 1);
    gtk_grid_attach (GTK_GRID (grid), ks.fr_entry_fields[REPLACE_ENTRY], 1, 0, 1, 1);
    gtk_widget_set_hexpand (ks.fr_entry_fields[REPLACE_ENTRY], TRUE);

    gtk_grid_attach (GTK_GRID (grid), fr_labels[FR_IN_LABEL], 0, 1, 1, 1);
    gtk_grid_attach (GTK_GRID (grid), columns_grid, 1, 1, 1, 1);

    for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; ++columns_cnt) {
        if (columns_cnt == COL_TYPE) { // The text for the type of a menu element has to be unmodifiable.
            continue;
        }

        ks.fr_in_columns[columns_cnt] = gtk_check_button_new_with_label (_(ks.column_header_txts[columns_cnt]));
        gtk_container_add (GTK_CONTAINER (columns_grid), ks.fr_in_columns[columns_cnt]);

        ks.fr_in_columns_css_providers[columns_cnt] = gtk_css_provider_new ();
        gtk_style_context_add_provider (gtk_widget_get_style_context (ks.fr_in_columns[columns_cnt]), 
                                        GTK_STYLE_PROVIDER (ks.fr_in_columns_css_providers[columns_cnt]), 
                                        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }

    ks.fr_in_all_columns = gtk_check_button_new_with_label (_("All Modifiable Columns"));
    gtk_container_add (GTK_CONTAINER (columns_grid), ks.fr_in_all_columns);
    ks.fr_in_all_columns_css_provider = gtk_css_provider_new ();
    gtk_style_context_add_provider (gtk_widget_get_style_context (ks.fr_in_all_columns), 
                                    GTK_STYLE_PROVIDER (ks.fr_in_all_columns_css_provider), 
                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    gtk_grid_attach (GTK_GRID (grid), special_options_grid, 1, 2, 1, 1);
    for (guint8 buttons_cnt = 0; buttons_cnt < NUMBER_OF_SPECIAL_OPTIONS; ++buttons_cnt) {
        ks.fr_special_options[buttons_cnt] = gtk_check_button_new_with_label (special_options_txts[buttons_cnt]);
        gtk_container_add (GTK_CONTAINER (special_options_grid), ks.fr_special_options[buttons_cnt]);
    }

    gtk_grid_attach (GTK_GRID (grid), ks.fr_replacements_done_label, 1, 3, 1, 1);
    gtk_widget_set_margin_top (ks.fr_replacements_done_label, 3);
    gtk_widget_set_margin_bottom (ks.fr_replacements_done_label, 3);

    gtk_grid_attach (GTK_GRID (grid), fr_labels[FR_WITH_LABEL], 0, 4, 1, 1);
    gtk_grid_attach (GTK_GRID (grid), ks.fr_entry_fields[WITH_ENTRY], 1, 4, 1, 1);
    gtk_widget_set_hexpand (ks.fr_entry_fields[WITH_ENTRY], TRUE);

    gtk_container_add (GTK_CONTAINER (content_area), grid);
    gtk_container_set_border_width (GTK_CONTAINER (content_area), 5);

    g_signal_connect_swapped (ks.fr_entry_fields[REPLACE_ENTRY], "changed", G_CALLBACK (fr_entry_field_content_changed), NULL);
    g_signal_connect_swapped (ks.fr_entry_fields[WITH_ENTRY], "changed", G_CALLBACK (fr_entry_field_content_changed), NULL);
    for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; ++columns_cnt) {
        if (columns_cnt != COL_TYPE) {
            ks.handler_id_fr_in_columns[columns_cnt] = g_signal_connect_swapped (ks.fr_in_columns[columns_cnt], "clicked", 
                                                                                 G_CALLBACK (fr_buttons_management), "specif");
        }
    }
    g_signal_connect_swapped (ks.fr_in_all_columns, "clicked", G_CALLBACK (fr_buttons_management), "all");
    g_signal_connect_swapped (gtk_dialog_get_widget_for_response (GTK_DIALOG (ks.fr_dialog), 1), "clicked", 
                              G_CALLBACK (find_and_replace_intialization), NULL);
    g_signal_connect_swapped (gtk_dialog_get_widget_for_response (GTK_DIALOG (ks.fr_dialog), 2), "clicked", 
                              G_CALLBACK (gtk_widget_destroy), ks.fr_dialog);

    // Default settings
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.fr_in_all_columns), TRUE);
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.fr_special_options[REGULAR_EXPRESSION]), TRUE);
    gtk_dialog_set_response_sensitive (GTK_DIALOG (ks.fr_dialog), 1, FALSE);

    gtk_widget_show_all (ks.fr_dialog);

    gtk_widget_set_size_request (ks.fr_dialog, 570, -1);
    gtk_window_set_position (GTK_WINDOW (ks.fr_dialog), GTK_WIN_POS_CENTER);
}
