/*************************************************************************/
/*  rename_dialog.cpp                                                    */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "rename_dialog.h"

#include "core/callable_method_pointer.h"
#include "core/method_bind.h"
#include "core/string_formatter.h"
#include "core/print_string.h"
#include "editor_node.h"
#include "editor_settings.h"
#include "editor_themes.h"
#include "editor_scale.h"
#include "modules/regex/regex.h"
#include "plugins/script_editor_plugin.h"
#include "scene/gui/control.h"
#include "scene/gui/label.h"
#include "scene/gui/tab_container.h"
#include "scene/main/scene_tree.h"

IMPL_GDCLASS(RenameDialog)

RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_undo_redo) {

    scene_tree_editor = p_scene_tree_editor;
    undo_redo = p_undo_redo;
    preview_node = nullptr;

    set_title(TTR("Batch Rename"));

    VBoxContainer *vbc = memnew(VBoxContainer);
    add_child(vbc);

    // -- Search/Replace Area

    GridContainer *grd_main = memnew(GridContainer);
    grd_main->set_columns(2);
    grd_main->set_v_size_flags(SIZE_EXPAND_FILL);
    vbc->add_child(grd_main);

    // ---- 1st & 2nd row

    Label *lbl_search = memnew(Label);
    lbl_search->set_text(TTR("Search"));

    lne_search = memnew(LineEdit);
    lne_search->set_placeholder(TTR("Search"));
    lne_search->set_name("lne_search");
    lne_search->set_h_size_flags(SIZE_EXPAND_FILL);

    Label *lbl_replace = memnew(Label);
    lbl_replace->set_text(TTR("Replace"));

    lne_replace = memnew(LineEdit);
    lne_replace->set_placeholder(TTR("Replace"));
    lne_replace->set_name("lne_replace");
    lne_replace->set_h_size_flags(SIZE_EXPAND_FILL);

    grd_main->add_child(lbl_search);
    grd_main->add_child(lbl_replace);
    grd_main->add_child(lne_search);
    grd_main->add_child(lne_replace);

    // ---- 3rd & 4th row

    Label *lbl_prefix = memnew(Label);
    lbl_prefix->set_text(TTR("Prefix"));

    lne_prefix = memnew(LineEdit);
    lne_prefix->set_placeholder(TTR("Prefix"));
    lne_prefix->set_name("lne_prefix");
    lne_prefix->set_h_size_flags(SIZE_EXPAND_FILL);

    Label *lbl_suffix = memnew(Label);
    lbl_suffix->set_text(TTR("Suffix"));

    lne_suffix = memnew(LineEdit);
    lne_suffix->set_placeholder(TTR("Suffix"));
    lne_suffix->set_name("lne_suffix");
    lne_suffix->set_h_size_flags(SIZE_EXPAND_FILL);

    grd_main->add_child(lbl_prefix);
    grd_main->add_child(lbl_suffix);
    grd_main->add_child(lne_prefix);
    grd_main->add_child(lne_suffix);

    // -- Feature Tabs

    const int feature_min_height = 160 * EDSCALE;

    cbut_regex = memnew(CheckButton);
    cbut_regex->set_text(TTR("Use Regular Expressions"));
    vbc->add_child(cbut_regex);

    CheckButton *cbut_collapse_features = memnew(CheckButton);
    cbut_collapse_features->set_text(TTR("Advanced Options"));
    vbc->add_child(cbut_collapse_features);

    tabc_features = memnew(TabContainer);
    tabc_features->set_tab_align(TabContainer::ALIGN_LEFT);
    vbc->add_child(tabc_features);

    // ---- Tab Substitute

    VBoxContainer *vbc_substitute = memnew(VBoxContainer);
    vbc_substitute->set_h_size_flags(SIZE_EXPAND_FILL);
    vbc_substitute->set_custom_minimum_size(Size2(0, feature_min_height));

    vbc_substitute->set_name(TTR("Substitute"));
    tabc_features->add_child(vbc_substitute);

    cbut_substitute = memnew(CheckBox);
    cbut_substitute->set_text(TTR("Substitute"));
    vbc_substitute->add_child(cbut_substitute);

    GridContainer *grd_substitute = memnew(GridContainer);
    grd_substitute->set_columns(3);
    vbc_substitute->add_child(grd_substitute);

    // Name

    but_insert_name = memnew(Button);
    but_insert_name->set_text("NAME");
    but_insert_name->set_tooltip("${NAME}\n" + TTR("Node name"));
    but_insert_name->set_focus_mode(FOCUS_NONE);
    but_insert_name->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${NAME}"));
    but_insert_name->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_name);

    // Parent

    but_insert_parent = memnew(Button);
    but_insert_parent->set_text("PARENT");
    but_insert_parent->set_tooltip("${PARENT}\n" + TTR("Node's parent name, if available"));
    but_insert_parent->set_focus_mode(FOCUS_NONE);
    but_insert_parent->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${PARENT}"));
    but_insert_parent->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_parent);

    // Type

    but_insert_type = memnew(Button);
    but_insert_type->set_text("TYPE");
    but_insert_type->set_tooltip("${TYPE}\n" + TTR("Node type"));
    but_insert_type->set_focus_mode(FOCUS_NONE);
    but_insert_type->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${TYPE}"));
    but_insert_type->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_type);

    // Scene

    but_insert_scene = memnew(Button);
    but_insert_scene->set_text("SCENE");
    but_insert_scene->set_tooltip("${SCENE}\n" + TTR("Current scene name"));
    but_insert_scene->set_focus_mode(FOCUS_NONE);
    but_insert_scene->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${SCENE}"));
    but_insert_scene->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_scene);

    // Root

    but_insert_root = memnew(Button);
    but_insert_root->set_text("ROOT");
    but_insert_root->set_tooltip("${ROOT}\n" + TTR("Root node name"));
    but_insert_root->set_focus_mode(FOCUS_NONE);
    but_insert_root->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${ROOT}"));
    but_insert_root->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_root);

    // Count

    but_insert_count = memnew(Button);
    but_insert_count->set_text("COUNTER");
    but_insert_count->set_tooltip("${COUNTER}\n" + TTR("Sequential integer counter.\nCompare counter options."));
    but_insert_count->set_focus_mode(FOCUS_NONE);
    but_insert_count->connect("pressed",callable_mp(this, &ClassName::_insert_text), make_binds("${COUNTER}"));
    but_insert_count->set_h_size_flags(SIZE_EXPAND_FILL);
    grd_substitute->add_child(but_insert_count);

    chk_per_level_counter = memnew(CheckBox);
    chk_per_level_counter->set_text(TTR("Per-level Counter"));
    chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes"));
    vbc_substitute->add_child(chk_per_level_counter);

    HBoxContainer *hbc_count_options = memnew(HBoxContainer);
    vbc_substitute->add_child(hbc_count_options);

    Label *lbl_count_start = memnew(Label);
    lbl_count_start->set_text(TTR("Start"));
    lbl_count_start->set_tooltip(TTR("Initial value for the counter"));
    hbc_count_options->add_child(lbl_count_start);

    spn_count_start = memnew(SpinBox);
    spn_count_start->set_tooltip(TTR("Initial value for the counter"));
    spn_count_start->set_step(1);
    spn_count_start->set_min(0);
    hbc_count_options->add_child(spn_count_start);

    Label *lbl_count_step = memnew(Label);
    lbl_count_step->set_text(TTR("Step"));
    lbl_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node"));
    hbc_count_options->add_child(lbl_count_step);

    spn_count_step = memnew(SpinBox);
    spn_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node"));
    spn_count_step->set_step(1);
    hbc_count_options->add_child(spn_count_step);

    Label *lbl_count_padding = memnew(Label);
    lbl_count_padding->set_text(TTR("Padding"));
    lbl_count_padding->set_tooltip(TTR("Minimum number of digits for the counter.\nMissing digits are padded with leading zeros."));
    hbc_count_options->add_child(lbl_count_padding);

    spn_count_padding = memnew(SpinBox);
    spn_count_padding->set_tooltip(TTR("Minimum number of digits for the counter.\nMissing digits are padded with leading zeros."));
    spn_count_padding->set_step(1);
    hbc_count_options->add_child(spn_count_padding);

    // ---- Tab Process

    VBoxContainer *vbc_process = memnew(VBoxContainer);
    vbc_process->set_h_size_flags(SIZE_EXPAND_FILL);
    vbc_process->set_name(TTR("Post-Process"));
    vbc_process->set_custom_minimum_size(Size2(0, feature_min_height));
    tabc_features->add_child(vbc_process);

    cbut_process = memnew(CheckBox);
    cbut_process->set_text(TTR("Post-Process"));
    vbc_process->add_child(cbut_process);

    // ------ Style

    HBoxContainer *hbc_style = memnew(HBoxContainer);
    vbc_process->add_child(hbc_style);

    Label *lbl_style = memnew(Label);
    lbl_style->set_text(TTR("Style"));
    hbc_style->add_child(lbl_style);

    opt_style = memnew(OptionButton);
    opt_style->add_item(TTR("Keep"));
    opt_style->add_item(TTR("PascalCase to snake_case"));
    opt_style->add_item(TTR("snake_case to PascalCase"));
    hbc_style->add_child(opt_style);

    // ------ Case

    HBoxContainer *hbc_case = memnew(HBoxContainer);
    vbc_process->add_child(hbc_case);

    Label *lbl_case = memnew(Label);
    lbl_case->set_text(TTR("Case"));
    hbc_case->add_child(lbl_case);

    opt_case = memnew(OptionButton);
    opt_case->add_item(TTR("Keep"));
    opt_case->add_item(TTR("To Lowercase"));
    opt_case->add_item(TTR("To Uppercase"));
    hbc_case->add_child(opt_case);

    // -- Preview

    HSeparator *sep_preview = memnew(HSeparator);
    sep_preview->set_custom_minimum_size(Size2(10, 20));
    vbc->add_child(sep_preview);

    lbl_preview_title = memnew(Label);
    lbl_preview_title->set_text(TTR("Preview"));
    vbc->add_child(lbl_preview_title);

    lbl_preview = memnew(Label);
    lbl_preview->set_text("");
    lbl_preview->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
    vbc->add_child(lbl_preview);

    // ---- Dialog related

    set_custom_minimum_size(Size2(383, 0));
    set_as_toplevel(true);
    get_ok()->set_text(TTR("Rename"));
    Button *but_reset = add_button(TTR("Reset"));

    eh.errfunc = _error_handler;
    eh.userdata = this;

    // ---- Connections

    cbut_collapse_features->connect("toggled",callable_mp(this, &ClassName::_features_toggled));

    // Substitite Buttons

    lne_search->connect("focus_entered",callable_mp(this, &ClassName::_update_substitute));
    lne_search->connect("focus_exited",callable_mp(this, &ClassName::_update_substitute));
    lne_replace->connect("focus_entered",callable_mp(this, &ClassName::_update_substitute));
    lne_replace->connect("focus_exited",callable_mp(this, &ClassName::_update_substitute));
    lne_prefix->connect("focus_entered",callable_mp(this, &ClassName::_update_substitute));
    lne_prefix->connect("focus_exited",callable_mp(this, &ClassName::_update_substitute));
    lne_suffix->connect("focus_entered",callable_mp(this, &ClassName::_update_substitute));
    lne_suffix->connect("focus_exited",callable_mp(this, &ClassName::_update_substitute));

    // Preview

    lne_prefix->connect("text_changed",callable_mp(this, &ClassName::_update_preview));
    lne_suffix->connect("text_changed",callable_mp(this, &ClassName::_update_preview));
    lne_search->connect("text_changed",callable_mp(this, &ClassName::_update_preview));
    lne_replace->connect("text_changed",callable_mp(this, &ClassName::_update_preview));
    spn_count_start->connect("value_changed",callable_mp(this, &ClassName::_update_preview_int));
    spn_count_step->connect("value_changed",callable_mp(this, &ClassName::_update_preview_int));
    spn_count_padding->connect("value_changed",callable_mp(this, &ClassName::_update_preview_int));
    opt_style->connect("item_selected",callable_mp(this, &ClassName::_update_preview_int));
    opt_case->connect("item_selected",callable_mp(this, &ClassName::_update_preview_int));
    cbut_substitute->connect("pressed",callable_mp(this, &ClassName::_update_preview), varray(""));
    cbut_regex->connect("pressed",callable_mp(this, &ClassName::_update_preview), varray(""));
    cbut_process->connect("pressed",callable_mp(this, &ClassName::_update_preview), varray(""));

    but_reset->connect("pressed",callable_mp(this, &ClassName::reset));

    reset();
    _features_toggled(false);
}

void RenameDialog::_bind_methods() {

    MethodBinder::bind_method("_features_toggled", &RenameDialog::_features_toggled);
    MethodBinder::bind_method("_update_preview", &RenameDialog::_update_preview);
    MethodBinder::bind_method("_update_preview_int", &RenameDialog::_update_preview_int);
    MethodBinder::bind_method("_insert_text", &RenameDialog::_insert_text);
    MethodBinder::bind_method("_update_substitute", &RenameDialog::_update_substitute);
    MethodBinder::bind_method("reset", &RenameDialog::reset);
    MethodBinder::bind_method("rename", &RenameDialog::rename);
}

void RenameDialog::_update_substitute() {

    LineEdit *focus_owner_line_edit = object_cast<LineEdit>(get_focus_owner());
    bool is_main_field = _is_main_field(focus_owner_line_edit);

    but_insert_name->set_disabled(!is_main_field);
    but_insert_parent->set_disabled(!is_main_field);
    but_insert_type->set_disabled(!is_main_field);
    but_insert_scene->set_disabled(!is_main_field);
    but_insert_root->set_disabled(!is_main_field);
    but_insert_count->set_disabled(!is_main_field);

    // The focus mode seems to be reset when disabling/re-enabling
    but_insert_name->set_focus_mode(FOCUS_NONE);
    but_insert_parent->set_focus_mode(FOCUS_NONE);
    but_insert_type->set_focus_mode(FOCUS_NONE);
    but_insert_scene->set_focus_mode(FOCUS_NONE);
    but_insert_root->set_focus_mode(FOCUS_NONE);
    but_insert_count->set_focus_mode(FOCUS_NONE);
}

void RenameDialog::_post_popup() {

    EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
    preview_node = nullptr;

    Array selected_node_list = editor_selection->get_selected_nodes();
    ERR_FAIL_COND(selected_node_list.empty());

    preview_node = selected_node_list[0].as<Node *>();

    _update_preview();
    _update_substitute();
}

void RenameDialog::_update_preview_int(int new_value) {
    _update_preview();
}

void RenameDialog::_update_preview(StringView new_text) {

    if (lock_preview_update || preview_node == nullptr)
        return;

    has_errors = false;
    add_error_handler(&eh);

    StringName new_name = _apply_rename(preview_node, spn_count_start->get_value());

    if (!has_errors) {

        lbl_preview_title->set_text(TTR("Preview"));
        lbl_preview->set_text(new_name);

        if (new_name == preview_node->get_name()) {
            // New name is identical to the old one. Don't color it as much to avoid distracting the user.
            const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_color("accent_color", "Editor");
            const Color text_color = EditorNode::get_singleton()->get_gui_base()->get_color("default_color", "RichTextLabel");

            lbl_preview->add_color_override("font_color", accent_color.linear_interpolate(text_color, 0.5));
        } else {
            lbl_preview->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("success_color", "Editor"));
        }
    }

    remove_error_handler(&eh);
}

StringName RenameDialog::_apply_rename(const Node *node, int count) {

    String search = lne_search->get_text();
    String replace = lne_replace->get_text();
    String prefix = lne_prefix->get_text();
    String suffix = lne_suffix->get_text();
    String new_name(node->get_name());

    if (cbut_substitute->is_pressed()) {
        search = _substitute(search, node, count);
        replace = _substitute(replace, node, count);
        prefix = _substitute(prefix, node, count);
        suffix = _substitute(suffix, node, count);
    }

    if (cbut_regex->is_pressed()) {

        new_name = _regex(search, new_name, replace);
    } else {
        new_name.replace(search, replace);
    }

    new_name = prefix + new_name + suffix;

    if (cbut_process->is_pressed()) {
        new_name = _postprocess(new_name);
    }

    return StringName(new_name);
}

String RenameDialog::_substitute(const String &subject, const Node *node, int count) {
    char buffmt_val[16];
    snprintf(buffmt_val,15,"%%0%dd",(int)spn_count_padding->get_value());
    String result = subject;
    result.replace("${COUNTER}", FormatVE(buffmt_val,count));

    if (node) {
        result.replace("${NAME}", node->get_name());
        result.replace("${TYPE}", node->get_class());
    }

    int current = EditorNode::get_singleton()->get_editor_data().get_edited_scene();
    result.replace("${SCENE}", EditorNode::get_singleton()->get_editor_data().get_scene_title(current).asCString());

    Node *root_node = SceneTree::get_singleton()->get_edited_scene_root();
    if (root_node) {
        result.replace("${ROOT}", root_node->get_name());
    }
    if (node) {
        Node *parent_node = node->get_parent();
        if (parent_node) {
            if (node == root_node) {
                // Can not substitute parent of root.
                result.replace("${PARENT}", "");
            } else {
                result.replace("${PARENT}", parent_node->get_name());
            }
        }
    }
    return result;
}

void RenameDialog::_error_handler(void *p_self, StringView p_func, StringView p_file, int p_line, StringView p_error, StringView p_errorexp, ErrorHandlerType p_type) {

    RenameDialog *self = (RenameDialog *)p_self;
    StringView source_file(p_file);

    // Only show first error that is related to "regex"
    if (self->has_errors || StringUtils::contains(source_file,"regex"))
        return;

    String err_str;
    if (not p_errorexp.empty()) {
        err_str = p_errorexp;
    } else {
        err_str = p_error;
    }

    self->has_errors = true;
    self->lbl_preview_title->set_text(TTR("Regular Expression Error"));
    self->lbl_preview->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"));
    self->lbl_preview->set_text(FormatSN(TTR("At character %s").asCString(), err_str.c_str()));

}

String RenameDialog::_regex(const String &pattern, const String &subject, const String &replacement) {

    RegEx regex(pattern);

    return regex.sub(subject, replacement, true);
}

String RenameDialog::_postprocess(const String &subject) {

    int style_id = opt_style->get_selected();

    String result = subject;

    if (style_id == 1) {
        // PascalCase to snake_case

        result = StringUtils::camelcase_to_underscore(result,true);
        result = _regex(String("_+"), result, "_");

    } else if (style_id == 2) {
        // snake_case to PascalCase

        RegEx pattern(("_+(.?)"));
        Array matches = pattern.search_all(result);

        // The name `_` would become empty; ignore it.
        if (!matches.empty() && result != "_") {
            String buffer;
            int start = 0;
            int end = 0;
            for (int i = 0; i < matches.size(); ++i) {
                start = ((Ref<RegExMatch>)matches[i])->get_start(1);
                buffer += StringUtils::substr(result,end, start - end - 1);
                buffer += StringUtils::char_uppercase(result[start]);
                end = start + 1;
            }
            buffer += StringUtils::substr(result,end, result.size() - (end + 1));
            result = StringUtils::capitalize(buffer.replaced("_", ""));
        }
    }

    int case_id = opt_case->get_selected();

    if (case_id == 1) {
        // To Lowercase
        result = StringUtils::to_lower(result);
    } else if (case_id == 2) {
        // To Upercase
        result = StringUtils::to_upper(result);
    }

    return result;
}

void RenameDialog::_iterate_scene(const Node *node, const Array &selection, int *counter) {

    if (!node)
        return;

    if (selection.contains(Variant(node))) {

        StringName new_name = _apply_rename(node, *counter);

        if (node->get_name() != new_name) {
            Pair<NodePath, StringName> rename_item;
            rename_item.first = node->get_path();
            rename_item.second = new_name;
            to_rename.push_back(rename_item);
        }

        *counter += spn_count_step->get_value();
    }

    int *cur_counter = counter;
    int level_counter = spn_count_start->get_value();

    if (chk_per_level_counter->is_pressed()) {
        cur_counter = &level_counter;
    }

    for (int i = 0; i < node->get_child_count(); ++i) {
        _iterate_scene(node->get_child(i), selection, cur_counter);
    }
}

void RenameDialog::rename() {

    // Editor selection is not ordered via scene tree. Instead iterate
    // over scene tree until all selected nodes are found in order.

    EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
    Array selected_node_list = editor_selection->get_selected_nodes();
    Node *root_node = SceneTree::get_singleton()->get_edited_scene_root();

    global_count = spn_count_start->get_value();
    to_rename.clear();

    // Forward recursive as opposed to the actual renaming.
    _iterate_scene(root_node, selected_node_list, &global_count);

    if (undo_redo && !to_rename.empty()) {

        undo_redo->create_action(TTR("Batch Rename"));

        // Make sure to iterate reversed so that child nodes will find parents.
        for (int i = to_rename.size() - 1; i >= 0; --i) {

            Node *n = root_node->get_node(to_rename[i].first);
            const StringName &new_name = to_rename[i].second;

            if (!n) {
                ERR_PRINT("Skipping missing node: " + String(to_rename[i].first.get_concatenated_subnames()));
                continue;
            }

            scene_tree_editor->emit_signal("node_prerename", Variant(n), new_name);
            undo_redo->add_do_method(scene_tree_editor, "_rename_node", Variant::from(n->get_instance_id()), new_name);
            undo_redo->add_undo_method(scene_tree_editor, "_rename_node", Variant::from(n->get_instance_id()), n->get_name());
        }

        undo_redo->commit_action();
    }
}

void RenameDialog::reset() {

    lock_preview_update = true;

    lne_prefix->clear();
    lne_suffix->clear();
    lne_search->clear();
    lne_replace->clear();

    cbut_substitute->set_pressed(false);
    cbut_regex->set_pressed(false);
    cbut_process->set_pressed(false);

    chk_per_level_counter->set_pressed(true);

    spn_count_start->set_value(1);
    spn_count_step->set_value(1);
    spn_count_padding->set_value(1);

    opt_style->select(0);
    opt_case->select(0);

    lock_preview_update = false;
    _update_preview();
}

bool RenameDialog::_is_main_field(LineEdit *line_edit) {
    return line_edit &&
           (line_edit == lne_search || line_edit == lne_replace || line_edit == lne_prefix || line_edit == lne_suffix);
}

void RenameDialog::_insert_text(StringView text) {

    LineEdit *focus_owner = object_cast<LineEdit>(get_focus_owner());

    if (_is_main_field(focus_owner)) {
        focus_owner->selection_delete();
        focus_owner->append_at_cursor(text);
        _update_preview();
    }
}

void RenameDialog::_features_toggled(bool pressed) {
    if (pressed) {
        tabc_features->show();
    } else {
        tabc_features->hide();
    }

    // Adjust to minimum size in y
    Size2i size = get_size();
    size.y = 0;
    set_size(size);
}
