text: /*************************************************************************/
  /*  rename_dialog.cpp                                                    */
  /*************************************************************************/
  /*                       This file is part of:                           */
  /*                           GODOT ENGINE                                */
  /*                      https://godotengine.org                          */
  /*************************************************************************/
  /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
  /* Copyright (c) 2014-2020 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/print_string.h"
  #include "editor_node.h"
  #include "editor_scale.h"
  #include "editor_settings.h"
  #include "editor_themes.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"
  
  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 = NULL;
  
  	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(String("${NAME}\n") + TTR("Node name"));
  	but_insert_name->set_focus_mode(FOCUS_NONE);
  	but_insert_name->connect("pressed", callable_mp(this, &RenameDialog::_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(String("${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, &RenameDialog::_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(String("${TYPE}\n") + TTR("Node type"));
  	but_insert_type->set_focus_mode(FOCUS_NONE);
  	but_insert_type->connect("pressed", callable_mp(this, &RenameDialog::_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(String("${SCENE}\n") + TTR("Current scene name"));
  	but_insert_scene->set_focus_mode(FOCUS_NONE);
  	but_insert_scene->connect("pressed", callable_mp(this, &RenameDialog::_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(String("${ROOT}\n") + TTR("Root node name"));
  	but_insert_root->set_focus_mode(FOCUS_NONE);
  	but_insert_root->connect("pressed", callable_mp(this, &RenameDialog::_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(String("${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, &RenameDialog::_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, &RenameDialog::_features_toggled));
  
  	// Substitite Buttons
  
  	lne_search->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_search->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_replace->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_replace->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_prefix->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_prefix->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_suffix->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
  	lne_suffix->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
  
  	// Preview
  
  	lne_prefix->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
  	lne_suffix->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
  	lne_search->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
  	lne_replace->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
  	spn_count_start->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
  	spn_count_step->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
  	spn_count_padding->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
  	opt_style->connect("item_selected", callable_mp(this, &RenameDialog::_update_preview_int));
  	opt_case->connect("item_selected", callable_mp(this, &RenameDialog::_update_preview_int));
  	cbut_substitute->connect("pressed", callable_mp(this, &RenameDialog::_update_preview), varray(""));
  	cbut_regex->connect("pressed", callable_mp(this, &RenameDialog::_update_preview), varray(""));
  	cbut_process->connect("pressed", callable_mp(this, &RenameDialog::_update_preview), varray(""));
  
  	but_reset->connect("pressed", callable_mp(this, &RenameDialog::reset));
  
  	reset();
  	_features_toggled(false);
  }
  
  void RenameDialog::_bind_methods() {
  
  	ClassDB::bind_method("rename", &RenameDialog::rename);
  }
  
  void RenameDialog::_update_substitute() {
  
  	LineEdit *focus_owner_line_edit = Object::cast_to<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 = NULL;
  
  	Array selected_node_list = editor_selection->get_selected_nodes();
  	ERR_FAIL_COND(selected_node_list.size() == 0);
  
  	preview_node = selected_node_list[0];
  
  	_update_preview();
  	_update_substitute();
  }
  
  void RenameDialog::_update_preview_int(int new_value) {
  	_update_preview();
  }
  
  void RenameDialog::_update_preview(String new_text) {
  
  	if (lock_preview_update || preview_node == NULL)
  		return;
  
  	has_errors = false;
  	add_error_handler(&eh);
  
  	String 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);
  }
  
  String 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 = new_name.replace(search, replace);
  	}
  
  	new_name = prefix + new_name + suffix;
  
  	if (cbut_process->is_pressed()) {
  		new_name = _postprocess(new_name);
  	}
  
  	return new_name;
  }
  
  String RenameDialog::_substitute(const String &subject, const Node *node, int count) {
  
  	String result = subject.replace("${COUNTER}", vformat("%0" + itos(spn_count_padding->get_value()) + "d", count));
  
  	if (node) {
  		result = result.replace("${NAME}", node->get_name());
  		result = result.replace("${TYPE}", node->get_class());
  	}
  
  	int current = EditorNode::get_singleton()->get_editor_data().get_edited_scene();
  	result = result.replace("${SCENE}", EditorNode::get_singleton()->get_editor_data().get_scene_title(current));
  
  	Node *root_node = SceneTree::get_singleton()->get_edited_scene_root();
  	if (root_node) {
  		result = 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 = result.replace("${PARENT}", "");
  			} else {
  				result = result.replace("${PARENT}", parent_node->get_name());
  			}
  		}
  	}
  	return result;
  }
  
  void RenameDialog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) {
  
  	RenameDialog *self = (RenameDialog *)p_self;
  	String source_file(p_file);
  
  	// Only show first error that is related to "regex"
  	if (self->has_errors || source_file.find("regex") < 0)
  		return;
  
  	String err_str;
  	if (p_errorexp && p_errorexp[0]) {
  		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(vformat(TTR("At character %s"), err_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 = result.camelcase_to_underscore(true);
  		result = _regex("_+", 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.size() && 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 += result.substr(end, start - end - 1);
  				buffer += result.substr(start, 1).to_upper();
  				end = start + 1;
  			}
  			buffer += result.substr(end, result.size() - (end + 1));
  			result = buffer.replace("_", "").capitalize();
  		}
  	}
  
  	int case_id = opt_case->get_selected();
  
  	if (case_id == 1) {
  		// To Lowercase
  		result = result.to_lower();
  	} else if (case_id == 2) {
  		// To Upercase
  		result = result.to_upper();
  	}
  
  	return result;
  }
  
  void RenameDialog::_iterate_scene(const Node *node, const Array &selection, int *counter) {
  
  	if (!node)
  		return;
  
  	if (selection.has(node)) {
  
  		String new_name = _apply_rename(node, *counter);
  
  		if (node->get_name() != new_name) {
  			Pair<NodePath, String> 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 String &new_name = to_rename[i].second;
  
  			if (!n) {
  				ERR_PRINT("Skipping missing node: " + to_rename[i].first.get_concatenated_subnames());
  				continue;
  			}
  
  			scene_tree_editor->emit_signal("node_prerename", n, new_name);
  			undo_redo->add_do_method(scene_tree_editor, "_rename_node", n->get_instance_id(), new_name);
  			undo_redo->add_undo_method(scene_tree_editor, "_rename_node", 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(String text) {
  
  	LineEdit *focus_owner = Object::cast_to<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);
  }
  