rename_dialog.cpp 24 KB

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