1/**************************************************************************/
2/* rename_dialog.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "rename_dialog.h"
32
33#include "modules/modules_enabled.gen.h" // For regex.
34#ifdef MODULE_REGEX_ENABLED
35
36#include "core/string/print_string.h"
37#include "editor/editor_node.h"
38#include "editor/editor_scale.h"
39#include "editor/editor_settings.h"
40#include "editor/editor_string_names.h"
41#include "editor/editor_themes.h"
42#include "editor/editor_undo_redo_manager.h"
43#include "editor/plugins/script_editor_plugin.h"
44#include "modules/regex/regex.h"
45#include "scene/gui/check_box.h"
46#include "scene/gui/check_button.h"
47#include "scene/gui/control.h"
48#include "scene/gui/grid_container.h"
49#include "scene/gui/label.h"
50#include "scene/gui/option_button.h"
51#include "scene/gui/separator.h"
52#include "scene/gui/spin_box.h"
53#include "scene/gui/tab_container.h"
54
55RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) {
56 scene_tree_editor = p_scene_tree_editor;
57 preview_node = nullptr;
58
59 set_title(TTR("Batch Rename"));
60
61 VBoxContainer *vbc = memnew(VBoxContainer);
62 add_child(vbc);
63
64 // -- Search/Replace Area
65
66 GridContainer *grd_main = memnew(GridContainer);
67 grd_main->set_columns(2);
68 grd_main->set_v_size_flags(Control::SIZE_EXPAND_FILL);
69 vbc->add_child(grd_main);
70
71 // ---- 1st & 2nd row
72
73 Label *lbl_search = memnew(Label);
74 lbl_search->set_text(TTR("Search:"));
75
76 lne_search = memnew(LineEdit);
77 lne_search->set_name("lne_search");
78 lne_search->set_h_size_flags(Control::SIZE_EXPAND_FILL);
79
80 Label *lbl_replace = memnew(Label);
81 lbl_replace->set_text(TTR("Replace:"));
82
83 lne_replace = memnew(LineEdit);
84 lne_replace->set_name("lne_replace");
85 lne_replace->set_h_size_flags(Control::SIZE_EXPAND_FILL);
86
87 grd_main->add_child(lbl_search);
88 grd_main->add_child(lbl_replace);
89 grd_main->add_child(lne_search);
90 grd_main->add_child(lne_replace);
91
92 // ---- 3rd & 4th row
93
94 Label *lbl_prefix = memnew(Label);
95 lbl_prefix->set_text(TTR("Prefix:"));
96
97 lne_prefix = memnew(LineEdit);
98 lne_prefix->set_name("lne_prefix");
99 lne_prefix->set_h_size_flags(Control::SIZE_EXPAND_FILL);
100
101 Label *lbl_suffix = memnew(Label);
102 lbl_suffix->set_text(TTR("Suffix:"));
103
104 lne_suffix = memnew(LineEdit);
105 lne_suffix->set_name("lne_suffix");
106 lne_suffix->set_h_size_flags(Control::SIZE_EXPAND_FILL);
107
108 grd_main->add_child(lbl_prefix);
109 grd_main->add_child(lbl_suffix);
110 grd_main->add_child(lne_prefix);
111 grd_main->add_child(lne_suffix);
112
113 // -- Feature Tabs
114
115 cbut_regex = memnew(CheckButton);
116 cbut_regex->set_text(TTR("Use Regular Expressions"));
117 vbc->add_child(cbut_regex);
118
119 CheckButton *cbut_collapse_features = memnew(CheckButton);
120 cbut_collapse_features->set_text(TTR("Advanced Options"));
121 vbc->add_child(cbut_collapse_features);
122
123 tabc_features = memnew(TabContainer);
124 tabc_features->set_use_hidden_tabs_for_min_size(true);
125 vbc->add_child(tabc_features);
126
127 // ---- Tab Substitute
128
129 VBoxContainer *vbc_substitute = memnew(VBoxContainer);
130 vbc_substitute->set_h_size_flags(Control::SIZE_EXPAND_FILL);
131
132 vbc_substitute->set_name(TTR("Substitute"));
133 tabc_features->add_child(vbc_substitute);
134
135 cbut_substitute = memnew(CheckBox);
136 cbut_substitute->set_text(TTR("Substitute"));
137 vbc_substitute->add_child(cbut_substitute);
138
139 GridContainer *grd_substitute = memnew(GridContainer);
140 grd_substitute->set_columns(3);
141 vbc_substitute->add_child(grd_substitute);
142
143 // Name
144
145 but_insert_name = memnew(Button);
146 but_insert_name->set_text("NAME");
147 but_insert_name->set_tooltip_text(String("${NAME}\n") + TTR("Node name."));
148 but_insert_name->set_focus_mode(Control::FOCUS_NONE);
149 but_insert_name->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${NAME}"));
150 but_insert_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
151 grd_substitute->add_child(but_insert_name);
152
153 // Parent
154
155 but_insert_parent = memnew(Button);
156 but_insert_parent->set_text("PARENT");
157 but_insert_parent->set_tooltip_text(String("${PARENT}\n") + TTR("Node's parent name, if available."));
158 but_insert_parent->set_focus_mode(Control::FOCUS_NONE);
159 but_insert_parent->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${PARENT}"));
160 but_insert_parent->set_h_size_flags(Control::SIZE_EXPAND_FILL);
161 grd_substitute->add_child(but_insert_parent);
162
163 // Type
164
165 but_insert_type = memnew(Button);
166 but_insert_type->set_text("TYPE");
167 but_insert_type->set_tooltip_text(String("${TYPE}\n") + TTR("Node type."));
168 but_insert_type->set_focus_mode(Control::FOCUS_NONE);
169 but_insert_type->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${TYPE}"));
170 but_insert_type->set_h_size_flags(Control::SIZE_EXPAND_FILL);
171 grd_substitute->add_child(but_insert_type);
172
173 // Scene
174
175 but_insert_scene = memnew(Button);
176 but_insert_scene->set_text("SCENE");
177 but_insert_scene->set_tooltip_text(String("${SCENE}\n") + TTR("Current scene name."));
178 but_insert_scene->set_focus_mode(Control::FOCUS_NONE);
179 but_insert_scene->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${SCENE}"));
180 but_insert_scene->set_h_size_flags(Control::SIZE_EXPAND_FILL);
181 grd_substitute->add_child(but_insert_scene);
182
183 // Root
184
185 but_insert_root = memnew(Button);
186 but_insert_root->set_text("ROOT");
187 but_insert_root->set_tooltip_text(String("${ROOT}\n") + TTR("Root node name."));
188 but_insert_root->set_focus_mode(Control::FOCUS_NONE);
189 but_insert_root->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${ROOT}"));
190 but_insert_root->set_h_size_flags(Control::SIZE_EXPAND_FILL);
191 grd_substitute->add_child(but_insert_root);
192
193 // Count
194
195 but_insert_count = memnew(Button);
196 but_insert_count->set_text("COUNTER");
197 but_insert_count->set_tooltip_text(String("${COUNTER}\n") + TTR("Sequential integer counter.\nCompare counter options."));
198 but_insert_count->set_focus_mode(Control::FOCUS_NONE);
199 but_insert_count->connect("pressed", callable_mp(this, &RenameDialog::_insert_text).bind("${COUNTER}"));
200 but_insert_count->set_h_size_flags(Control::SIZE_EXPAND_FILL);
201 grd_substitute->add_child(but_insert_count);
202
203 chk_per_level_counter = memnew(CheckBox);
204 chk_per_level_counter->set_text(TTR("Per-level Counter"));
205 chk_per_level_counter->set_tooltip_text(TTR("If set, the counter restarts for each group of child nodes."));
206 vbc_substitute->add_child(chk_per_level_counter);
207
208 HBoxContainer *hbc_count_options = memnew(HBoxContainer);
209 vbc_substitute->add_child(hbc_count_options);
210
211 Label *lbl_count_start = memnew(Label);
212 lbl_count_start->set_text(TTR("Start"));
213 lbl_count_start->set_tooltip_text(TTR("Initial value for the counter."));
214 hbc_count_options->add_child(lbl_count_start);
215
216 spn_count_start = memnew(SpinBox);
217 spn_count_start->set_tooltip_text(TTR("Initial value for the counter."));
218 spn_count_start->set_step(1);
219 spn_count_start->set_min(0);
220 hbc_count_options->add_child(spn_count_start);
221
222 Label *lbl_count_step = memnew(Label);
223 lbl_count_step->set_text(TTR("Step"));
224 lbl_count_step->set_tooltip_text(TTR("Amount by which counter is incremented for each node."));
225 hbc_count_options->add_child(lbl_count_step);
226
227 spn_count_step = memnew(SpinBox);
228 spn_count_step->set_tooltip_text(TTR("Amount by which counter is incremented for each node."));
229 spn_count_step->set_step(1);
230 hbc_count_options->add_child(spn_count_step);
231
232 Label *lbl_count_padding = memnew(Label);
233 lbl_count_padding->set_text(TTR("Padding"));
234 lbl_count_padding->set_tooltip_text(TTR("Minimum number of digits for the counter.\nMissing digits are padded with leading zeros."));
235 hbc_count_options->add_child(lbl_count_padding);
236
237 spn_count_padding = memnew(SpinBox);
238 spn_count_padding->set_tooltip_text(TTR("Minimum number of digits for the counter.\nMissing digits are padded with leading zeros."));
239 spn_count_padding->set_step(1);
240 hbc_count_options->add_child(spn_count_padding);
241
242 // ---- Tab Process
243
244 VBoxContainer *vbc_process = memnew(VBoxContainer);
245 vbc_process->set_h_size_flags(Control::SIZE_EXPAND_FILL);
246 vbc_process->set_name(TTR("Post-Process"));
247 tabc_features->add_child(vbc_process);
248
249 cbut_process = memnew(CheckBox);
250 cbut_process->set_text(TTR("Post-Process"));
251 vbc_process->add_child(cbut_process);
252
253 // ------ Style
254
255 HBoxContainer *hbc_style = memnew(HBoxContainer);
256 vbc_process->add_child(hbc_style);
257
258 Label *lbl_style = memnew(Label);
259 lbl_style->set_text(TTR("Style"));
260 hbc_style->add_child(lbl_style);
261
262 opt_style = memnew(OptionButton);
263 opt_style->add_item(TTR("Keep"));
264 opt_style->add_item(TTR("PascalCase to snake_case"));
265 opt_style->add_item(TTR("snake_case to PascalCase"));
266 hbc_style->add_child(opt_style);
267
268 // ------ Case
269
270 HBoxContainer *hbc_case = memnew(HBoxContainer);
271 vbc_process->add_child(hbc_case);
272
273 Label *lbl_case = memnew(Label);
274 lbl_case->set_text(TTR("Case"));
275 hbc_case->add_child(lbl_case);
276
277 opt_case = memnew(OptionButton);
278 opt_case->add_item(TTR("Keep"));
279 opt_case->add_item(TTR("To Lowercase"));
280 opt_case->add_item(TTR("To Uppercase"));
281 hbc_case->add_child(opt_case);
282
283 // -- Preview
284
285 HSeparator *sep_preview = memnew(HSeparator);
286 sep_preview->set_custom_minimum_size(Size2(10, 20));
287 vbc->add_child(sep_preview);
288
289 lbl_preview_title = memnew(Label);
290 vbc->add_child(lbl_preview_title);
291
292 lbl_preview = memnew(Label);
293 lbl_preview->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
294 vbc->add_child(lbl_preview);
295
296 // ---- Dialog related
297
298 set_min_size(Size2(383, 0));
299 set_ok_button_text(TTR("Rename"));
300 Button *but_reset = add_button(TTR("Reset"));
301
302 eh.errfunc = _error_handler;
303 eh.userdata = this;
304
305 // ---- Connections
306
307 cbut_collapse_features->connect("toggled", callable_mp(this, &RenameDialog::_features_toggled));
308
309 // Substitute Buttons
310
311 lne_search->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
312 lne_search->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
313 lne_replace->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
314 lne_replace->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
315 lne_prefix->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
316 lne_prefix->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
317 lne_suffix->connect("focus_entered", callable_mp(this, &RenameDialog::_update_substitute));
318 lne_suffix->connect("focus_exited", callable_mp(this, &RenameDialog::_update_substitute));
319
320 // Preview
321
322 lne_prefix->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
323 lne_suffix->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
324 lne_search->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
325 lne_replace->connect("text_changed", callable_mp(this, &RenameDialog::_update_preview));
326 spn_count_start->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
327 spn_count_step->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
328 spn_count_padding->connect("value_changed", callable_mp(this, &RenameDialog::_update_preview_int));
329 opt_style->connect("item_selected", callable_mp(this, &RenameDialog::_update_preview_int));
330 opt_case->connect("item_selected", callable_mp(this, &RenameDialog::_update_preview_int));
331 cbut_substitute->connect("pressed", callable_mp(this, &RenameDialog::_update_preview).bind(""));
332 cbut_regex->connect("pressed", callable_mp(this, &RenameDialog::_update_preview).bind(""));
333 cbut_process->connect("pressed", callable_mp(this, &RenameDialog::_update_preview).bind(""));
334
335 but_reset->connect("pressed", callable_mp(this, &RenameDialog::reset));
336
337 reset();
338 _features_toggled(false);
339}
340
341void RenameDialog::_bind_methods() {
342 ClassDB::bind_method("rename", &RenameDialog::rename);
343}
344
345void RenameDialog::_update_substitute() {
346 LineEdit *focus_owner_line_edit = Object::cast_to<LineEdit>(get_viewport()->gui_get_focus_owner());
347 bool is_main_field = _is_main_field(focus_owner_line_edit);
348
349 but_insert_name->set_disabled(!is_main_field);
350 but_insert_parent->set_disabled(!is_main_field);
351 but_insert_type->set_disabled(!is_main_field);
352 but_insert_scene->set_disabled(!is_main_field);
353 but_insert_root->set_disabled(!is_main_field);
354 but_insert_count->set_disabled(!is_main_field);
355
356 // The focus mode seems to be reset when disabling/re-enabling
357 but_insert_name->set_focus_mode(Control::FOCUS_NONE);
358 but_insert_parent->set_focus_mode(Control::FOCUS_NONE);
359 but_insert_type->set_focus_mode(Control::FOCUS_NONE);
360 but_insert_scene->set_focus_mode(Control::FOCUS_NONE);
361 but_insert_root->set_focus_mode(Control::FOCUS_NONE);
362 but_insert_count->set_focus_mode(Control::FOCUS_NONE);
363}
364
365void RenameDialog::_post_popup() {
366 ConfirmationDialog::_post_popup();
367
368 EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
369 preview_node = nullptr;
370
371 Array selected_node_list = editor_selection->get_selected_nodes();
372 ERR_FAIL_COND(selected_node_list.size() == 0);
373
374 preview_node = Object::cast_to<Node>(selected_node_list[0]);
375
376 _update_preview();
377 _update_substitute();
378}
379
380void RenameDialog::_update_preview_int(int new_value) {
381 _update_preview();
382}
383
384void RenameDialog::_update_preview(String new_text) {
385 if (lock_preview_update || preview_node == nullptr) {
386 return;
387 }
388
389 has_errors = false;
390 add_error_handler(&eh);
391
392 String new_name = _apply_rename(preview_node, spn_count_start->get_value());
393
394 if (!has_errors) {
395 lbl_preview_title->set_text(TTR("Preview:"));
396 lbl_preview->set_text(new_name);
397
398 if (new_name == preview_node->get_name()) {
399 // New name is identical to the old one. Don't color it as much to avoid distracting the user.
400 const Color accent_color = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("accent_color"), EditorStringName(Editor));
401 const Color text_color = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("default_color"), SNAME("RichTextLabel"));
402 lbl_preview->add_theme_color_override("font_color", accent_color.lerp(text_color, 0.5));
403 } else {
404 lbl_preview->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("success_color"), EditorStringName(Editor)));
405 }
406 }
407
408 remove_error_handler(&eh);
409}
410
411String RenameDialog::_apply_rename(const Node *node, int count) {
412 String search = lne_search->get_text();
413 String replace = lne_replace->get_text();
414 String prefix = lne_prefix->get_text();
415 String suffix = lne_suffix->get_text();
416 String new_name = node->get_name();
417
418 if (cbut_substitute->is_pressed()) {
419 search = _substitute(search, node, count);
420 replace = _substitute(replace, node, count);
421 prefix = _substitute(prefix, node, count);
422 suffix = _substitute(suffix, node, count);
423 }
424
425 if (cbut_regex->is_pressed()) {
426 new_name = _regex(search, new_name, replace);
427 } else {
428 new_name = new_name.replace(search, replace);
429 }
430
431 new_name = prefix + new_name + suffix;
432
433 if (cbut_process->is_pressed()) {
434 new_name = _postprocess(new_name);
435 }
436
437 return new_name;
438}
439
440String RenameDialog::_substitute(const String &subject, const Node *node, int count) {
441 String result = subject.replace("${COUNTER}", vformat("%0" + itos(spn_count_padding->get_value()) + "d", count));
442
443 if (node) {
444 result = result.replace("${NAME}", node->get_name());
445 result = result.replace("${TYPE}", node->get_class());
446 }
447
448 int current = EditorNode::get_editor_data().get_edited_scene();
449 // Always request the scene title with the extension stripped.
450 // Otherwise, the result could vary depending on whether a scene with the same name
451 // (but different extension) is currently open.
452 result = result.replace("${SCENE}", EditorNode::get_editor_data().get_scene_title(current, true));
453
454 Node *root_node = SceneTree::get_singleton()->get_edited_scene_root();
455 if (root_node) {
456 result = result.replace("${ROOT}", root_node->get_name());
457 }
458 if (node) {
459 Node *parent_node = node->get_parent();
460 if (parent_node) {
461 if (node == root_node) {
462 // Can not substitute parent of root.
463 result = result.replace("${PARENT}", "");
464 } else {
465 result = result.replace("${PARENT}", parent_node->get_name());
466 }
467 }
468 }
469 return result;
470}
471
472void 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, bool p_editor_notify, ErrorHandlerType p_type) {
473 RenameDialog *self = (RenameDialog *)p_self;
474 String source_file = String::utf8(p_file);
475
476 // Only show first error that is related to "regex"
477 if (self->has_errors || source_file.find("regex") < 0) {
478 return;
479 }
480
481 String err_str;
482 if (p_errorexp && p_errorexp[0]) {
483 err_str = String::utf8(p_errorexp);
484 } else {
485 err_str = String::utf8(p_error);
486 }
487
488 self->has_errors = true;
489 self->lbl_preview_title->set_text(TTR("Regular Expression Error:"));
490 self->lbl_preview->add_theme_color_override("font_color", EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor)));
491 self->lbl_preview->set_text(vformat(TTR("At character %s"), err_str));
492}
493
494String RenameDialog::_regex(const String &pattern, const String &subject, const String &replacement) {
495 RegEx regex(pattern);
496
497 return regex.sub(subject, replacement, true);
498}
499
500String RenameDialog::_postprocess(const String &subject) {
501 int style_id = opt_style->get_selected();
502
503 String result = subject;
504
505 if (style_id == 1) {
506 // PascalCase to snake_case
507
508 result = result.to_snake_case();
509 result = _regex("_+", result, "_");
510
511 } else if (style_id == 2) {
512 // snake_case to PascalCase
513
514 RegEx pattern("_+(.?)");
515 Array matches = pattern.search_all(result);
516
517 // The name `_` would become empty; ignore it.
518 if (matches.size() && result != "_") {
519 String buffer;
520 int start = 0;
521 int end = 0;
522 for (int i = 0; i < matches.size(); ++i) {
523 start = ((Ref<RegExMatch>)matches[i])->get_start(1);
524 buffer += result.substr(end, start - end - 1);
525 buffer += result.substr(start, 1).to_upper();
526 end = start + 1;
527 }
528 buffer += result.substr(end, result.size() - (end + 1));
529 result = buffer.to_pascal_case();
530 }
531 }
532
533 int case_id = opt_case->get_selected();
534
535 if (case_id == 1) {
536 // To Lowercase
537 result = result.to_lower();
538 } else if (case_id == 2) {
539 // To Uppercase
540 result = result.to_upper();
541 }
542
543 return result;
544}
545
546void RenameDialog::_iterate_scene(const Node *node, const Array &selection, int *counter) {
547 if (!node) {
548 return;
549 }
550
551 if (selection.has(node)) {
552 String new_name = _apply_rename(node, *counter);
553
554 if (node->get_name() != new_name) {
555 Pair<NodePath, String> rename_item;
556 rename_item.first = node->get_path();
557 rename_item.second = new_name;
558 to_rename.push_back(rename_item);
559 }
560
561 *counter += spn_count_step->get_value();
562 }
563
564 int *cur_counter = counter;
565 int level_counter = spn_count_start->get_value();
566
567 if (chk_per_level_counter->is_pressed()) {
568 cur_counter = &level_counter;
569 }
570
571 for (int i = 0; i < node->get_child_count(); ++i) {
572 _iterate_scene(node->get_child(i), selection, cur_counter);
573 }
574}
575
576void RenameDialog::rename() {
577 // Editor selection is not ordered via scene tree. Instead iterate
578 // over scene tree until all selected nodes are found in order.
579
580 EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
581 Array selected_node_list = editor_selection->get_selected_nodes();
582 Node *root_node = SceneTree::get_singleton()->get_edited_scene_root();
583
584 global_count = spn_count_start->get_value();
585 to_rename.clear();
586
587 // Forward recursive as opposed to the actual renaming.
588 _iterate_scene(root_node, selected_node_list, &global_count);
589
590 if (!to_rename.is_empty()) {
591 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
592 undo_redo->create_action(TTR("Batch Rename"), UndoRedo::MERGE_DISABLE, root_node, true);
593
594 // Make sure to iterate reversed so that child nodes will find parents.
595 for (int i = to_rename.size() - 1; i >= 0; --i) {
596 Node *n = root_node->get_node(to_rename[i].first);
597 const String &new_name = to_rename[i].second;
598
599 if (!n) {
600 ERR_PRINT("Skipping missing node: " + to_rename[i].first.get_concatenated_subnames());
601 continue;
602 }
603
604 scene_tree_editor->call("_rename_node", n, new_name);
605 }
606
607 undo_redo->commit_action();
608 }
609}
610
611void RenameDialog::reset() {
612 lock_preview_update = true;
613
614 lne_prefix->clear();
615 lne_suffix->clear();
616 lne_search->clear();
617 lne_replace->clear();
618
619 cbut_substitute->set_pressed(false);
620 cbut_regex->set_pressed(false);
621 cbut_process->set_pressed(false);
622
623 chk_per_level_counter->set_pressed(true);
624
625 spn_count_start->set_value(1);
626 spn_count_step->set_value(1);
627 spn_count_padding->set_value(1);
628
629 opt_style->select(0);
630 opt_case->select(0);
631
632 lock_preview_update = false;
633 _update_preview();
634}
635
636bool RenameDialog::_is_main_field(LineEdit *line_edit) {
637 return line_edit &&
638 (line_edit == lne_search || line_edit == lne_replace || line_edit == lne_prefix || line_edit == lne_suffix);
639}
640
641void RenameDialog::_insert_text(String text) {
642 LineEdit *focus_owner = Object::cast_to<LineEdit>(get_viewport()->gui_get_focus_owner());
643
644 if (_is_main_field(focus_owner)) {
645 focus_owner->selection_delete();
646 focus_owner->insert_text_at_caret(text);
647 _update_preview();
648 }
649}
650
651void RenameDialog::_features_toggled(bool pressed) {
652 if (pressed) {
653 tabc_features->show();
654 } else {
655 tabc_features->hide();
656 }
657
658 // Adjust to minimum size in y
659 Size2i new_size = get_size();
660 new_size.y = 0;
661 set_size(new_size);
662}
663
664#endif // MODULE_REGEX_ENABLED
665