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