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 | |
55 | RenameDialog::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 | |
341 | void RenameDialog::_bind_methods() { |
342 | ClassDB::bind_method("rename" , &RenameDialog::rename); |
343 | } |
344 | |
345 | void 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 | |
365 | void RenameDialog::() { |
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 | |
380 | void RenameDialog::_update_preview_int(int new_value) { |
381 | _update_preview(); |
382 | } |
383 | |
384 | void 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 | |
411 | String 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 | |
440 | String 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 | |
472 | 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) { |
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 | |
494 | String 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 | |
500 | String 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 | |
546 | void 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 | |
576 | void 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 | |
611 | void 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 | |
636 | bool 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 | |
641 | void 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 | |
651 | void 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 | |