1/**************************************************************************/
2/* project_settings_editor.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 "project_settings_editor.h"
32
33#include "core/config/project_settings.h"
34#include "editor/editor_log.h"
35#include "editor/editor_node.h"
36#include "editor/editor_scale.h"
37#include "editor/editor_settings.h"
38#include "editor/editor_string_names.h"
39#include "editor/editor_undo_redo_manager.h"
40#include "editor/export/editor_export.h"
41#include "scene/gui/check_button.h"
42#include "servers/movie_writer/movie_writer.h"
43
44ProjectSettingsEditor *ProjectSettingsEditor::singleton = nullptr;
45
46void ProjectSettingsEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) {
47 localization_editor->connect_filesystem_dock_signals(p_fs_dock);
48}
49
50void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) {
51 // Restore valid window bounds or pop up at default size.
52 Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "project_settings", Rect2());
53 if (saved_size != Rect2()) {
54 popup(saved_size);
55 } else {
56 popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
57 }
58
59 _add_feature_overrides();
60 general_settings_inspector->update_category_list();
61 set_process_shortcut_input(true);
62
63 localization_editor->update_translations();
64 autoload_settings->update_autoload();
65 plugin_settings->update_plugins();
66 import_defaults_editor->clear();
67
68 if (p_clear_filter) {
69 search_box->clear();
70 }
71}
72
73void ProjectSettingsEditor::queue_save() {
74 timer->start();
75}
76
77void ProjectSettingsEditor::set_plugins_page() {
78 tab_container->set_current_tab(tab_container->get_tab_idx_from_control(plugin_settings));
79}
80
81void ProjectSettingsEditor::set_general_page(const String &p_category) {
82 tab_container->set_current_tab(tab_container->get_tab_idx_from_control(general_editor));
83 general_settings_inspector->set_current_section(p_category);
84}
85
86void ProjectSettingsEditor::update_plugins() {
87 plugin_settings->update_plugins();
88}
89
90void ProjectSettingsEditor::_setting_edited(const String &p_name) {
91 queue_save();
92}
93
94void ProjectSettingsEditor::_update_advanced(bool p_is_advanced) {
95 custom_properties->set_visible(p_is_advanced);
96}
97
98void ProjectSettingsEditor::_advanced_toggled(bool p_button_pressed) {
99 EditorSettings::get_singleton()->set_project_metadata("project_settings", "advanced_mode", p_button_pressed);
100 _update_advanced(p_button_pressed);
101 general_settings_inspector->set_restrict_to_basic_settings(!p_button_pressed);
102}
103
104void ProjectSettingsEditor::_setting_selected(const String &p_path) {
105 if (p_path.is_empty()) {
106 return;
107 }
108
109 property_box->set_text(general_settings_inspector->get_current_section() + "/" + p_path);
110
111 _update_property_box(); // set_text doesn't trigger text_changed
112}
113
114void ProjectSettingsEditor::_add_setting() {
115 String setting = _get_setting_name();
116
117 // Initialize the property with the default value for the given type.
118 Callable::CallError ce;
119 Variant value;
120 Variant::construct(Variant::Type(type_box->get_selected_id()), value, nullptr, 0, ce);
121
122 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
123 undo_redo->create_action(TTR("Add Project Setting"));
124 undo_redo->add_do_property(ps, setting, value);
125 undo_redo->add_undo_property(ps, setting, ps->has_setting(setting) ? ps->get(setting) : Variant());
126
127 undo_redo->add_do_method(general_settings_inspector, "update_category_list");
128 undo_redo->add_undo_method(general_settings_inspector, "update_category_list");
129 undo_redo->add_do_method(this, "queue_save");
130 undo_redo->add_undo_method(this, "queue_save");
131 undo_redo->commit_action();
132
133 general_settings_inspector->set_current_section(setting.get_slice("/", 1));
134 add_button->release_focus();
135}
136
137void ProjectSettingsEditor::_delete_setting() {
138 String setting = _get_setting_name();
139 Variant value = ps->get(setting);
140 int order = ps->get_order(setting);
141
142 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
143 undo_redo->create_action(TTR("Delete Item"));
144
145 undo_redo->add_do_method(ps, "clear", setting);
146 undo_redo->add_undo_method(ps, "set", setting, value);
147 undo_redo->add_undo_method(ps, "set_order", setting, order);
148
149 undo_redo->add_do_method(general_settings_inspector, "update_category_list");
150 undo_redo->add_undo_method(general_settings_inspector, "update_category_list");
151 undo_redo->add_do_method(this, "queue_save");
152 undo_redo->add_undo_method(this, "queue_save");
153
154 undo_redo->commit_action();
155
156 property_box->clear();
157 del_button->release_focus();
158}
159
160void ProjectSettingsEditor::_property_box_changed(const String &p_text) {
161 _update_property_box();
162}
163
164void ProjectSettingsEditor::_feature_selected(int p_index) {
165 Vector<String> t = property_box->get_text().strip_edges().split(".", true, 1);
166 const String feature = p_index ? "." + feature_box->get_item_text(p_index) : "";
167 property_box->set_text(t[0] + feature);
168 _update_property_box();
169}
170
171void ProjectSettingsEditor::_update_property_box() {
172 const String setting = _get_setting_name();
173 const Vector<String> t = setting.split(".", true, 1);
174 const String name = t[0];
175 const String feature = (t.size() == 2) ? t[1] : "";
176 bool feature_invalid = (t.size() == 2) && (t[1].is_empty());
177
178 add_button->set_disabled(true);
179 del_button->set_disabled(true);
180
181 if (!feature.is_empty()) {
182 feature_invalid = true;
183 for (int i = 1; i < feature_box->get_item_count(); i++) {
184 if (feature == feature_box->get_item_text(i)) {
185 feature_invalid = false;
186 feature_box->select(i);
187 break;
188 }
189 }
190 }
191
192 if (feature.is_empty() || feature_invalid) {
193 feature_box->select(0);
194 }
195
196 if (property_box->get_text().is_empty()) {
197 return;
198 }
199
200 if (ps->has_setting(setting)) {
201 del_button->set_disabled(ps->is_builtin_setting(setting));
202 _select_type(ps->get_setting(setting).get_type());
203 } else {
204 if (ps->has_setting(name)) {
205 _select_type(ps->get_setting(name).get_type());
206 } else {
207 type_box->select(0);
208 }
209
210 if (feature_invalid) {
211 return;
212 }
213
214 const Vector<String> names = name.split("/");
215 for (int i = 0; i < names.size(); i++) {
216 if (!names[i].is_valid_identifier()) {
217 return;
218 }
219 }
220
221 add_button->set_disabled(false);
222 }
223}
224
225void ProjectSettingsEditor::_select_type(Variant::Type p_type) {
226 type_box->select(type_box->get_item_index(p_type));
227}
228
229void ProjectSettingsEditor::shortcut_input(const Ref<InputEvent> &p_event) {
230 ERR_FAIL_COND(p_event.is_null());
231 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
232
233 const Ref<InputEventKey> k = p_event;
234 if (k.is_valid() && k->is_pressed()) {
235 bool handled = false;
236
237 if (ED_IS_SHORTCUT("ui_undo", p_event)) {
238 String action = undo_redo->get_current_action_name();
239 if (!action.is_empty()) {
240 EditorNode::get_log()->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
241 }
242 undo_redo->undo();
243 handled = true;
244 }
245
246 if (ED_IS_SHORTCUT("ui_redo", p_event)) {
247 undo_redo->redo();
248 String action = undo_redo->get_current_action_name();
249 if (!action.is_empty()) {
250 EditorNode::get_log()->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
251 }
252 handled = true;
253 }
254
255 if (k->is_match(InputEventKey::create_reference(KeyModifierMask::CMD_OR_CTRL | Key::F))) {
256 search_box->grab_focus();
257 search_box->select_all();
258 handled = true;
259 }
260
261 if (handled) {
262 set_input_as_handled();
263 }
264 }
265}
266
267String ProjectSettingsEditor::_get_setting_name() const {
268 String name = property_box->get_text().strip_edges();
269 if (!name.contains("/")) {
270 name = "global/" + name;
271 }
272 return name;
273}
274
275void ProjectSettingsEditor::_add_feature_overrides() {
276 HashSet<String> presets;
277
278 presets.insert("bptc");
279 presets.insert("s3tc");
280 presets.insert("etc");
281 presets.insert("etc2");
282 presets.insert("editor");
283 presets.insert("template_debug");
284 presets.insert("template_release");
285 presets.insert("debug");
286 presets.insert("release");
287 presets.insert("template");
288 presets.insert("double");
289 presets.insert("single");
290 presets.insert("32");
291 presets.insert("64");
292 presets.insert("movie");
293
294 EditorExport *ee = EditorExport::get_singleton();
295
296 for (int i = 0; i < ee->get_export_platform_count(); i++) {
297 List<String> p;
298 ee->get_export_platform(i)->get_platform_features(&p);
299 for (const String &E : p) {
300 presets.insert(E);
301 }
302 }
303
304 for (int i = 0; i < ee->get_export_preset_count(); i++) {
305 List<String> p;
306 ee->get_export_preset(i)->get_platform()->get_preset_features(ee->get_export_preset(i), &p);
307 for (const String &E : p) {
308 presets.insert(E);
309 }
310
311 String custom = ee->get_export_preset(i)->get_custom_features();
312 Vector<String> custom_list = custom.split(",");
313 for (int j = 0; j < custom_list.size(); j++) {
314 String f = custom_list[j].strip_edges();
315 if (!f.is_empty()) {
316 presets.insert(f);
317 }
318 }
319 }
320
321 feature_box->clear();
322 feature_box->add_item(TTR("(All)"), 0); // So it is always on top.
323 int id = 1;
324 for (const String &E : presets) {
325 feature_box->add_item(E, id++);
326 }
327}
328
329void ProjectSettingsEditor::_editor_restart() {
330 ProjectSettings::get_singleton()->save();
331 EditorNode::get_singleton()->save_all_scenes();
332 EditorNode::get_singleton()->restart_editor();
333}
334
335void ProjectSettingsEditor::_editor_restart_request() {
336 restart_container->show();
337}
338
339void ProjectSettingsEditor::_editor_restart_close() {
340 restart_container->hide();
341}
342
343void ProjectSettingsEditor::_action_added(const String &p_name) {
344 String name = "input/" + p_name;
345
346 ERR_FAIL_COND_MSG(ProjectSettings::get_singleton()->has_setting(name),
347 "An action with this name already exists.");
348
349 Dictionary action;
350 action["events"] = Array();
351 action["deadzone"] = 0.5f;
352
353 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
354 undo_redo->create_action(TTR("Add Input Action"));
355 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", name, action);
356 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name);
357
358 undo_redo->add_do_method(this, "_update_action_map_editor");
359 undo_redo->add_undo_method(this, "_update_action_map_editor");
360 undo_redo->add_do_method(this, "queue_save");
361 undo_redo->add_undo_method(this, "queue_save");
362 undo_redo->commit_action();
363}
364
365void ProjectSettingsEditor::_action_edited(const String &p_name, const Dictionary &p_action) {
366 const String property_name = "input/" + p_name;
367 Dictionary old_val = GLOBAL_GET(property_name);
368
369 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
370 if (old_val["deadzone"] != p_action["deadzone"]) {
371 // Deadzone Changed
372 undo_redo->create_action(TTR("Change Action deadzone"));
373 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", property_name, p_action);
374 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
375
376 } else {
377 // Events changed
378 undo_redo->create_action(TTR("Change Input Action Event(s)"));
379 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", property_name, p_action);
380 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
381 }
382
383 undo_redo->add_do_method(this, "_update_action_map_editor");
384 undo_redo->add_undo_method(this, "_update_action_map_editor");
385 undo_redo->add_do_method(this, "queue_save");
386 undo_redo->add_undo_method(this, "queue_save");
387 undo_redo->commit_action();
388}
389
390void ProjectSettingsEditor::_action_removed(const String &p_name) {
391 const String property_name = "input/" + p_name;
392
393 Dictionary old_val = GLOBAL_GET(property_name);
394 int order = ProjectSettings::get_singleton()->get_order(property_name);
395
396 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
397 undo_redo->create_action(TTR("Erase Input Action"));
398 undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", property_name);
399 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
400 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", property_name, order);
401
402 undo_redo->add_do_method(this, "_update_action_map_editor");
403 undo_redo->add_undo_method(this, "_update_action_map_editor");
404 undo_redo->add_do_method(this, "queue_save");
405 undo_redo->add_undo_method(this, "queue_save");
406 undo_redo->commit_action();
407}
408
409void ProjectSettingsEditor::_action_renamed(const String &p_old_name, const String &p_new_name) {
410 const String old_property_name = "input/" + p_old_name;
411 const String new_property_name = "input/" + p_new_name;
412
413 ERR_FAIL_COND_MSG(ProjectSettings::get_singleton()->has_setting(new_property_name),
414 "An action with this name already exists.");
415
416 int order = ProjectSettings::get_singleton()->get_order(old_property_name);
417 Dictionary action = GLOBAL_GET(old_property_name);
418
419 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
420 undo_redo->create_action(TTR("Rename Input Action"));
421 // Do: clear old, set new
422 undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", old_property_name);
423 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", new_property_name, action);
424 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", new_property_name, order);
425 // Undo: clear new, set old
426 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", new_property_name);
427 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", old_property_name, action);
428 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", old_property_name, order);
429
430 undo_redo->add_do_method(this, "_update_action_map_editor");
431 undo_redo->add_undo_method(this, "_update_action_map_editor");
432 undo_redo->add_do_method(this, "queue_save");
433 undo_redo->add_undo_method(this, "queue_save");
434 undo_redo->commit_action();
435}
436
437void ProjectSettingsEditor::_action_reordered(const String &p_action_name, const String &p_relative_to, bool p_before) {
438 const String action_name = "input/" + p_action_name;
439 const String target_name = "input/" + p_relative_to;
440
441 // It is much easier to rebuild the custom "input" properties rather than messing around with the "order" values of them.
442 Variant action_value = ps->get(action_name);
443 Variant target_value = ps->get(target_name);
444
445 List<PropertyInfo> props;
446 HashMap<String, Variant> action_values;
447 ProjectSettings::get_singleton()->get_property_list(&props);
448
449 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
450 undo_redo->create_action(TTR("Update Input Action Order"));
451
452 for (const PropertyInfo &prop : props) {
453 // Skip builtins and non-inputs
454 // Order matters here, checking for "input/" filters out properties that aren't settings and produce errors in is_builtin_setting().
455 if (!prop.name.begins_with("input/") || ProjectSettings::get_singleton()->is_builtin_setting(prop.name)) {
456 continue;
457 }
458
459 action_values.insert(prop.name, ps->get(prop.name));
460
461 undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", prop.name);
462 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", prop.name);
463 }
464
465 for (const KeyValue<String, Variant> &E : action_values) {
466 String name = E.key;
467 const Variant &value = E.value;
468
469 if (name == target_name) {
470 if (p_before) {
471 // Insert before target
472 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
473 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
474
475 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
476 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
477 } else {
478 // Insert after target
479 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
480 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
481
482 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
483 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
484 }
485
486 } else if (name != action_name) {
487 undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", name, value);
488 undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", name, value);
489 }
490 }
491
492 undo_redo->add_do_method(this, "_update_action_map_editor");
493 undo_redo->add_undo_method(this, "_update_action_map_editor");
494 undo_redo->add_do_method(this, "queue_save");
495 undo_redo->add_undo_method(this, "queue_save");
496 undo_redo->commit_action();
497}
498
499void ProjectSettingsEditor::_update_action_map_editor() {
500 Vector<ActionMapEditor::ActionInfo> actions;
501
502 List<PropertyInfo> props;
503 ProjectSettings::get_singleton()->get_property_list(&props);
504
505 const Ref<Texture2D> builtin_icon = get_editor_theme_icon(SNAME("PinPressed"));
506 for (const PropertyInfo &E : props) {
507 const String property_name = E.name;
508
509 if (!property_name.begins_with("input/")) {
510 continue;
511 }
512
513 // Strip the "input/" from the left.
514 String display_name = property_name.substr(String("input/").size() - 1);
515 Dictionary action = GLOBAL_GET(property_name);
516
517 ActionMapEditor::ActionInfo action_info;
518 action_info.action = action;
519 action_info.editable = true;
520 action_info.name = display_name;
521
522 const bool is_builtin_input = ProjectSettings::get_singleton()->get_input_presets().find(property_name) != nullptr;
523 if (is_builtin_input) {
524 action_info.editable = false;
525 action_info.icon = builtin_icon;
526 action_info.has_initial = true;
527 action_info.action_initial = ProjectSettings::get_singleton()->property_get_revert(property_name);
528 }
529
530 actions.push_back(action_info);
531 }
532
533 action_map_editor->update_action_list(actions);
534}
535
536void ProjectSettingsEditor::_update_theme() {
537 search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
538 restart_close_button->set_icon(get_editor_theme_icon(SNAME("Close")));
539 restart_container->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
540 restart_icon->set_texture(get_editor_theme_icon(SNAME("StatusWarning")));
541 restart_label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
542
543 type_box->clear();
544 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
545 if (i == Variant::NIL || i == Variant::OBJECT || i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
546 // These types can't be serialized properly, so skip them.
547 continue;
548 }
549 String type = Variant::get_type_name(Variant::Type(i));
550 type_box->add_icon_item(get_editor_theme_icon(type), type, i);
551 }
552}
553
554void ProjectSettingsEditor::_input_filter_focused() {
555 set_close_on_escape(false);
556}
557
558void ProjectSettingsEditor::_input_filter_unfocused() {
559 set_close_on_escape(true);
560}
561
562void ProjectSettingsEditor::_notification(int p_what) {
563 switch (p_what) {
564 case NOTIFICATION_VISIBILITY_CHANGED: {
565 if (!is_visible()) {
566 EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", Rect2(get_position(), get_size()));
567 }
568 } break;
569
570 case NOTIFICATION_ENTER_TREE: {
571 general_settings_inspector->edit(ps);
572 _update_action_map_editor();
573 _update_theme();
574 } break;
575
576 case NOTIFICATION_THEME_CHANGED: {
577 _update_theme();
578 } break;
579 }
580}
581
582void ProjectSettingsEditor::_bind_methods() {
583 ClassDB::bind_method(D_METHOD("queue_save"), &ProjectSettingsEditor::queue_save);
584
585 ClassDB::bind_method(D_METHOD("_update_action_map_editor"), &ProjectSettingsEditor::_update_action_map_editor);
586}
587
588ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
589 singleton = this;
590 set_title(TTR("Project Settings (project.godot)"));
591 set_clamp_to_embedder(true);
592
593 ps = ProjectSettings::get_singleton();
594 data = p_data;
595
596 tab_container = memnew(TabContainer);
597 tab_container->set_use_hidden_tabs_for_min_size(true);
598 tab_container->set_theme_type_variation("TabContainerOdd");
599 add_child(tab_container);
600
601 general_editor = memnew(VBoxContainer);
602 general_editor->set_name(TTR("General"));
603 general_editor->set_alignment(BoxContainer::ALIGNMENT_BEGIN);
604 general_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
605 tab_container->add_child(general_editor);
606
607 HBoxContainer *search_bar = memnew(HBoxContainer);
608 general_editor->add_child(search_bar);
609
610 search_box = memnew(LineEdit);
611 search_box->set_placeholder(TTR("Filter Settings"));
612 search_box->set_clear_button_enabled(true);
613 search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
614 search_bar->add_child(search_box);
615
616 advanced = memnew(CheckButton);
617 advanced->set_text(TTR("Advanced Settings"));
618 advanced->connect("toggled", callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
619 search_bar->add_child(advanced);
620
621 custom_properties = memnew(HBoxContainer);
622 general_editor->add_child(custom_properties);
623
624 property_box = memnew(LineEdit);
625 property_box->set_placeholder(TTR("Select a Setting or Type its Name"));
626 property_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
627 property_box->connect("text_changed", callable_mp(this, &ProjectSettingsEditor::_property_box_changed));
628 custom_properties->add_child(property_box);
629
630 feature_box = memnew(OptionButton);
631 feature_box->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
632 feature_box->connect("item_selected", callable_mp(this, &ProjectSettingsEditor::_feature_selected));
633 custom_properties->add_child(feature_box);
634
635 type_box = memnew(OptionButton);
636 type_box->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
637 custom_properties->add_child(type_box);
638
639 add_button = memnew(Button);
640 add_button->set_text(TTR("Add"));
641 add_button->set_disabled(true);
642 add_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_add_setting));
643 custom_properties->add_child(add_button);
644
645 del_button = memnew(Button);
646 del_button->set_text(TTR("Delete"));
647 del_button->set_disabled(true);
648 del_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_delete_setting));
649 custom_properties->add_child(del_button);
650
651 general_settings_inspector = memnew(SectionedInspector);
652 general_settings_inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL);
653 general_settings_inspector->register_search_box(search_box);
654 general_settings_inspector->get_inspector()->set_use_filter(true);
655 general_settings_inspector->get_inspector()->connect("property_selected", callable_mp(this, &ProjectSettingsEditor::_setting_selected));
656 general_settings_inspector->get_inspector()->connect("property_edited", callable_mp(this, &ProjectSettingsEditor::_setting_edited));
657 general_settings_inspector->get_inspector()->connect("restart_requested", callable_mp(this, &ProjectSettingsEditor::_editor_restart_request));
658 general_editor->add_child(general_settings_inspector);
659
660 restart_container = memnew(PanelContainer);
661 general_editor->add_child(restart_container);
662
663 HBoxContainer *restart_hb = memnew(HBoxContainer);
664 restart_container->hide();
665 restart_container->add_child(restart_hb);
666
667 restart_icon = memnew(TextureRect);
668 restart_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
669 restart_hb->add_child(restart_icon);
670
671 restart_label = memnew(Label);
672 restart_label->set_text(TTR("Changed settings will be applied to the editor after restarting."));
673 restart_hb->add_child(restart_label);
674 restart_hb->add_spacer();
675
676 Button *restart_button = memnew(Button);
677 restart_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_editor_restart));
678 restart_hb->add_child(restart_button);
679 restart_button->set_text(TTR("Save & Restart"));
680
681 restart_close_button = memnew(Button);
682 restart_close_button->set_flat(true);
683 restart_close_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_editor_restart_close));
684 restart_hb->add_child(restart_close_button);
685
686 action_map_editor = memnew(ActionMapEditor);
687 action_map_editor->set_name(TTR("Input Map"));
688 action_map_editor->connect("action_added", callable_mp(this, &ProjectSettingsEditor::_action_added));
689 action_map_editor->connect("action_edited", callable_mp(this, &ProjectSettingsEditor::_action_edited));
690 action_map_editor->connect("action_removed", callable_mp(this, &ProjectSettingsEditor::_action_removed));
691 action_map_editor->connect("action_renamed", callable_mp(this, &ProjectSettingsEditor::_action_renamed));
692 action_map_editor->connect("action_reordered", callable_mp(this, &ProjectSettingsEditor::_action_reordered));
693 action_map_editor->connect(SNAME("filter_focused"), callable_mp(this, &ProjectSettingsEditor::_input_filter_focused));
694 action_map_editor->connect(SNAME("filter_unfocused"), callable_mp(this, &ProjectSettingsEditor::_input_filter_unfocused));
695 tab_container->add_child(action_map_editor);
696
697 localization_editor = memnew(LocalizationEditor);
698 localization_editor->set_name(TTR("Localization"));
699 localization_editor->connect("localization_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
700 tab_container->add_child(localization_editor);
701
702 autoload_settings = memnew(EditorAutoloadSettings);
703 autoload_settings->set_name(TTR("Autoload"));
704 autoload_settings->connect("autoload_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
705 tab_container->add_child(autoload_settings);
706
707 shaders_global_shader_uniforms_editor = memnew(ShaderGlobalsEditor);
708 shaders_global_shader_uniforms_editor->set_name(TTR("Shader Globals"));
709 shaders_global_shader_uniforms_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
710 tab_container->add_child(shaders_global_shader_uniforms_editor);
711
712 plugin_settings = memnew(EditorPluginSettings);
713 plugin_settings->set_name(TTR("Plugins"));
714 tab_container->add_child(plugin_settings);
715
716 timer = memnew(Timer);
717 timer->set_wait_time(1.5);
718 timer->connect("timeout", callable_mp(ps, &ProjectSettings::save));
719 timer->set_one_shot(true);
720 add_child(timer);
721
722 set_ok_button_text(TTR("Close"));
723 set_hide_on_ok(true);
724
725 bool use_advanced = EditorSettings::get_singleton()->get_project_metadata("project_settings", "advanced_mode", false);
726
727 if (use_advanced) {
728 advanced->set_pressed(true);
729 }
730
731 _update_advanced(use_advanced);
732 general_settings_inspector->set_restrict_to_basic_settings(!use_advanced);
733
734 import_defaults_editor = memnew(ImportDefaultsEditor);
735 import_defaults_editor->set_name(TTR("Import Defaults"));
736 tab_container->add_child(import_defaults_editor);
737
738 MovieWriter::set_extensions_hint(); // ensure extensions are properly displayed.
739}
740