From e58b10c883056930e966e842fad6bc1bbcd7979c Mon Sep 17 00:00:00 2001 From: Blazej Floch Date: Sun, 21 Jan 2018 01:12:25 -0500 Subject: Implements "Batch Rename" editor tool. --- editor/editor_data.cpp | 4 +- editor/editor_data.h | 2 +- editor/rename_dialog.cpp | 691 +++++++++++++++++++++++++++++++++++++++++++++ editor/rename_dialog.h | 119 ++++++++ editor/scene_tree_dock.cpp | 15 +- editor/scene_tree_dock.h | 3 + scene/gui/line_edit.h | 2 +- 7 files changed, 831 insertions(+), 5 deletions(-) create mode 100644 editor/rename_dialog.cpp create mode 100644 editor/rename_dialog.h diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 95ed40d889..70e5a8679b 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -867,7 +867,7 @@ Array EditorSelection::_get_transformable_selected_nodes() { return ret; } -Array EditorSelection::_get_selected_nodes() { +Array EditorSelection::get_selected_nodes() { Array ret; @@ -885,7 +885,7 @@ void EditorSelection::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &EditorSelection::clear); ClassDB::bind_method(D_METHOD("add_node", "node"), &EditorSelection::add_node); ClassDB::bind_method(D_METHOD("remove_node", "node"), &EditorSelection::remove_node); - ClassDB::bind_method(D_METHOD("get_selected_nodes"), &EditorSelection::_get_selected_nodes); + ClassDB::bind_method(D_METHOD("get_selected_nodes"), &EditorSelection::get_selected_nodes); ClassDB::bind_method(D_METHOD("get_transformable_selected_nodes"), &EditorSelection::_get_transformable_selected_nodes); ClassDB::bind_method(D_METHOD("_emit_change"), &EditorSelection::_emit_change); ADD_SIGNAL(MethodInfo("selection_changed")); diff --git a/editor/editor_data.h b/editor/editor_data.h index 844145853d..750916b8a5 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -224,7 +224,6 @@ private: List selected_node_list; void _update_nl(); - Array _get_selected_nodes(); Array _get_transformable_selected_nodes(); void _emit_change(); @@ -232,6 +231,7 @@ protected: static void _bind_methods(); public: + Array get_selected_nodes(); void add_node(Node *p_node); void remove_node(Node *p_node); bool is_selected(Node *) const; diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp new file mode 100644 index 0000000000..d617089142 --- /dev/null +++ b/editor/rename_dialog.cpp @@ -0,0 +1,691 @@ +/*************************************************************************/ +/* rename_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "editor_node.h" +#include "editor_settings.h" +#include "editor_themes.h" +#include "modules/regex/regex.h" +#include "plugins/script_editor_plugin.h" +#include "print_string.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; + + Ref collapse_theme = create_editor_theme(); + collapse_theme->set_icon("checked", "CheckBox", collapse_theme->get_icon("GuiTreeArrowDown", "EditorIcons")); + collapse_theme->set_icon("unchecked", "CheckBox", collapse_theme->get_icon("GuiTreeArrowRight", "EditorIcons")); + + CheckBox *chk_collapse_features = memnew(CheckBox); + chk_collapse_features->set_text(TTR("Advanced options")); + chk_collapse_features->set_theme(collapse_theme); + chk_collapse_features->set_focus_mode(FOCUS_NONE); + vbc->add_child(chk_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(CheckButton); + 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", this, "_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", this, "_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", this, "_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", this, "_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", this, "_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", this, "_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("Ammount 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("Ammount 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("Minium 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("Minium 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 RegEx + + VBoxContainer *vbc_regex = memnew(VBoxContainer); + vbc_regex->set_h_size_flags(SIZE_EXPAND_FILL); + vbc_regex->set_name(TTR("Regular Expressions")); + vbc_regex->set_custom_minimum_size(Size2(0, feature_min_height)); + tabc_features->add_child(vbc_regex); + + cbut_regex = memnew(CheckButton); + cbut_regex->set_text(TTR("Regular Expressions")); + vbc_regex->add_child(cbut_regex); + + // ---- 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(CheckButton); + 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("CamelCase to under_scored")); + opt_style->add_item(TTR("under_scored to CamelCase")); + 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", Color(1, 0.5f, 0, 1)); + 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 + + chk_collapse_features->connect("toggled", this, "_features_toggled"); + + // Substitite Buttons + + lne_search->connect("focus_entered", this, "_update_substitute"); + lne_search->connect("focus_exited", this, "_update_substitute"); + lne_replace->connect("focus_entered", this, "_update_substitute"); + lne_replace->connect("focus_exited", this, "_update_substitute"); + lne_prefix->connect("focus_entered", this, "_update_substitute"); + lne_prefix->connect("focus_exited", this, "_update_substitute"); + lne_suffix->connect("focus_entered", this, "_update_substitute"); + lne_suffix->connect("focus_exited", this, "_update_substitute"); + + // Preview + + lne_prefix->connect("text_changed", this, "_update_preview"); + lne_suffix->connect("text_changed", this, "_update_preview"); + lne_search->connect("text_changed", this, "_update_preview"); + lne_replace->connect("text_changed", this, "_update_preview"); + spn_count_start->connect("value_changed", this, "_update_preview_int"); + spn_count_step->connect("value_changed", this, "_update_preview_int"); + spn_count_padding->connect("value_changed", this, "_update_preview_int"); + opt_style->connect("item_selected", this, "_update_preview_int"); + opt_case->connect("item_selected", this, "_update_preview_int"); + cbut_substitute->connect("pressed", this, "_update_preview", varray("")); + cbut_regex->connect("pressed", this, "_update_preview", varray("")); + cbut_process->connect("pressed", this, "_update_preview", varray("")); + + but_reset->connect("pressed", this, "reset"); + + reset(); + _features_toggled(false); +} + +void RenameDialog::_bind_methods() { + + ClassDB::bind_method("_features_toggled", &RenameDialog::_features_toggled); + ClassDB::bind_method("_update_preview", &RenameDialog::_update_preview); + ClassDB::bind_method("_update_preview_int", &RenameDialog::_update_preview_int); + ClassDB::bind_method("_insert_text", &RenameDialog::_insert_text); + ClassDB::bind_method("_update_substitute", &RenameDialog::_update_substitute); + ClassDB::bind_method("reset", &RenameDialog::reset); + ClassDB::bind_method("rename", &RenameDialog::rename); +} + +void RenameDialog::_update_substitute() { + + LineEdit *focus_owner_line_edit = Object::cast_to(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()) { + lbl_preview->add_color_override("font_color", Color(0, 0.5f, 0.25f, 1)); + } else { + lbl_preview->add_color_override("font_color", Color(0, 1, 0.5f, 1)); + } + } + + 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()); + } + + 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("Error")); + self->lbl_preview->add_color_override("font_color", Color(1, 0.25f, 0, 1)); + self->lbl_preview->set_text(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) { + + // CamelCase to Under_Line + result = result.camelcase_to_underscore(true); + result = _regex("_+", result, "_"); + + } else if (style_id == 2) { + + // Under_Line to CamelCase + RegEx pattern("_+(.?)"); + Array matches = pattern.search_all(result); + + // _ name would become empty. Ignore + if (matches.size() && result != "_") { + String buffer; + int start = 0; + int end = 0; + for (int i = 0; i < matches.size(); ++i) { + start = ((Ref)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 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_PRINTS("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(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); +} diff --git a/editor/rename_dialog.h b/editor/rename_dialog.h new file mode 100644 index 0000000000..c5ebc30c0c --- /dev/null +++ b/editor/rename_dialog.h @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* rename_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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. */ +/*************************************************************************/ + +#ifndef RENAME_DIALOG_H +#define RENAME_DIALOG_H + +#include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/option_button.h" +#include "scene/gui/spin_box.h" + +#include "editor/scene_tree_editor.h" +#include "undo_redo.h" + +/** +@author Blazej Floch +*/ + +class RenameDialog : public ConfirmationDialog { + + GDCLASS(RenameDialog, ConfirmationDialog); + + virtual void ok_pressed() { rename(); }; + void _cancel_pressed(){}; + void _features_toggled(bool pressed); + void _insert_text(String text); + void _update_substitute(); + bool _is_main_field(LineEdit *line_edit); + + void _iterate_scene(const Node *node, const Array &selection, int *count); + String _apply_rename(const Node *node, int count); + String _substitute(const String &subject, const Node *node, int count); + String _regex(const String &pattern, const String &subject, const String &replacement); + String _postprocess(const String &subject); + void _update_preview(String new_text = ""); + void _update_preview_int(int new_value = 0); + static void _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); + + SceneTreeEditor *scene_tree_editor; + UndoRedo *undo_redo; + int global_count; + + LineEdit *lne_search; + LineEdit *lne_replace; + LineEdit *lne_prefix; + LineEdit *lne_suffix; + + TabContainer *tabc_features; + + CheckButton *cbut_substitute; + CheckButton *cbut_regex; + CheckButton *cbut_process; + CheckBox *chk_per_level_counter; + + Button *but_insert_name; + Button *but_insert_parent; + Button *but_insert_type; + Button *but_insert_scene; + Button *but_insert_root; + Button *but_insert_count; + + SpinBox *spn_count_start; + SpinBox *spn_count_step; + SpinBox *spn_count_padding; + + OptionButton *opt_style; + OptionButton *opt_case; + + Label *lbl_preview_title; + Label *lbl_preview; + + List > to_rename; + Node *preview_node; + bool lock_preview_update; + ErrorHandlerList eh; + bool has_errors; + +protected: + void _notification(int p_what){}; + static void _bind_methods(); + virtual void _post_popup(); + +public: + void reset(); + void rename(); + + RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_undo_redo = NULL); + ~RenameDialog(){}; +}; + +#endif diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 41ee5ab286..1f392dc405 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -70,7 +70,9 @@ void SceneTreeDock::_unhandled_key_input(Ref p_event) { if (!p_event->is_pressed() || p_event->is_echo()) return; - if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { + if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { + _tool_selected(TOOL_BATCH_RENAME); + } else if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { _tool_selected(TOOL_NEW); } else if (ED_IS_SHORTCUT("scene_tree/instance_scene", p_event)) { _tool_selected(TOOL_INSTANCE); @@ -277,6 +279,12 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { switch (p_tool) { + case TOOL_BATCH_RENAME: { + Tree *tree = scene_tree->get_scene_tree(); + if (tree->is_anything_selected()) { + rename_dialog->popup_centered(); + } + } break; case TOOL_NEW: { String preferred = ""; @@ -1745,6 +1753,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->clear(); + menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/batch_rename"), TOOL_BATCH_RENAME); if (selection.size() == 1) { subresources.clear(); @@ -1934,6 +1943,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel filter_hbc->add_constant_override("separate", 0); ToolButton *tb; + ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KEY_MASK_CMD | KEY_F2); ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A); ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene")); ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type")); @@ -2032,6 +2042,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel add_child(create_dialog); create_dialog->connect("create", this, "_create"); + rename_dialog = memnew(RenameDialog(scene_tree, &editor_data->get_undo_redo())); + add_child(rename_dialog); + script_create_dialog = memnew(ScriptCreateDialog); add_child(script_create_dialog); script_create_dialog->connect("script_created", this, "_script_created"); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 0a68aa7dc2..2af8cd3fe8 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -36,6 +36,7 @@ #include "editor/editor_data.h" #include "editor/editor_sub_scene.h" #include "editor/groups_editor.h" +#include "editor/rename_dialog.h" #include "editor/reparent_dialog.h" #include "editor/script_create_dialog.h" #include "scene/animation/animation_player.h" @@ -58,6 +59,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_NEW, TOOL_INSTANCE, + TOOL_BATCH_RENAME, TOOL_REPLACE, TOOL_ATTACH_SCRIPT, TOOL_CLEAR_SCRIPT, @@ -90,6 +92,7 @@ class SceneTreeDock : public VBoxContainer { int current_option; CreateDialog *create_dialog; + RenameDialog *rename_dialog; ToolButton *button_add; ToolButton *button_instance; diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e15980d3c4..e371c3c605 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -121,7 +121,6 @@ private: void shift_selection_check_post(bool); void selection_fill_at_cursor(); - void selection_delete(); void set_window_pos(int p_pos); void set_cursor_at_pixel_pos(int p_x); @@ -157,6 +156,7 @@ public: void select(int p_from = 0, int p_to = -1); void select_all(); + void selection_delete(); void deselect(); void delete_char(); -- cgit v1.2.3