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