summaryrefslogtreecommitdiff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/code_editor.cpp50
-rw-r--r--editor/create_dialog.cpp21
-rw-r--r--editor/editor_data.cpp25
-rw-r--r--editor/editor_data.h1
-rw-r--r--editor/editor_help.cpp27
-rw-r--r--editor/editor_help.h1
-rw-r--r--editor/editor_node.cpp42
-rw-r--r--editor/editor_themes.cpp4
-rw-r--r--editor/filesystem_dock.cpp90
-rw-r--r--editor/filesystem_dock.h5
-rw-r--r--editor/find_in_files.cpp829
-rw-r--r--editor/find_in_files.h184
-rw-r--r--editor/import/editor_import_collada.cpp2
-rw-r--r--editor/import/editor_scene_importer_gltf.cpp15
-rw-r--r--editor/import/resource_importer_wav.cpp28
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp6
-rw-r--r--editor/plugins/animation_tree_editor_plugin.cpp2
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/item_list_editor_plugin.cpp28
-rw-r--r--editor/plugins/item_list_editor_plugin.h4
-rw-r--r--editor/plugins/mesh_instance_editor_plugin.cpp4
-rw-r--r--editor/plugins/script_editor_plugin.cpp194
-rw-r--r--editor/plugins/script_editor_plugin.h25
-rw-r--r--editor/plugins/script_text_editor.cpp69
-rw-r--r--editor/plugins/script_text_editor.h9
-rw-r--r--editor/plugins/shader_graph_editor_plugin.cpp2
-rw-r--r--editor/plugins/spatial_editor_plugin.cpp26
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp2
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp10
-rw-r--r--editor/plugins/theme_editor_plugin.cpp4
-rw-r--r--editor/plugins/tile_map_editor_plugin.cpp58
-rw-r--r--editor/plugins/tile_map_editor_plugin.h2
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp6
-rw-r--r--editor/project_settings_editor.cpp45
-rw-r--r--editor/project_settings_editor.h6
-rw-r--r--editor/property_editor.cpp53
-rw-r--r--editor/scene_tree_dock.cpp76
-rw-r--r--editor/settings_config_dialog.cpp16
39 files changed, 1732 insertions, 243 deletions
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 9ae9ab1501..4c7f2f53cc 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -212,35 +212,39 @@ void FindReplaceBar::_replace_all() {
text_edit->begin_complex_operation();
- while (search_next()) {
-
- // replace area
- Point2i match_from(result_line, result_col);
- Point2i match_to(result_line, result_col + search_text_len);
-
- if (match_from < prev_match)
- break; // done
+ if (search_current()) {
+ do {
+ // replace area
+ Point2i match_from(result_line, result_col);
+ Point2i match_to(result_line, result_col + search_text_len);
+
+ if (match_from < prev_match) {
+ break; // done
+ }
- prev_match = Point2i(result_line, result_col + replace_text.length());
+ prev_match = Point2i(result_line, result_col + replace_text.length());
- text_edit->unfold_line(result_line);
- text_edit->select(result_line, result_col, result_line, match_to.y);
+ text_edit->unfold_line(result_line);
+ text_edit->select(result_line, result_col, result_line, match_to.y);
- if (selection_enabled && is_selection_only()) {
+ if (selection_enabled && is_selection_only()) {
+ if (match_from < selection_begin || match_to > selection_end) {
+ continue;
+ }
- if (match_from < selection_begin || match_to > selection_end)
- continue;
+ // replace but adjust selection bounds
+ text_edit->insert_text_at_cursor(replace_text);
+ if (match_to.x == selection_end.x) {
+ selection_end.y += replace_text.length() - search_text_len;
+ }
- // replace but adjust selection bounds
- text_edit->insert_text_at_cursor(replace_text);
- if (match_to.x == selection_end.x)
- selection_end.y += replace_text.length() - search_text_len;
- } else {
- // just replace
- text_edit->insert_text_at_cursor(replace_text);
- }
+ } else {
+ // just replace
+ text_edit->insert_text_at_cursor(replace_text);
+ }
- rc++;
+ rc++;
+ } while (search_next());
}
text_edit->end_complex_operation();
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 13ef6e9e0e..78fb35e354 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -433,26 +433,7 @@ Object *CreateDialog::instance_selected() {
custom = md;
if (custom != String()) {
- if (EditorNode::get_editor_data().get_custom_types().has(custom)) {
-
- for (int i = 0; i < EditorNode::get_editor_data().get_custom_types()[custom].size(); i++) {
- if (EditorNode::get_editor_data().get_custom_types()[custom][i].name == selected->get_text(0)) {
- Ref<Texture> icon = EditorNode::get_editor_data().get_custom_types()[custom][i].icon;
- Ref<Script> script = EditorNode::get_editor_data().get_custom_types()[custom][i].script;
- String name = selected->get_text(0);
-
- Object *ob = ClassDB::instance(custom);
- ERR_FAIL_COND_V(!ob, NULL);
- if (ob->is_class("Node")) {
- ob->call("set_name", name);
- }
- ob->set_script(script.get_ref_ptr());
- if (icon.is_valid())
- ob->set_meta("_editor_icon", icon);
- return ob;
- }
- }
- }
+ return EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom);
} else {
return ClassDB::instance(selected->get_text(0));
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 95ed40d889..ef9265ecd2 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -452,6 +452,31 @@ void EditorData::add_custom_type(const String &p_type, const String &p_inherits,
custom_types[p_inherits].push_back(ct);
}
+Object *EditorData::instance_custom_type(const String &p_type, const String &p_inherits) {
+
+ if (get_custom_types().has(p_inherits)) {
+
+ for (int i = 0; i < get_custom_types()[p_inherits].size(); i++) {
+ if (get_custom_types()[p_inherits][i].name == p_type) {
+ Ref<Texture> icon = get_custom_types()[p_inherits][i].icon;
+ Ref<Script> script = get_custom_types()[p_inherits][i].script;
+
+ Object *ob = ClassDB::instance(p_inherits);
+ ERR_FAIL_COND_V(!ob, NULL);
+ if (ob->is_class("Node")) {
+ ob->call("set_name", p_type);
+ }
+ ob->set_script(script.get_ref_ptr());
+ if (icon.is_valid())
+ ob->set_meta("_editor_icon", icon);
+ return ob;
+ }
+ }
+ }
+
+ return NULL;
+}
+
void EditorData::remove_custom_type(const String &p_type) {
for (Map<String, Vector<CustomType> >::Element *E = custom_types.front(); E; E = E->next()) {
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 844145853d..1a498a6150 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -171,6 +171,7 @@ public:
void restore_editor_global_states();
void add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture> &p_icon);
+ Object *instance_custom_type(const String &p_type, const String &p_inherits);
void remove_custom_type(const String &p_type);
const Map<String, Vector<CustomType> > &get_custom_types() const { return custom_types; }
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 7f76cf1af2..f3be02a8c7 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -1172,7 +1172,12 @@ Error EditorHelp::_goto_desc(const String &p_class, int p_vscr) {
class_desc->push_indent(1);
Vector<DocData::ConstantDoc> enum_list = E->get();
+ Map<String, int> enumValuesContainer;
+ int enumStartingLine = enum_line[E->key()];
+
for (int i = 0; i < enum_list.size(); i++) {
+ if (cd.name == "@GlobalScope")
+ enumValuesContainer[enum_list[i].name] = enumStartingLine;
class_desc->push_font(doc_code_font);
class_desc->push_color(headline_color);
@@ -1200,6 +1205,9 @@ Error EditorHelp::_goto_desc(const String &p_class, int p_vscr) {
class_desc->add_newline();
}
+ if (cd.name == "@GlobalScope")
+ enum_values_line[E->key()] = enumValuesContainer;
+
class_desc->pop();
class_desc->add_newline();
@@ -1485,21 +1493,32 @@ void EditorHelp::_help_callback(const String &p_topic) {
if (method_line.has(name))
line = method_line[name];
} else if (what == "class_property") {
-
if (property_line.has(name))
line = property_line[name];
} else if (what == "class_enum") {
-
if (enum_line.has(name))
line = enum_line[name];
} else if (what == "class_theme_item") {
-
if (theme_property_line.has(name))
line = theme_property_line[name];
} else if (what == "class_constant") {
-
if (constant_line.has(name))
line = constant_line[name];
+ } else if (what == "class_global") {
+ if (constant_line.has(name))
+ line = constant_line[name];
+ else {
+ Map<String, Map<String, int> >::Element *iter = enum_values_line.front();
+ while (true) {
+ if (iter->value().has(name)) {
+ line = iter->value()[name];
+ break;
+ } else if (iter == enum_values_line.back())
+ break;
+ else
+ iter = iter->next();
+ }
+ }
}
class_desc->call_deferred("scroll_to_line", line);
diff --git a/editor/editor_help.h b/editor/editor_help.h
index aa84aa611f..0f93e1b55b 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -152,6 +152,7 @@ class EditorHelp : public VBoxContainer {
Map<String, int> theme_property_line;
Map<String, int> constant_line;
Map<String, int> enum_line;
+ Map<String, Map<String, int> > enum_values_line;
int description_line;
RichTextLabel *class_desc;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 7936cf446f..141769b16a 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -470,24 +470,28 @@ void EditorNode::_fs_changed() {
}
if (preset.is_null()) {
String err = "Unknown export preset: " + export_defer.preset;
- ERR_PRINT(err.utf8().get_data());
+ ERR_PRINTS(err);
} else {
Ref<EditorExportPlatform> platform = preset->get_platform();
if (platform.is_null()) {
String err = "Preset \"" + export_defer.preset + "\" doesn't have a platform.";
- ERR_PRINT(err.utf8().get_data());
+ ERR_PRINTS(err);
} else {
// ensures export_project does not loop infinitely, because notifications may
// come during the export
export_defer.preset = "";
+ Error err;
if (!preset->is_runnable() && (export_defer.path.ends_with(".pck") || export_defer.path.ends_with(".zip"))) {
if (export_defer.path.ends_with(".zip")) {
- platform->save_zip(preset, export_defer.path);
+ err = platform->save_zip(preset, export_defer.path);
} else if (export_defer.path.ends_with(".pck")) {
- platform->save_pack(preset, export_defer.path);
+ err = platform->save_pack(preset, export_defer.path);
}
} else {
- platform->export_project(preset, export_defer.debug, export_defer.path, /*p_flags*/ 0);
+ err = platform->export_project(preset, export_defer.debug, export_defer.path, /*p_flags*/ 0);
+ }
+ if (err != OK) {
+ ERR_PRINTS(vformat(TTR("Project export failed with error code %d."), (int)err));
}
}
}
@@ -1230,29 +1234,25 @@ void EditorNode::_dialog_action(String p_file) {
} break;
case FILE_EXPORT_TILESET: {
- Ref<TileSet> ml;
- if (FileAccess::exists(p_file)) {
- ml = ResourceLoader::load(p_file, "TileSet");
+ Ref<TileSet> tileset;
+ if (FileAccess::exists(p_file) && file_export_lib_merge->is_pressed()) {
+ tileset = ResourceLoader::load(p_file, "TileSet");
- if (ml.is_null()) {
- if (file_export_lib_merge->is_pressed()) {
- current_option = -1;
- accept->get_ok()->set_text(TTR("I see.."));
- accept->set_text(TTR("Can't load TileSet for merging!"));
- accept->popup_centered_minsize();
- return;
- }
- } else if (!file_export_lib_merge->is_pressed()) {
- ml->clear();
+ if (tileset.is_null()) {
+ current_option = -1;
+ accept->get_ok()->set_text(TTR("I see.."));
+ accept->set_text(TTR("Can't load TileSet for merging!"));
+ accept->popup_centered_minsize();
+ return;
}
} else {
- ml = Ref<TileSet>(memnew(TileSet));
+ tileset = Ref<TileSet>(memnew(TileSet));
}
- TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true);
+ TileSetEditor::update_library_file(editor_data.get_edited_scene_root(), tileset, true);
- Error err = ResourceSaver::save(p_file, ml);
+ Error err = ResourceSaver::save(p_file, tileset);
if (err) {
current_option = -1;
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index b10f785a28..3582379e34 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -626,8 +626,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_color("font_color_disabled", "PopupMenu", font_color_disabled);
theme->set_icon("checked", "PopupMenu", theme->get_icon("GuiChecked", "EditorIcons"));
theme->set_icon("unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons"));
- theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiChecked", "EditorIcons"));
- theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiUnchecked", "EditorIcons"));
+ theme->set_icon("radio_checked", "PopupMenu", theme->get_icon("GuiRadioChecked", "EditorIcons"));
+ theme->set_icon("radio_unchecked", "PopupMenu", theme->get_icon("GuiRadioUnchecked", "EditorIcons"));
theme->set_icon("submenu", "PopupMenu", theme->get_icon("ArrowRight", "EditorIcons"));
theme->set_icon("visibility_hidden", "PopupMenu", theme->get_icon("GuiVisibilityHidden", "EditorIcons"));
theme->set_icon("visibility_visible", "PopupMenu", theme->get_icon("GuiVisibilityVisible", "EditorIcons"));
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 9bfa50148f..16223dbb16 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -719,12 +719,13 @@ void FileSystemDock::_push_to_history() {
button_hist_next->set_disabled(history_pos == history.size() - 1);
}
-void FileSystemDock::_get_all_files_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files) const {
+void FileSystemDock::_get_all_items_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files, Vector<String> &folders) const {
if (efsd == NULL)
return;
for (int i = 0; i < efsd->get_subdir_count(); i++) {
- _get_all_files_in_dir(efsd->get_subdir(i), files);
+ folders.push_back(efsd->get_subdir(i)->get_path());
+ _get_all_items_in_dir(efsd->get_subdir(i), files, folders);
}
for (int i = 0; i < efsd->get_file_count(); i++) {
files.push_back(efsd->get_file_path(i));
@@ -746,7 +747,8 @@ void FileSystemDock::_find_remaps(EditorFileSystemDirectory *efsd, const Map<Str
}
}
-void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_renames) const {
+void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_new_path,
+ Map<String, String> &p_file_renames, Map<String, String> &p_folder_renames) const {
//Ensure folder paths end with "/"
String old_path = (p_item.is_file || p_item.path.ends_with("/")) ? p_item.path : (p_item.path + "/");
String new_path = (p_item.is_file || p_new_path.ends_with("/")) ? p_new_path : (p_new_path + "/");
@@ -763,11 +765,13 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_
}
//Build a list of files which will have new paths as a result of this operation
- Vector<String> changed_paths;
+ Vector<String> file_changed_paths;
+ Vector<String> folder_changed_paths;
if (p_item.is_file) {
- changed_paths.push_back(old_path);
+ file_changed_paths.push_back(old_path);
} else {
- _get_all_files_in_dir(EditorFileSystem::get_singleton()->get_filesystem_path(old_path), changed_paths);
+ folder_changed_paths.push_back(old_path);
+ _get_all_items_in_dir(EditorFileSystem::get_singleton()->get_filesystem_path(old_path), file_changed_paths, folder_changed_paths);
}
DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
@@ -783,12 +787,12 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_
}
// update scene if it is open
- for (int i = 0; i < changed_paths.size(); ++i) {
- String new_item_path = p_item.is_file ? new_path : changed_paths[i].replace_first(old_path, new_path);
- if (ResourceLoader::get_resource_type(new_item_path) == "PackedScene" && editor->is_scene_open(changed_paths[i])) {
+ for (int i = 0; i < file_changed_paths.size(); ++i) {
+ String new_item_path = p_item.is_file ? new_path : file_changed_paths[i].replace_first(old_path, new_path);
+ if (ResourceLoader::get_resource_type(new_item_path) == "PackedScene" && editor->is_scene_open(file_changed_paths[i])) {
EditorData *ed = &editor->get_editor_data();
for (int j = 0; j < ed->get_edited_scene_count(); j++) {
- if (ed->get_scene_path(j) == changed_paths[i]) {
+ if (ed->get_scene_path(j) == file_changed_paths[i]) {
ed->get_edited_scene_root(j)->set_filename(new_item_path);
break;
}
@@ -797,9 +801,12 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_
}
//Only treat as a changed dependency if it was successfully moved
- for (int i = 0; i < changed_paths.size(); ++i) {
- p_renames[changed_paths[i]] = changed_paths[i].replace_first(old_path, new_path);
- print_line(" Remap: " + changed_paths[i] + " -> " + p_renames[changed_paths[i]]);
+ for (int i = 0; i < file_changed_paths.size(); ++i) {
+ p_file_renames[file_changed_paths[i]] = file_changed_paths[i].replace_first(old_path, new_path);
+ print_line(" Remap: " + file_changed_paths[i] + " -> " + p_file_renames[file_changed_paths[i]]);
+ }
+ for (int i = 0; i < folder_changed_paths.size(); ++i) {
+ p_folder_renames[folder_changed_paths[i]] = folder_changed_paths[i].replace_first(old_path, new_path);
}
} else {
EditorNode::get_singleton()->add_io_error(TTR("Error moving:") + "\n" + old_path + "\n");
@@ -912,6 +919,25 @@ void FileSystemDock::_update_dependencies_after_move(const Map<String, String> &
}
}
+void FileSystemDock::_update_favorite_dirs_list_after_move(const Map<String, String> &p_renames) const {
+
+ Vector<String> favorite_dirs = EditorSettings::get_singleton()->get_favorite_dirs();
+ Vector<String> new_favorite_dirs;
+
+ for (int i = 0; i < favorite_dirs.size(); i++) {
+ String old_path = favorite_dirs[i] + "/";
+
+ if (p_renames.has(old_path)) {
+ String new_path = p_renames[old_path];
+ new_favorite_dirs.push_back(new_path.substr(0, new_path.length() - 1));
+ } else {
+ new_favorite_dirs.push_back(favorite_dirs[i]);
+ }
+ }
+
+ EditorSettings::get_singleton()->set_favorite_dirs(new_favorite_dirs);
+}
+
void FileSystemDock::_make_dir_confirm() {
String dir_name = make_dir_dialog_text->get_text().strip_edges();
@@ -970,10 +996,12 @@ void FileSystemDock::_rename_operation_confirm() {
}
memdelete(da);
- Map<String, String> renames;
- _try_move_item(to_rename, new_path, renames);
- _update_dependencies_after_move(renames);
- _update_resource_paths_after_move(renames);
+ Map<String, String> file_renames;
+ Map<String, String> folder_renames;
+ _try_move_item(to_rename, new_path, file_renames, folder_renames);
+ _update_dependencies_after_move(file_renames);
+ _update_resource_paths_after_move(file_renames);
+ _update_favorite_dirs_list_after_move(folder_renames);
//Rescan everything
print_line("call rescan!");
@@ -1017,15 +1045,17 @@ void FileSystemDock::_duplicate_operation_confirm() {
void FileSystemDock::_move_operation_confirm(const String &p_to_path) {
- Map<String, String> renames;
+ Map<String, String> file_renames;
+ Map<String, String> folder_renames;
for (int i = 0; i < to_move.size(); i++) {
String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path;
String new_path = p_to_path.plus_file(old_path.get_file());
- _try_move_item(to_move[i], new_path, renames);
+ _try_move_item(to_move[i], new_path, file_renames, folder_renames);
}
- _update_dependencies_after_move(renames);
- _update_resource_paths_after_move(renames);
+ _update_dependencies_after_move(file_renames);
+ _update_resource_paths_after_move(file_renames);
+ _update_favorite_dirs_list_after_move(folder_renames);
print_line("call rescan!");
_rescan();
@@ -1034,8 +1064,19 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path) {
void FileSystemDock::_file_option(int p_option) {
switch (p_option) {
case FILE_SHOW_IN_EXPLORER: {
- String dir = ProjectSettings::get_singleton()->globalize_path(this->path);
- OS::get_singleton()->shell_open(String("file://") + dir);
+
+ String path = this->path;
+
+ // first try to grab directory from selected file, so that it works for searched files
+ int idx = files->get_current();
+
+ if (idx >= 0 && idx < files->get_item_count()) {
+ path = files->get_item_metadata(idx);
+ path = path.get_base_dir();
+ }
+
+ path = ProjectSettings::get_singleton()->globalize_path(path);
+ OS::get_singleton()->shell_open(String("file://") + path);
} break;
case FILE_OPEN: {
for (int i = 0; i < files->get_item_count(); i++) {
@@ -1660,6 +1701,8 @@ void FileSystemDock::_files_gui_input(Ref<InputEvent> p_event) {
_file_option(FILE_COPY_PATH);
} else if (ED_IS_SHORTCUT("filesystem_dock/delete", p_event)) {
_file_option(FILE_REMOVE);
+ } else if (ED_IS_SHORTCUT("filesystem_dock/rename", p_event)) {
+ _file_option(FILE_RENAME);
}
}
}
@@ -1770,6 +1813,7 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) {
ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KEY_MASK_CMD | KEY_C);
ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KEY_MASK_CMD | KEY_D);
ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), KEY_DELETE);
+ ED_SHORTCUT("filesystem_dock/rename", TTR("Rename"));
HBoxContainer *toolbar_hbc = memnew(HBoxContainer);
add_child(toolbar_hbc);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 377316d1ba..c8448a1022 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -176,12 +176,13 @@ private:
void _file_selected();
void _dir_selected();
- void _get_all_files_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files) const;
+ void _get_all_items_in_dir(EditorFileSystemDirectory *efsd, Vector<String> &files, Vector<String> &folders) const;
void _find_remaps(EditorFileSystemDirectory *efsd, const Map<String, String> &renames, Vector<String> &to_remaps) const;
- void _try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_renames) const;
+ void _try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_file_renames, Map<String, String> &p_folder_renames) const;
void _try_duplicate_item(const FileOrFolder &p_item, const String &p_new_path) const;
void _update_dependencies_after_move(const Map<String, String> &p_renames) const;
void _update_resource_paths_after_move(const Map<String, String> &p_renames) const;
+ void _update_favorite_dirs_list_after_move(const Map<String, String> &p_renames) const;
void _make_dir_confirm();
void _rename_operation_confirm();
diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp
new file mode 100644
index 0000000000..9442bbc0e8
--- /dev/null
+++ b/editor/find_in_files.cpp
@@ -0,0 +1,829 @@
+/*************************************************************************/
+/* find_in_files.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "find_in_files.h"
+#include "core/os/dir_access.h"
+#include "core/os/os.h"
+#include "editor_scale.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/check_box.h"
+#include "scene/gui/file_dialog.h"
+#include "scene/gui/grid_container.h"
+#include "scene/gui/item_list.h"
+#include "scene/gui/label.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/progress_bar.h"
+
+#define ROOT_PREFIX "res://"
+
+const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found";
+const char *FindInFiles::SIGNAL_FINISHED = "finished";
+
+// TODO Would be nice in Vector and PoolVectors
+template <typename T>
+inline void pop_back(T &container) {
+ container.resize(container.size() - 1);
+}
+
+// TODO Copied from TextEdit private, would be nice to extract it in a single place
+static bool is_text_char(CharType c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+}
+
+FindInFiles::FindInFiles() {
+ _root_prefix = ROOT_PREFIX;
+ _extension_filter.insert("gd");
+ _extension_filter.insert("cs");
+ _searching = false;
+ _whole_words = true;
+ _match_case = true;
+}
+
+void FindInFiles::set_search_text(String p_pattern) {
+ _pattern = p_pattern;
+}
+
+void FindInFiles::set_whole_words(bool p_whole_word) {
+ _whole_words = p_whole_word;
+}
+
+void FindInFiles::set_match_case(bool p_match_case) {
+ _match_case = p_match_case;
+}
+
+void FindInFiles::set_folder(String folder) {
+ _root_dir = folder;
+}
+
+void FindInFiles::set_filter(const Set<String> &exts) {
+ _extension_filter = exts;
+}
+
+void FindInFiles::_notification(int p_notification) {
+ if (p_notification == NOTIFICATION_PROCESS) {
+ _process();
+ }
+}
+
+void FindInFiles::start() {
+ if (_pattern == "") {
+ print_line("Nothing to search, pattern is empty");
+ emit_signal(SIGNAL_FINISHED);
+ return;
+ }
+ if (_extension_filter.size() == 0) {
+ print_line("Nothing to search, filter matches no files");
+ emit_signal(SIGNAL_FINISHED);
+ return;
+ }
+
+ // Init search
+ _current_dir = "";
+ PoolStringArray init_folder;
+ init_folder.append(_root_dir);
+ _folders_stack.push_back(init_folder);
+
+ _initial_files_count = 0;
+
+ _searching = true;
+ set_process(true);
+}
+
+void FindInFiles::stop() {
+ _searching = false;
+ _current_dir = "";
+ set_process(false);
+}
+
+void FindInFiles::_process() {
+ // This part can be moved to a thread if needed
+
+ OS &os = *OS::get_singleton();
+ float duration = 0.0;
+ while (duration < 1.0 / 120.0) {
+ float time_before = os.get_ticks_msec();
+ _iterate();
+ duration += (os.get_ticks_msec() - time_before);
+ }
+}
+
+void FindInFiles::_iterate() {
+
+ if (_folders_stack.size() != 0) {
+
+ // Scan folders first so we can build a list of files and have progress info later
+
+ PoolStringArray &folders_to_scan = _folders_stack[_folders_stack.size() - 1];
+
+ if (folders_to_scan.size() != 0) {
+ // Scan one folder below
+
+ String folder_name = folders_to_scan[folders_to_scan.size() - 1];
+ pop_back(folders_to_scan);
+
+ _current_dir = _current_dir.plus_file(folder_name);
+
+ PoolStringArray sub_dirs;
+ _scan_dir(_root_prefix + _current_dir, sub_dirs);
+
+ if (sub_dirs.size() != 0) {
+ _folders_stack.push_back(sub_dirs);
+ }
+
+ } else {
+ // Go back one level
+
+ pop_back(_folders_stack);
+ _current_dir = _current_dir.get_base_dir();
+
+ if (_folders_stack.size() == 0) {
+ // All folders scanned
+ _initial_files_count = _files_to_scan.size();
+ }
+ }
+
+ } else if (_files_to_scan.size() != 0) {
+
+ // Then scan files
+
+ String fpath = _files_to_scan[_files_to_scan.size() - 1];
+ pop_back(_files_to_scan);
+ _scan_file(_root_prefix + fpath);
+
+ } else {
+ print_line("Search complete");
+ set_process(false);
+ _current_dir = "";
+ _searching = false;
+ emit_signal(SIGNAL_FINISHED);
+ }
+}
+
+float FindInFiles::get_progress() const {
+ if (_initial_files_count != 0) {
+ return static_cast<float>(_initial_files_count - _files_to_scan.size()) / static_cast<float>(_initial_files_count);
+ }
+ return 0;
+}
+
+void FindInFiles::_scan_dir(String path, PoolStringArray &out_folders) {
+
+ DirAccess *dir = DirAccess::open(path);
+ if (dir == NULL) {
+ print_line("Cannot open directory! " + path);
+ return;
+ }
+
+ //print_line(String("Scanning ") + path);
+
+ dir->list_dir_begin();
+
+ for (int i = 0; i < 1000; ++i) {
+ String file = dir->get_next();
+
+ if (file == "")
+ break;
+
+ // Ignore special dirs and hidden dirs (such as .git and .import)
+ if (file == "." || file == ".." || file.begins_with("."))
+ continue;
+
+ if (dir->current_is_dir())
+ out_folders.append(file);
+
+ else {
+ String file_ext = file.get_extension();
+ if (_extension_filter.has(file_ext)) {
+ _files_to_scan.push_back(file);
+ }
+ }
+ }
+}
+
+void FindInFiles::_scan_file(String fpath) {
+
+ FileAccess *f = FileAccess::open(fpath, FileAccess::READ);
+ if (f == NULL) {
+ f->close();
+ print_line(String("Cannot open file ") + fpath);
+ return;
+ }
+
+ int line_number = 0;
+
+ while (!f->eof_reached()) {
+
+ // line number starts at 1
+ ++line_number;
+
+ int begin = 0;
+ int end = 0;
+
+ String line = f->get_line();
+
+ // Find all occurrences in the current line
+ while (true) {
+ begin = _match_case ? line.find(_pattern, end) : line.findn(_pattern, end);
+
+ if (begin == -1)
+ break;
+
+ end = begin + _pattern.length();
+
+ if (_whole_words) {
+ if (begin > 0 && is_text_char(line[begin - 1])) {
+ continue;
+ }
+ if (end < line.size() && is_text_char(line[end])) {
+ continue;
+ }
+ }
+
+ emit_signal(SIGNAL_RESULT_FOUND, fpath, line_number, begin, end, line);
+ }
+ }
+
+ f->close();
+}
+
+void FindInFiles::_bind_methods() {
+
+ ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_FOUND,
+ PropertyInfo(Variant::STRING, "path"),
+ PropertyInfo(Variant::INT, "line_number"),
+ PropertyInfo(Variant::INT, "begin"),
+ PropertyInfo(Variant::INT, "end"),
+ PropertyInfo(Variant::STRING, "text")));
+
+ ADD_SIGNAL(MethodInfo(SIGNAL_FINISHED));
+}
+
+//-----------------------------------------------------------------------------
+const char *FindInFilesDialog::SIGNAL_FIND_REQUESTED = "find_requested";
+const char *FindInFilesDialog::SIGNAL_REPLACE_REQUESTED = "replace_requested";
+
+FindInFilesDialog::FindInFilesDialog() {
+
+ set_custom_minimum_size(Size2(400, 190));
+ set_resizable(true);
+ set_title(TTR("Find in files"));
+
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 8 * EDSCALE);
+ vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 8 * EDSCALE);
+ vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -8 * EDSCALE);
+ vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -8 * EDSCALE);
+ add_child(vbc);
+
+ GridContainer *gc = memnew(GridContainer);
+ gc->set_columns(2);
+ vbc->add_child(gc);
+
+ Label *find_label = memnew(Label);
+ find_label->set_text(TTR("Find: "));
+ gc->add_child(find_label);
+
+ _search_text_line_edit = memnew(LineEdit);
+ _search_text_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ _search_text_line_edit->connect("text_changed", this, "_on_search_text_modified");
+ _search_text_line_edit->connect("text_entered", this, "_on_search_text_entered");
+ gc->add_child(_search_text_line_edit);
+
+ {
+ Control *placeholder = memnew(Control);
+ gc->add_child(placeholder);
+ }
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+
+ _whole_words_checkbox = memnew(CheckBox);
+ _whole_words_checkbox->set_text(TTR("Whole words"));
+ _whole_words_checkbox->set_pressed(true);
+ hbc->add_child(_whole_words_checkbox);
+
+ _match_case_checkbox = memnew(CheckBox);
+ _match_case_checkbox->set_text(TTR("Match case"));
+ _match_case_checkbox->set_pressed(true);
+ hbc->add_child(_match_case_checkbox);
+
+ gc->add_child(hbc);
+ }
+
+ Label *folder_label = memnew(Label);
+ folder_label->set_text(TTR("Folder: "));
+ gc->add_child(folder_label);
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+
+ Label *prefix_label = memnew(Label);
+ prefix_label->set_text(ROOT_PREFIX);
+ hbc->add_child(prefix_label);
+
+ _folder_line_edit = memnew(LineEdit);
+ _folder_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ hbc->add_child(_folder_line_edit);
+
+ Button *folder_button = memnew(Button);
+ folder_button->set_text("...");
+ folder_button->connect("pressed", this, "_on_folder_button_pressed");
+ hbc->add_child(folder_button);
+
+ _folder_dialog = memnew(FileDialog);
+ _folder_dialog->set_mode(FileDialog::MODE_OPEN_DIR);
+ _folder_dialog->connect("dir_selected", this, "_on_folder_selected");
+ add_child(_folder_dialog);
+
+ gc->add_child(hbc);
+ }
+
+ Label *filter_label = memnew(Label);
+ filter_label->set_text(TTR("Filter: "));
+ gc->add_child(filter_label);
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+
+ Vector<String> exts;
+ exts.push_back("gd");
+ exts.push_back("cs");
+
+ for (int i = 0; i < exts.size(); ++i) {
+ CheckBox *cb = memnew(CheckBox);
+ cb->set_text(exts[i]);
+ cb->set_pressed(true);
+ hbc->add_child(cb);
+ _filters.push_back(cb);
+ }
+
+ gc->add_child(hbc);
+ }
+
+ {
+ Control *placeholder = memnew(Control);
+ placeholder->set_custom_minimum_size(Size2(0, EDSCALE * 16));
+ vbc->add_child(placeholder);
+ }
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+ hbc->set_alignment(HBoxContainer::ALIGN_CENTER);
+
+ _find_button = memnew(Button);
+ _find_button->set_text(TTR("Find..."));
+ _find_button->connect("pressed", this, "_on_find_button_pressed");
+ _find_button->set_disabled(true);
+ hbc->add_child(_find_button);
+
+ {
+ Control *placeholder = memnew(Control);
+ placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0));
+ hbc->add_child(placeholder);
+ }
+
+ _replace_button = memnew(Button);
+ _replace_button->set_text(TTR("Replace..."));
+ _replace_button->connect("pressed", this, "_on_replace_button_pressed");
+ _replace_button->set_disabled(true);
+ hbc->add_child(_replace_button);
+
+ {
+ Control *placeholder = memnew(Control);
+ placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0));
+ hbc->add_child(placeholder);
+ }
+
+ Button *cancel_button = memnew(Button);
+ cancel_button->set_text(TTR("Cancel"));
+ cancel_button->connect("pressed", this, "hide");
+ hbc->add_child(cancel_button);
+
+ vbc->add_child(hbc);
+ }
+}
+
+void FindInFilesDialog::set_search_text(String text) {
+ _search_text_line_edit->set_text(text);
+}
+
+String FindInFilesDialog::get_search_text() const {
+ String text = _search_text_line_edit->get_text();
+ return text.strip_edges();
+}
+
+bool FindInFilesDialog::is_match_case() const {
+ return _match_case_checkbox->is_pressed();
+}
+
+bool FindInFilesDialog::is_whole_words() const {
+ return _whole_words_checkbox->is_pressed();
+}
+
+String FindInFilesDialog::get_folder() const {
+ String text = _folder_line_edit->get_text();
+ return text.strip_edges();
+}
+
+Set<String> FindInFilesDialog::get_filter() const {
+ Set<String> filters;
+ for (int i = 0; i < _filters.size(); ++i) {
+ CheckBox *cb = _filters[i];
+ if (cb->is_pressed()) {
+ filters.insert(_filters[i]->get_text());
+ }
+ }
+ return filters;
+}
+
+void FindInFilesDialog::_notification(int p_what) {
+ if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+ if (is_visible()) {
+ // Doesn't work more than once if not deferred...
+ _search_text_line_edit->call_deferred("grab_focus");
+ _search_text_line_edit->select_all();
+ }
+ }
+}
+
+void FindInFilesDialog::_on_folder_button_pressed() {
+ _folder_dialog->popup_centered_ratio();
+}
+
+void FindInFilesDialog::_on_find_button_pressed() {
+ emit_signal(SIGNAL_FIND_REQUESTED);
+ hide();
+}
+
+void FindInFilesDialog::_on_replace_button_pressed() {
+ emit_signal(SIGNAL_REPLACE_REQUESTED);
+ hide();
+}
+
+void FindInFilesDialog::_on_search_text_modified(String text) {
+
+ ERR_FAIL_COND(!_find_button);
+ ERR_FAIL_COND(!_replace_button);
+
+ _find_button->set_disabled(get_search_text().empty());
+ _replace_button->set_disabled(get_search_text().empty());
+}
+
+void FindInFilesDialog::_on_search_text_entered(String text) {
+ // This allows to trigger a global search without leaving the keyboard
+ if (!_find_button->is_disabled())
+ _on_find_button_pressed();
+}
+
+void FindInFilesDialog::_on_folder_selected(String path) {
+ int i = path.find("://");
+ if (i != -1)
+ path = path.right(i + 3);
+ _folder_line_edit->set_text(path);
+}
+
+void FindInFilesDialog::_bind_methods() {
+
+ ClassDB::bind_method("_on_folder_button_pressed", &FindInFilesDialog::_on_folder_button_pressed);
+ ClassDB::bind_method("_on_find_button_pressed", &FindInFilesDialog::_on_find_button_pressed);
+ ClassDB::bind_method("_on_replace_button_pressed", &FindInFilesDialog::_on_replace_button_pressed);
+ ClassDB::bind_method("_on_folder_selected", &FindInFilesDialog::_on_folder_selected);
+ ClassDB::bind_method("_on_search_text_modified", &FindInFilesDialog::_on_search_text_modified);
+ ClassDB::bind_method("_on_search_text_entered", &FindInFilesDialog::_on_search_text_entered);
+
+ ADD_SIGNAL(MethodInfo(SIGNAL_FIND_REQUESTED));
+ ADD_SIGNAL(MethodInfo(SIGNAL_REPLACE_REQUESTED));
+}
+
+//-----------------------------------------------------------------------------
+const char *FindInFilesPanel::SIGNAL_RESULT_SELECTED = "result_selected";
+const char *FindInFilesPanel::SIGNAL_FILES_MODIFIED = "files_modified";
+
+FindInFilesPanel::FindInFilesPanel() {
+
+ _finder = memnew(FindInFiles);
+ _finder->connect(FindInFiles::SIGNAL_RESULT_FOUND, this, "_on_result_found");
+ _finder->connect(FindInFiles::SIGNAL_FINISHED, this, "_on_finished");
+ add_child(_finder);
+
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 0);
+ vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 0);
+ vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0);
+ vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0);
+ add_child(vbc);
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+
+ Label *find_label = memnew(Label);
+ find_label->set_text(TTR("Find: "));
+ hbc->add_child(find_label);
+
+ _search_text_label = memnew(Label);
+ _search_text_label->add_font_override("font", get_font("source", "EditorFonts"));
+ hbc->add_child(_search_text_label);
+
+ _progress_bar = memnew(ProgressBar);
+ _progress_bar->set_h_size_flags(SIZE_EXPAND_FILL);
+ hbc->add_child(_progress_bar);
+ set_progress_visible(false);
+
+ _status_label = memnew(Label);
+ hbc->add_child(_status_label);
+
+ _cancel_button = memnew(Button);
+ _cancel_button->set_text(TTR("Cancel"));
+ _cancel_button->connect("pressed", this, "_on_cancel_button_clicked");
+ _cancel_button->set_disabled(true);
+ hbc->add_child(_cancel_button);
+
+ vbc->add_child(hbc);
+ }
+
+ // In the future, this should be replaced by a more specific list container,
+ // which can highlight text regions and change opacity for enabled/disabled states
+ _results_display = memnew(ItemList);
+ _results_display->add_font_override("font", get_font("source", "EditorFonts"));
+ _results_display->set_v_size_flags(SIZE_EXPAND_FILL);
+ _results_display->connect("item_selected", this, "_on_result_selected");
+ vbc->add_child(_results_display);
+
+ {
+ _replace_container = memnew(HBoxContainer);
+
+ Label *replace_label = memnew(Label);
+ replace_label->set_text(TTR("Replace: "));
+ _replace_container->add_child(replace_label);
+
+ _replace_line_edit = memnew(LineEdit);
+ _replace_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ _replace_line_edit->connect("text_changed", this, "_on_replace_text_changed");
+ _replace_container->add_child(_replace_line_edit);
+
+ _replace_all_button = memnew(Button);
+ _replace_all_button->set_text(TTR("Replace all (no undo)"));
+ _replace_all_button->connect("pressed", this, "_on_replace_all_clicked");
+ _replace_container->add_child(_replace_all_button);
+
+ _replace_container->hide();
+
+ vbc->add_child(_replace_container);
+ }
+}
+
+void FindInFilesPanel::set_with_replace(bool with_replace) {
+
+ _replace_container->set_visible(with_replace);
+}
+
+void FindInFilesPanel::start_search() {
+
+ _results_display->clear();
+ _status_label->set_text(TTR("Searching..."));
+ _search_text_label->set_text(_finder->get_search_text());
+
+ set_process(true);
+ set_progress_visible(true);
+
+ _finder->start();
+
+ update_replace_buttons();
+ _cancel_button->set_disabled(false);
+}
+
+void FindInFilesPanel::stop_search() {
+
+ _finder->stop();
+
+ _status_label->set_text("");
+ update_replace_buttons();
+ set_progress_visible(false);
+ _cancel_button->set_disabled(true);
+}
+
+void FindInFilesPanel::_notification(int p_what) {
+ if (p_what == NOTIFICATION_PROCESS) {
+ _progress_bar->set_as_ratio(_finder->get_progress());
+ }
+}
+
+void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin, int end, String text) {
+
+ int i = _results_display->get_item_count();
+ _results_display->add_item(fpath + ": " + String::num(line_number) + ": " + text.replace("\t", " "));
+ _results_display->set_item_metadata(i, varray(fpath, line_number, begin, end));
+}
+
+void FindInFilesPanel::_on_finished() {
+
+ _status_label->set_text(TTR("Search complete"));
+ update_replace_buttons();
+ set_progress_visible(false);
+ _cancel_button->set_disabled(true);
+}
+
+void FindInFilesPanel::_on_cancel_button_clicked() {
+ stop_search();
+}
+
+void FindInFilesPanel::_on_result_selected(int i) {
+
+ Array meta = _results_display->get_item_metadata(i);
+ emit_signal(SIGNAL_RESULT_SELECTED, meta[0], meta[1], meta[2], meta[3]);
+}
+
+void FindInFilesPanel::_on_replace_text_changed(String text) {
+ update_replace_buttons();
+}
+
+void FindInFilesPanel::_on_replace_all_clicked() {
+
+ String replace_text = get_replace_text();
+ ERR_FAIL_COND(replace_text.empty());
+
+ String last_fpath;
+ PoolIntArray locations;
+ PoolStringArray modified_files;
+
+ for (int i = 0; i < _results_display->get_item_count(); ++i) {
+
+ Array meta = _results_display->get_item_metadata(i);
+
+ String fpath = meta[0];
+
+ // Results are sorted by file, so we can batch replaces
+ if (fpath != last_fpath) {
+ if (locations.size() != 0) {
+ apply_replaces_in_file(last_fpath, locations, replace_text);
+ modified_files.append(last_fpath);
+ locations.resize(0);
+ }
+ }
+
+ locations.append(meta[1]); // line_number
+ locations.append(meta[2]); // begin
+ locations.append(meta[3]); // end
+
+ last_fpath = fpath;
+ }
+
+ if (locations.size() != 0) {
+ apply_replaces_in_file(last_fpath, locations, replace_text);
+ modified_files.append(last_fpath);
+ }
+
+ // Hide replace bar so we can't trigger the action twice without doing a new search
+ set_with_replace(false);
+
+ emit_signal(SIGNAL_FILES_MODIFIED, modified_files);
+}
+
+// Same as get_line, but preserves line ending characters
+class ConservativeGetLine {
+public:
+ String get_line(FileAccess *f) {
+
+ _line_buffer.clear();
+
+ CharType c = f->get_8();
+
+ while (!f->eof_reached()) {
+
+ if (c == '\n') {
+ _line_buffer.push_back(c);
+ _line_buffer.push_back(0);
+ return String::utf8(_line_buffer.ptr());
+
+ } else if (c == '\0') {
+ _line_buffer.push_back(c);
+ return String::utf8(_line_buffer.ptr());
+
+ } else if (c != '\r') {
+ _line_buffer.push_back(c);
+ }
+
+ c = f->get_8();
+ }
+
+ _line_buffer.push_back(0);
+ return String::utf8(_line_buffer.ptr());
+ }
+
+private:
+ Vector<char> _line_buffer;
+};
+
+void FindInFilesPanel::apply_replaces_in_file(String fpath, PoolIntArray locations, String text) {
+
+ ERR_FAIL_COND(locations.size() % 3 != 0);
+
+ //print_line(String("Replacing {0} occurrences in {1}").format(varray(fpath, locations.size() / 3)));
+
+ // If the file is already open, I assume the editor will reload it.
+ // If there are unsaved changes, the user will be asked on focus,
+ // however that means either loosing changes or loosing replaces.
+
+ FileAccess *f = FileAccess::open(fpath, FileAccess::READ);
+ ERR_FAIL_COND(f == NULL);
+
+ String buffer;
+ int current_line = 1;
+
+ ConservativeGetLine conservative;
+
+ String line = conservative.get_line(f);
+
+ PoolIntArray::Read locations_read = locations.read();
+ for (int i = 0; i < locations.size(); i += 3) {
+
+ int repl_line_number = locations_read[i];
+ int repl_begin = locations_read[i + 1];
+ int repl_end = locations_read[i + 2];
+
+ while (current_line < repl_line_number) {
+ buffer += line;
+ line = conservative.get_line(f);
+ ++current_line;
+ }
+
+ line = line.left(repl_begin) + text + line.right(repl_end);
+ }
+
+ buffer += line;
+
+ while (!f->eof_reached()) {
+ buffer += conservative.get_line(f);
+ }
+
+ // Now the modified contents are in the buffer, rewrite the file with our changes
+
+ Error err = f->reopen(fpath, FileAccess::WRITE);
+ ERR_FAIL_COND(err != OK);
+
+ f->store_string(buffer);
+
+ f->close();
+}
+
+String FindInFilesPanel::get_replace_text() {
+ return _replace_line_edit->get_text().strip_edges();
+}
+
+void FindInFilesPanel::update_replace_buttons() {
+
+ String text = get_replace_text();
+ bool disabled = text.empty() || _finder->is_searching();
+
+ _replace_all_button->set_disabled(disabled);
+}
+
+void FindInFilesPanel::set_progress_visible(bool visible) {
+ _progress_bar->set_self_modulate(Color(1, 1, 1, visible ? 1 : 0));
+}
+
+void FindInFilesPanel::_bind_methods() {
+
+ ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found);
+ ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished);
+ ClassDB::bind_method("_on_cancel_button_clicked", &FindInFilesPanel::_on_cancel_button_clicked);
+ ClassDB::bind_method("_on_result_selected", &FindInFilesPanel::_on_result_selected);
+ ClassDB::bind_method("_on_replace_text_changed", &FindInFilesPanel::_on_replace_text_changed);
+ ClassDB::bind_method("_on_replace_all_clicked", &FindInFilesPanel::_on_replace_all_clicked);
+
+ ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED,
+ PropertyInfo(Variant::STRING, "path"),
+ PropertyInfo(Variant::INT, "line_number"),
+ PropertyInfo(Variant::INT, "begin"),
+ PropertyInfo(Variant::INT, "end")));
+
+ ADD_SIGNAL(MethodInfo(SIGNAL_FILES_MODIFIED, PropertyInfo(Variant::STRING, "paths")));
+}
diff --git a/editor/find_in_files.h b/editor/find_in_files.h
new file mode 100644
index 0000000000..d57184960b
--- /dev/null
+++ b/editor/find_in_files.h
@@ -0,0 +1,184 @@
+/*************************************************************************/
+/* find_in_files.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef FIND_IN_FILES_H
+#define FIND_IN_FILES_H
+
+#include "scene/gui/dialogs.h"
+
+// Performs the actual search
+class FindInFiles : public Node {
+ GDCLASS(FindInFiles, Node)
+public:
+ static const char *SIGNAL_RESULT_FOUND;
+ static const char *SIGNAL_FINISHED;
+
+ FindInFiles();
+
+ void set_search_text(String p_pattern);
+ void set_whole_words(bool p_whole_word);
+ void set_match_case(bool p_match_case);
+ void set_folder(String folder);
+ void set_filter(const Set<String> &exts);
+
+ String get_search_text() const { return _pattern; }
+
+ bool is_whole_words() const { return _whole_words; }
+ bool is_match_case() const { return _match_case; }
+
+ void start();
+ void stop();
+
+ bool is_searching() const { return _searching; }
+ float get_progress() const;
+
+protected:
+ void _notification(int p_notification);
+
+ static void _bind_methods();
+
+private:
+ void _process();
+ void _iterate();
+ void _scan_dir(String path, PoolStringArray &out_folders);
+ void _scan_file(String fpath);
+
+ // Config
+ String _pattern;
+ Set<String> _extension_filter;
+ String _root_prefix;
+ String _root_dir;
+ bool _whole_words;
+ bool _match_case;
+
+ // State
+ bool _searching;
+ String _current_dir;
+ Vector<PoolStringArray> _folders_stack;
+ Vector<String> _files_to_scan;
+ int _initial_files_count;
+};
+
+class LineEdit;
+class CheckBox;
+class FileDialog;
+
+// Prompts search parameters
+class FindInFilesDialog : public WindowDialog {
+ GDCLASS(FindInFilesDialog, WindowDialog)
+public:
+ static const char *SIGNAL_FIND_REQUESTED;
+ static const char *SIGNAL_REPLACE_REQUESTED;
+
+ FindInFilesDialog();
+
+ void set_search_text(String text);
+
+ String get_search_text() const;
+ bool is_match_case() const;
+ bool is_whole_words() const;
+ String get_folder() const;
+ Set<String> get_filter() const;
+
+protected:
+ static void _bind_methods();
+
+ void _notification(int p_what);
+
+private:
+ void _on_folder_button_pressed();
+ void _on_find_button_pressed();
+ void _on_replace_button_pressed();
+ void _on_folder_selected(String path);
+ void _on_search_text_modified(String text);
+ void _on_search_text_entered(String text);
+
+ LineEdit *_search_text_line_edit;
+ LineEdit *_folder_line_edit;
+ Vector<CheckBox *> _filters;
+ CheckBox *_match_case_checkbox;
+ CheckBox *_whole_words_checkbox;
+ Button *_find_button;
+ Button *_replace_button;
+ FileDialog *_folder_dialog;
+};
+
+class Button;
+class ItemList;
+class ProgressBar;
+
+// Display search results
+class FindInFilesPanel : public Control {
+ GDCLASS(FindInFilesPanel, Control)
+public:
+ static const char *SIGNAL_RESULT_SELECTED;
+ static const char *SIGNAL_FILES_MODIFIED;
+
+ FindInFilesPanel();
+
+ FindInFiles *get_finder() const { return _finder; }
+
+ void set_with_replace(bool with_replace);
+
+ void start_search();
+ void stop_search();
+
+protected:
+ static void _bind_methods();
+
+ void _notification(int p_what);
+
+private:
+ void _on_result_found(String fpath, int line_number, int begin, int end, String text);
+ void _on_finished();
+ void _on_cancel_button_clicked();
+ void _on_result_selected(int i);
+ void _on_replace_text_changed(String text);
+ void _on_replace_all_clicked();
+
+ void apply_replaces_in_file(String fpath, PoolIntArray locations, String text);
+
+ void update_replace_buttons();
+ String get_replace_text();
+ void set_progress_visible(bool visible);
+
+ FindInFiles *_finder;
+ Label *_search_text_label;
+ ItemList *_results_display;
+ Label *_status_label;
+ Button *_cancel_button;
+ ProgressBar *_progress_bar;
+
+ HBoxContainer *_replace_container;
+ LineEdit *_replace_line_edit;
+ Button *_replace_all_button;
+};
+
+#endif // FIND_IN_FILES_H
diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp
index 863b13cbd7..c1e897a04c 100644
--- a/editor/import/editor_import_collada.cpp
+++ b/editor/import/editor_import_collada.cpp
@@ -480,7 +480,7 @@ Error ColladaImport::_create_material(const String &p_target) {
}
}
- float roughness = Math::sqrt(1.0 - ((Math::log(effect.shininess) / Math::log(2.0)) / 8.0)); //not very right..
+ float roughness = (effect.shininess - 1.0) / 510;
material->set_roughness(roughness);
if (effect.double_sided) {
diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp
index 321d29f2f6..af79f9946a 100644
--- a/editor/import/editor_scene_importer_gltf.cpp
+++ b/editor/import/editor_scene_importer_gltf.cpp
@@ -1732,14 +1732,19 @@ void EditorSceneImporterGLTF::_generate_bone(GLTFState &state, int p_node, Vecto
for (int i = 0; i < n->joints.size(); i++) {
ERR_FAIL_COND(n->joints[i].skin < 0);
- int bone_index = skeletons[n->joints[i].skin]->get_bone_count();
- skeletons[n->joints[i].skin]->add_bone(n->name);
+ int bone_index = n->joints[i].bone;
+
+ Skeleton *s = skeletons[n->joints[i].skin];
+ while (s->get_bone_count() <= bone_index) {
+ s->add_bone("Bone " + itos(s->get_bone_count()));
+ }
+
if (p_parent_bones.size()) {
- skeletons[n->joints[i].skin]->set_bone_parent(bone_index, p_parent_bones[i]);
+ s->set_bone_parent(bone_index, p_parent_bones[i]);
}
- skeletons[n->joints[i].skin]->set_bone_rest(bone_index, state.skins[n->joints[i].skin].bones[n->joints[i].bone].inverse_bind.affine_inverse());
+ s->set_bone_rest(bone_index, state.skins[n->joints[i].skin].bones[n->joints[i].bone].inverse_bind.affine_inverse());
- n->godot_nodes.push_back(skeletons[n->joints[i].skin]);
+ n->godot_nodes.push_back(s);
n->joints[i].godot_bone_index = bone_index;
parent_bones.push_back(bone_index);
}
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 03155b3a48..debdeb1c4a 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -304,17 +304,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
int limit_rate_hz = p_options["force/max_rate_hz"];
if (limit_rate && rate > limit_rate_hz && rate > 0 && frames > 0) {
//resampleeee!!!
- int new_data_frames = frames * limit_rate_hz / rate;
+ int new_data_frames = (int)(frames * (float)limit_rate_hz / (float)rate);
+
+ print_line("\tresampling ratio: " + rtos((float)limit_rate_hz / (float)rate));
+ print_line("\tnew frames: " + itos(new_data_frames));
+
Vector<float> new_data;
new_data.resize(new_data_frames * format_channels);
for (int c = 0; c < format_channels; c++) {
+ float frac = .0f;
+ int ipos = 0;
+
for (int i = 0; i < new_data_frames; i++) {
//simple cubic interpolation should be enough.
- float pos = float(i) * frames / new_data_frames;
- float mu = pos - Math::floor(pos);
- int ipos = int(Math::floor(pos));
+
+ float mu = frac;
float y0 = data[MAX(0, ipos - 1) * format_channels + c];
float y1 = data[ipos * format_channels + c];
@@ -330,14 +336,22 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
float res = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3);
new_data[i * format_channels + c] = res;
+
+ // update position and always keep fractional part within ]0...1]
+ // in order to avoid 32bit floating point precision errors
+
+ frac += (float)rate / (float)limit_rate_hz;
+ int tpos = (int)Math::floor(frac);
+ ipos += tpos;
+ frac -= tpos;
}
}
if (loop) {
-
- loop_begin = loop_begin * new_data_frames / frames;
- loop_end = loop_end * new_data_frames / frames;
+ loop_begin = (int)(loop_begin * (float)new_data_frames / (float)frames);
+ loop_end = (int)(loop_end * (float)new_data_frames / (float)frames);
}
+
data = new_data;
rate = limit_rate_hz;
frames = new_data_frames;
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 04c9246aed..7612f18aff 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -1679,10 +1679,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay
onion_skinning->get_popup()->add_separator();
onion_skinning->get_popup()->add_item(TTR("Depth"), -1);
onion_skinning->get_popup()->set_item_disabled(onion_skinning->get_popup()->get_item_count() - 1, true);
- onion_skinning->get_popup()->add_check_item(TTR("1 step"), ONION_SKINNING_1_STEP);
+ onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP);
onion_skinning->get_popup()->set_item_checked(onion_skinning->get_popup()->get_item_count() - 1, true);
- onion_skinning->get_popup()->add_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS);
- onion_skinning->get_popup()->add_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS);
+ onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS);
+ onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS);
onion_skinning->get_popup()->add_separator();
onion_skinning->get_popup()->add_check_item(TTR("Differences Only"), ONION_SKINNING_DIFFERENCES_ONLY);
onion_skinning->get_popup()->add_check_item(TTR("Force White Modulate"), ONION_SKINNING_FORCE_WHITE_MODULATE);
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index f0e186e4b0..37213c1866 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -756,6 +756,7 @@ void AnimationTreeEditor::_gui_input(Ref<InputEvent> p_event) {
if (rclick_type == CLICK_INPUT_SLOT || rclick_type == CLICK_OUTPUT_SLOT) {
node_popup->clear();
+ node_popup->set_size(Size2(1, 1));
node_popup->add_item(TTR("Disconnect"), NODE_DISCONNECT);
if (anim_tree->node_get_type(rclick_node) == AnimationTreePlayer::NODE_TRANSITION) {
node_popup->add_item(TTR("Add Input"), NODE_ADD_INPUT);
@@ -774,6 +775,7 @@ void AnimationTreeEditor::_gui_input(Ref<InputEvent> p_event) {
if (rclick_type == CLICK_NODE) {
node_popup->clear();
+ node_popup->set_size(Size2(1, 1));
node_popup->add_item(TTR("Rename"), NODE_RENAME);
node_popup->add_item(TTR("Remove"), NODE_ERASE);
if (anim_tree->node_get_type(rclick_node) == AnimationTreePlayer::NODE_TRANSITION)
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index c203b4b81e..b72c9b25be 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -902,7 +902,7 @@ void EditorAssetLibrary::_search(int p_page) {
}
if (filter->get_text() != String()) {
- args += "&filter=" + filter->get_text().http_escape();
+ args += "&filter=" + filter->get_text().percent_encode();
}
if (p_page > 0) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 0184bff050..b8d0958c99 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -1672,8 +1672,6 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
selection_menu_additive_selection = b->get_shift();
selection_menu->set_global_position(b->get_global_position());
selection_menu->popup();
- selection_menu->call_deferred("grab_click_focus");
- selection_menu->set_invalidate_click_until_motion();
return true;
}
}
diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp
index 5020f5b851..8b44f672b0 100644
--- a/editor/plugins/item_list_editor_plugin.cpp
+++ b/editor/plugins/item_list_editor_plugin.cpp
@@ -42,9 +42,18 @@ bool ItemListPlugin::_set(const StringName &p_name, const Variant &p_value) {
set_item_text(idx, p_value);
else if (what == "icon")
set_item_icon(idx, p_value);
- else if (what == "checkable")
- set_item_checkable(idx, p_value);
- else if (what == "checked")
+ else if (what == "checkable") {
+ // This keeps compatibility to/from versions where this property was a boolean, before radio buttons
+ switch ((int)p_value) {
+ case 0:
+ case 1:
+ set_item_checkable(idx, p_value);
+ break;
+ case 2:
+ set_item_radio_checkable(idx, true);
+ break;
+ }
+ } else if (what == "checked")
set_item_checked(idx, p_value);
else if (what == "id")
set_item_id(idx, p_value);
@@ -68,9 +77,14 @@ bool ItemListPlugin::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = get_item_text(idx);
else if (what == "icon")
r_ret = get_item_icon(idx);
- else if (what == "checkable")
- r_ret = is_item_checkable(idx);
- else if (what == "checked")
+ else if (what == "checkable") {
+ // This keeps compatibility to/from versions where this property was a boolean, before radio buttons
+ if (!is_item_checkable(idx)) {
+ r_ret = 0;
+ } else {
+ r_ret = is_item_radio_checkable(idx) ? 2 : 1;
+ }
+ } else if (what == "checked")
r_ret = is_item_checked(idx);
else if (what == "id")
r_ret = get_item_id(idx);
@@ -95,7 +109,7 @@ void ItemListPlugin::_get_property_list(List<PropertyInfo> *p_list) const {
int flags = get_flags();
if (flags & FLAG_CHECKABLE) {
- p_list->push_back(PropertyInfo(Variant::BOOL, base + "checkable"));
+ p_list->push_back(PropertyInfo(Variant::BOOL, base + "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"));
p_list->push_back(PropertyInfo(Variant::BOOL, base + "checked"));
}
diff --git a/editor/plugins/item_list_editor_plugin.h b/editor/plugins/item_list_editor_plugin.h
index bd7d3db10d..d6a071b9b9 100644
--- a/editor/plugins/item_list_editor_plugin.h
+++ b/editor/plugins/item_list_editor_plugin.h
@@ -74,7 +74,9 @@ public:
virtual Ref<Texture> get_item_icon(int p_idx) const { return Ref<Texture>(); };
virtual void set_item_checkable(int p_idx, bool p_check) {}
+ virtual void set_item_radio_checkable(int p_idx, bool p_check) {}
virtual bool is_item_checkable(int p_idx) const { return false; };
+ virtual bool is_item_radio_checkable(int p_idx) const { return false; };
virtual void set_item_checked(int p_idx, bool p_checked) {}
virtual bool is_item_checked(int p_idx) const { return false; };
@@ -145,7 +147,9 @@ public:
virtual Ref<Texture> get_item_icon(int p_idx) const { return pp->get_item_icon(p_idx); }
virtual void set_item_checkable(int p_idx, bool p_check) { pp->set_item_as_checkable(p_idx, p_check); }
+ virtual void set_item_radio_checkable(int p_idx, bool p_check) { pp->set_item_as_radio_checkable(p_idx, p_check); }
virtual bool is_item_checkable(int p_idx) const { return pp->is_item_checkable(p_idx); }
+ virtual bool is_item_radio_checkable(int p_idx) const { return pp->is_item_radio_checkable(p_idx); }
virtual void set_item_checked(int p_idx, bool p_checked) { pp->set_item_checked(p_idx, p_checked); }
virtual bool is_item_checked(int p_idx) const { return pp->is_item_checked(p_idx); }
diff --git a/editor/plugins/mesh_instance_editor_plugin.cpp b/editor/plugins/mesh_instance_editor_plugin.cpp
index cb5f7ba76c..7ea2b27744 100644
--- a/editor/plugins/mesh_instance_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_editor_plugin.cpp
@@ -344,6 +344,10 @@ void MeshInstanceEditor::_create_outline_mesh() {
err_dialog->set_text(TTR("Mesh has not surface to create outlines from!"));
err_dialog->popup_centered_minsize();
return;
+ } else if (mesh->get_surface_count() == 1 && mesh->surface_get_primitive_type(0) != Mesh::PRIMITIVE_TRIANGLES) {
+ err_dialog->set_text(TTR("Mesh primitive type is not PRIMITIVE_TRIANGLES!"));
+ err_dialog->popup_centered_minsize();
+ return;
}
Ref<Mesh> mesho = mesh->create_outline(outline_size->get_value());
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 09388870f1..9193b3fbbf 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -39,9 +39,11 @@
#include "core/project_settings.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "editor/find_in_files.h"
#include "editor/node_dock.h"
#include "editor/script_editor_debugger.h"
#include "scene/main/viewport.h"
+#include "script_text_editor.h"
/*** SCRIPT EDITOR ****/
@@ -54,6 +56,8 @@ void ScriptEditorBase::_bind_methods() {
ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("request_save_history"));
ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));
+ // TODO This signal is no use for VisualScript...
+ ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
}
static bool _can_open_in_editor(Script *p_script) {
@@ -298,15 +302,9 @@ void ScriptEditor::_script_created(Ref<Script> p_script) {
void ScriptEditor::_goto_script_line2(int p_line) {
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return;
-
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
- if (!current)
- return;
-
- current->goto_line(p_line);
+ ScriptEditorBase *current = _get_current_editor();
+ if (current)
+ current->goto_line(p_line);
}
void ScriptEditor::_goto_script_line(REF p_script, int p_line) {
@@ -316,19 +314,22 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) {
if (edit(p_script, p_line, 0)) {
editor->push_item(p_script.ptr());
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return;
-
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
- if (!current)
- return;
-
- current->goto_line(p_line, true);
+ ScriptEditorBase *current = _get_current_editor();
+ if (current)
+ current->goto_line(p_line, true);
}
}
}
+ScriptEditorBase *ScriptEditor::_get_current_editor() const {
+
+ int selected = tab_container->get_current_tab();
+ if (selected < 0 || selected >= tab_container->get_child_count())
+ return NULL;
+
+ return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
+}
+
void ScriptEditor::_update_history_arrows() {
script_back->set_disabled(history_pos <= 0);
@@ -587,7 +588,7 @@ void ScriptEditor::_close_docs_tab() {
}
void ScriptEditor::_copy_script_path() {
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab()));
+ ScriptEditorBase *se = _get_current_editor();
Ref<Script> script = se->get_edited_script();
OS::get_singleton()->set_clipboard(script->get_path());
}
@@ -819,11 +820,8 @@ void ScriptEditor::_file_dialog_action(String p_file) {
Ref<Script> ScriptEditor::_get_current_script() {
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return NULL;
+ ScriptEditorBase *current = _get_current_editor();
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
if (current) {
return current->get_edited_script();
} else {
@@ -939,11 +937,7 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return;
-
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
+ ScriptEditorBase *current = _get_current_editor();
if (current) {
switch (p_option) {
@@ -1033,7 +1027,7 @@ void ScriptEditor::_menu_option(int p_option) {
_copy_script_path();
} break;
case SHOW_IN_FILE_SYSTEM: {
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab()));
+ ScriptEditorBase *se = _get_current_editor();
Ref<Script> script = se->get_edited_script();
FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock();
file_system_dock->navigate_to_path(script->get_path());
@@ -1223,6 +1217,17 @@ void ScriptEditor::_notification(int p_what) {
recent_scripts->set_as_minsize();
} break;
+ case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: {
+
+ if (is_visible()) {
+ find_in_files_button->show();
+ } else {
+ find_in_files->hide();
+ find_in_files_button->hide();
+ }
+
+ } break;
+
default:
break;
}
@@ -1230,15 +1235,11 @@ void ScriptEditor::_notification(int p_what) {
bool ScriptEditor::can_take_away_focus() const {
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return true;
-
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
- if (!current)
+ ScriptEditorBase *current = _get_current_editor();
+ if (current)
+ return current->can_lose_focus_on_node_selection();
+ else
return true;
-
- return current->can_lose_focus_on_node_selection();
}
void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) {
@@ -1315,20 +1316,13 @@ void ScriptEditor::ensure_focus_current() {
if (!is_inside_tree())
return;
- int cidx = tab_container->get_current_tab();
- if (cidx < 0 || cidx >= tab_container->get_tab_count())
- return;
-
- Control *c = Object::cast_to<Control>(tab_container->get_child(cidx));
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(c);
- if (!se)
- return;
- se->ensure_focus();
+ ScriptEditorBase *current = _get_current_editor();
+ if (current)
+ current->ensure_focus();
}
void ScriptEditor::_members_overview_selected(int p_idx) {
- Node *current = tab_container->get_child(tab_container->get_current_tab());
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current);
+ ScriptEditorBase *se = _get_current_editor();
if (!se) {
return;
}
@@ -1362,18 +1356,12 @@ void ScriptEditor::ensure_select_current() {
if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) {
- Node *current = tab_container->get_child(tab_container->get_current_tab());
-
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current);
+ ScriptEditorBase *se = _get_current_editor();
if (se) {
- Ref<Script> script = se->get_edited_script();
-
if (!grab_focus_block && is_visible_in_tree())
se->ensure_focus();
}
-
- EditorHelp *eh = Object::cast_to<EditorHelp>(current);
}
_update_selected_editor_menu();
@@ -1413,12 +1401,7 @@ struct _ScriptEditorItemData {
void ScriptEditor::_update_members_overview_visibility() {
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return;
-
- Node *current = tab_container->get_child(tab_container->get_current_tab());
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current);
+ ScriptEditorBase *se = _get_current_editor();
if (!se) {
members_overview->set_visible(false);
return;
@@ -1434,12 +1417,7 @@ void ScriptEditor::_update_members_overview_visibility() {
void ScriptEditor::_update_members_overview() {
members_overview->clear();
- int selected = tab_container->get_current_tab();
- if (selected < 0 || selected >= tab_container->get_child_count())
- return;
-
- Node *current = tab_container->get_child(tab_container->get_current_tab());
- ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current);
+ ScriptEditorBase *se = _get_current_editor();
if (!se) {
return;
}
@@ -1778,6 +1756,20 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool
}
ERR_FAIL_COND_V(!se, false);
+ bool highlighter_set = false;
+ for (int i = 0; i < syntax_highlighters_func_count; i++) {
+ SyntaxHighlighter *highlighter = syntax_highlighters_funcs[i]();
+ se->add_syntax_highlighter(highlighter);
+
+ if (!highlighter_set) {
+ List<String> languages = highlighter->get_supported_languages();
+ if (languages.find(p_script->get_language()->get_name())) {
+ se->set_syntax_highlighter(highlighter);
+ highlighter_set = true;
+ }
+ }
+ }
+
tab_container->add_child(se);
se->set_edited_script(p_script);
se->set_tooltip_request_func("_get_debug_tooltip", this);
@@ -1799,6 +1791,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool
se->connect("request_open_script_at_line", this, "_goto_script_line");
se->connect("go_to_help", this, "_help_class_goto");
se->connect("request_save_history", this, "_save_history");
+ se->connect("search_in_files_requested", this, "_on_find_in_files_requested");
//test for modification, maybe the script was not edited but was loaded
@@ -2494,6 +2487,14 @@ void ScriptEditor::_open_script_request(const String &p_path) {
}
}
+int ScriptEditor::syntax_highlighters_func_count = 0;
+CreateSyntaxHighlighterFunc ScriptEditor::syntax_highlighters_funcs[ScriptEditor::SYNTAX_HIGHLIGHTER_FUNC_MAX];
+
+void ScriptEditor::register_create_syntax_highlighter_function(CreateSyntaxHighlighterFunc p_func) {
+ ERR_FAIL_COND(syntax_highlighters_func_count == SYNTAX_HIGHLIGHTER_FUNC_MAX);
+ syntax_highlighters_funcs[syntax_highlighters_func_count++] = p_func;
+}
+
int ScriptEditor::script_editor_func_count = 0;
CreateScriptEditorFunc ScriptEditor::script_editor_funcs[ScriptEditor::SCRIPT_EDITOR_FUNC_MAX];
@@ -2508,6 +2509,48 @@ void ScriptEditor::_script_changed() {
NodeDock::singleton->update_lists();
}
+void ScriptEditor::_on_find_in_files_requested(String text) {
+
+ find_in_files_dialog->set_search_text(text);
+ find_in_files_dialog->popup_centered_minsize();
+}
+
+void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) {
+
+ Ref<Resource> res = ResourceLoader::load(fpath);
+ edit(res);
+
+ ScriptEditorBase *seb = _get_current_editor();
+
+ ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(seb);
+ if (ste) {
+ ste->goto_line_selection(line_number - 1, begin, end);
+ }
+}
+
+void ScriptEditor::_start_find_in_files(bool with_replace) {
+
+ FindInFiles *f = find_in_files->get_finder();
+
+ f->set_search_text(find_in_files_dialog->get_search_text());
+ f->set_match_case(find_in_files_dialog->is_match_case());
+ f->set_whole_words(find_in_files_dialog->is_match_case());
+ f->set_folder(find_in_files_dialog->get_folder());
+ f->set_filter(find_in_files_dialog->get_filter());
+
+ find_in_files->set_with_replace(with_replace);
+ find_in_files->start_search();
+
+ find_in_files_button->set_pressed(true);
+ find_in_files->show();
+}
+
+void ScriptEditor::_on_find_in_files_modified_files(PoolStringArray paths) {
+
+ _test_script_times_on_disk();
+ _update_modified_scripts_for_external_editor();
+}
+
void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_file_dialog_action", &ScriptEditor::_file_dialog_action);
@@ -2555,6 +2598,10 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_script_list_gui_input", &ScriptEditor::_script_list_gui_input);
ClassDB::bind_method("_script_changed", &ScriptEditor::_script_changed);
ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts);
+ ClassDB::bind_method("_on_find_in_files_requested", &ScriptEditor::_on_find_in_files_requested);
+ ClassDB::bind_method("_start_find_in_files", &ScriptEditor::_start_find_in_files);
+ ClassDB::bind_method("_on_find_in_files_result_selected", &ScriptEditor::_on_find_in_files_result_selected);
+ ClassDB::bind_method("_on_find_in_files_modified_files", &ScriptEditor::_on_find_in_files_modified_files);
ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw);
ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw);
@@ -2816,6 +2863,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
add_child(help_index);
help_index->connect("open_class", this, "_help_class_open");
+ find_in_files_dialog = memnew(FindInFilesDialog);
+ find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, this, "_start_find_in_files", varray(false));
+ find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, this, "_start_find_in_files", varray(true));
+ add_child(find_in_files_dialog);
+ find_in_files = memnew(FindInFilesPanel);
+ find_in_files_button = editor->add_bottom_panel_item(TTR("Search results"), find_in_files);
+ find_in_files_button->set_tooltip(TTR("Search in files"));
+ find_in_files->set_custom_minimum_size(Size2(0, 200));
+ find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, this, "_on_find_in_files_result_selected");
+ find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, this, "_on_find_in_files_modified_files");
+ find_in_files->hide();
+ find_in_files_button->hide();
+
history_pos = -1;
//debugger_gui->hide();
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index bcc604d990..9f37b18d7d 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -80,6 +80,9 @@ protected:
static void _bind_methods();
public:
+ virtual void add_syntax_highlighter(SyntaxHighlighter *p_highlighter) = 0;
+ virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter) = 0;
+
virtual void apply_code() = 0;
virtual Ref<Script> get_edited_script() const = 0;
virtual Vector<String> get_functions() = 0;
@@ -112,9 +115,12 @@ public:
ScriptEditorBase() {}
};
+typedef SyntaxHighlighter *(*CreateSyntaxHighlighterFunc)();
typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Script> &p_script);
class EditorScriptCodeCompletionCache;
+class FindInFilesDialog;
+class FindInFilesPanel;
class ScriptEditor : public PanelContainer {
@@ -213,13 +219,21 @@ class ScriptEditor : public PanelContainer {
ToolButton *script_back;
ToolButton *script_forward;
+ FindInFilesDialog *find_in_files_dialog;
+ FindInFilesPanel *find_in_files;
+ Button *find_in_files_button;
+
enum {
- SCRIPT_EDITOR_FUNC_MAX = 32
+ SCRIPT_EDITOR_FUNC_MAX = 32,
+ SYNTAX_HIGHLIGHTER_FUNC_MAX = 32
};
static int script_editor_func_count;
static CreateScriptEditorFunc script_editor_funcs[SCRIPT_EDITOR_FUNC_MAX];
+ static int syntax_highlighters_func_count;
+ static CreateSyntaxHighlighterFunc syntax_highlighters_funcs[SYNTAX_HIGHLIGHTER_FUNC_MAX];
+
struct ScriptHistory {
Control *control;
@@ -296,6 +310,8 @@ class ScriptEditor : public PanelContainer {
void _update_window_menu();
void _script_created(Ref<Script> p_script);
+ ScriptEditorBase *_get_current_editor() const;
+
void _save_layout();
void _editor_settings_changed();
void _autosave_scripts();
@@ -351,6 +367,11 @@ class ScriptEditor : public PanelContainer {
Ref<Script> _get_current_script();
Array _get_open_scripts() const;
+ void _on_find_in_files_requested(String text);
+ void _on_find_in_files_result_selected(String fpath, int line_number, int begin, int end);
+ void _start_find_in_files(bool with_replace);
+ void _on_find_in_files_modified_files(PoolStringArray paths);
+
static void _open_script_request(const String &p_path);
static ScriptEditor *script_editor;
@@ -399,7 +420,9 @@ public:
ScriptEditorDebugger *get_debugger() { return debugger; }
void set_live_auto_reload_running_scripts(bool p_enabled);
+ static void register_create_syntax_highlighter_function(CreateSyntaxHighlighterFunc p_func);
static void register_create_script_editor_function(CreateScriptEditorFunc p_func);
+
ScriptEditor(EditorNode *p_editor);
~ScriptEditor();
};
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index c8ea2f79fe..bcc575a7ac 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -529,6 +529,14 @@ void ScriptTextEditor::goto_line(int p_line, bool p_with_error) {
tx->call_deferred("cursor_set_line", p_line);
}
+void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
+ TextEdit *tx = code_editor->get_text_edit();
+ tx->unfold_line(p_line);
+ tx->call_deferred("cursor_set_line", p_line);
+ tx->call_deferred("cursor_set_column", p_begin);
+ tx->select(p_line, p_begin, p_line, p_end);
+}
+
void ScriptTextEditor::ensure_focus() {
code_editor->get_text_edit()->grab_focus();
@@ -573,6 +581,7 @@ void ScriptTextEditor::set_edited_script(const Ref<Script> &p_script) {
ERR_FAIL_COND(!script.is_null());
script = p_script;
+ _set_theme_for_script();
code_editor->get_text_edit()->set_text(script->get_source_code());
code_editor->get_text_edit()->clear_undo_history();
@@ -580,8 +589,6 @@ void ScriptTextEditor::set_edited_script(const Ref<Script> &p_script) {
emit_signal("name_changed");
code_editor->update_line_and_column();
-
- _set_theme_for_script();
}
void ScriptTextEditor::_validate_script() {
@@ -789,6 +796,26 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
emit_signal("go_to_help", "class_method:" + result.class_name + ":" + result.class_member);
} break;
+ case ScriptLanguage::LookupResult::RESULT_CLASS_ENUM: {
+
+ StringName cname = result.class_name;
+ StringName success;
+ while (true) {
+ success = ClassDB::get_integer_constant_enum(cname, result.class_member, true);
+ if (success != StringName()) {
+ result.class_name = cname;
+ cname = ClassDB::get_parent_class(cname);
+ } else {
+ break;
+ }
+ }
+
+ emit_signal("go_to_help", "class_enum:" + result.class_name + ":" + result.class_member);
+
+ } break;
+ case ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE: {
+ emit_signal("go_to_help", "class_global:" + result.class_name + ":" + result.class_member);
+ } break;
}
}
}
@@ -1154,6 +1181,15 @@ void ScriptTextEditor::_edit_option(int p_op) {
code_editor->get_find_replace_bar()->popup_replace();
} break;
+ case SEARCH_IN_FILES: {
+
+ String selected_text = code_editor->get_text_edit()->get_selection_text();
+
+ // Yep, because it doesn't make sense to instance this dialog for every single script open...
+ // So this will be delegated to the ScriptEditor
+ emit_signal("search_in_files_requested", selected_text);
+
+ } break;
case SEARCH_LOCATE_FUNCTION: {
quick_open->popup(get_functions());
@@ -1245,11 +1281,26 @@ void ScriptTextEditor::_edit_option(int p_op) {
}
}
+void ScriptTextEditor::add_syntax_highlighter(SyntaxHighlighter *p_highlighter) {
+ highlighters[p_highlighter->get_name()] = p_highlighter;
+ highlighter_menu->get_popup()->add_item(p_highlighter->get_name());
+}
+
+void ScriptTextEditor::set_syntax_highlighter(SyntaxHighlighter *p_highlighter) {
+ TextEdit *te = code_editor->get_text_edit();
+ te->_set_syntax_highlighting(p_highlighter);
+}
+
+void ScriptTextEditor::_change_syntax_highlighter(int p_idx) {
+ set_syntax_highlighter(highlighters[highlighter_menu->get_popup()->get_item_text(p_idx)]);
+}
+
void ScriptTextEditor::_bind_methods() {
ClassDB::bind_method("_validate_script", &ScriptTextEditor::_validate_script);
ClassDB::bind_method("_load_theme_settings", &ScriptTextEditor::_load_theme_settings);
ClassDB::bind_method("_breakpoint_toggled", &ScriptTextEditor::_breakpoint_toggled);
+ ClassDB::bind_method("_change_syntax_highlighter", &ScriptTextEditor::_change_syntax_highlighter);
ClassDB::bind_method("_edit_option", &ScriptTextEditor::_edit_option);
ClassDB::bind_method("_goto_line", &ScriptTextEditor::_goto_line);
ClassDB::bind_method("_lookup_symbol", &ScriptTextEditor::_lookup_symbol);
@@ -1626,6 +1677,8 @@ ScriptTextEditor::ScriptTextEditor() {
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
search_menu->get_popup()->add_separator();
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES);
+ search_menu->get_popup()->add_separator();
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
search_menu->get_popup()->add_separator();
@@ -1635,6 +1688,14 @@ ScriptTextEditor::ScriptTextEditor() {
edit_hb->add_child(edit_menu);
+ highlighters["Standard"] = NULL;
+
+ highlighter_menu = memnew(MenuButton);
+ highlighter_menu->set_text(TTR("Syntax Highlighter"));
+ highlighter_menu->get_popup()->add_item("Standard");
+ highlighter_menu->get_popup()->connect("id_pressed", this, "_change_syntax_highlighter");
+ edit_hb->add_child(highlighter_menu);
+
quick_open = memnew(ScriptEditorQuickOpen);
add_child(quick_open);
quick_open->connect("goto_line", this, "_goto_line");
@@ -1697,7 +1758,9 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3);
ED_SHORTCUT("script_text_editor/replace", TTR("Replace.."), KEY_MASK_CMD | KEY_R);
- ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_F);
+ ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F);
+
+ ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F);
ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line.."), KEY_MASK_CMD | KEY_L);
ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 22e8fbce25..a93e1a6fa8 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -49,6 +49,7 @@ class ScriptTextEditor : public ScriptEditorBase {
HBoxContainer *edit_hb;
MenuButton *edit_menu;
+ MenuButton *highlighter_menu;
MenuButton *search_menu;
PopupMenu *context_menu;
@@ -105,6 +106,7 @@ class ScriptTextEditor : public ScriptEditorBase {
SEARCH_REPLACE,
SEARCH_LOCATE_FUNCTION,
SEARCH_GOTO_LINE,
+ SEARCH_IN_FILES,
DEBUG_TOGGLE_BREAKPOINT,
DEBUG_REMOVE_ALL_BREAKPOINTS,
DEBUG_GOTO_NEXT_BREAKPOINT,
@@ -125,6 +127,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ Map<String, SyntaxHighlighter *> highlighters;
+ void _change_syntax_highlighter(int p_idx);
+
void _edit_option(int p_op);
void _make_context_menu(bool p_selection, bool p_color, bool p_can_fold, bool p_is_folded);
void _text_edit_gui_input(const Ref<InputEvent> &ev);
@@ -145,6 +150,9 @@ protected:
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
public:
+ virtual void add_syntax_highlighter(SyntaxHighlighter *p_highlighter);
+ virtual void set_syntax_highlighter(SyntaxHighlighter *p_highlighter);
+
virtual void apply_code();
virtual Ref<Script> get_edited_script() const;
virtual Vector<String> get_functions();
@@ -163,6 +171,7 @@ public:
virtual void tag_saved_version();
virtual void goto_line(int p_line, bool p_with_error = false);
+ void goto_line_selection(int p_line, int p_begin, int p_end);
virtual void reload(bool p_soft);
virtual void get_breakpoints(List<int> *p_breakpoints);
diff --git a/editor/plugins/shader_graph_editor_plugin.cpp b/editor/plugins/shader_graph_editor_plugin.cpp
index 59085c203f..e1d28cc215 100644
--- a/editor/plugins/shader_graph_editor_plugin.cpp
+++ b/editor/plugins/shader_graph_editor_plugin.cpp
@@ -2769,8 +2769,6 @@ void ShaderGraphEditor::_popup_requested(const Vector2 &p_position)
popup->set_global_position(p_position);
popup->set_size( Size2( 200, 0) );
popup->popup();
- popup->call_deferred("grab_click_focus");
- popup->set_invalidate_click_until_motion();
}
void ShaderGraphEditor::_notification(int p_what) {
diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp
index e484140527..9d7c582e0e 100644
--- a/editor/plugins/spatial_editor_plugin.cpp
+++ b/editor/plugins/spatial_editor_plugin.cpp
@@ -886,8 +886,6 @@ void SpatialEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
selection_menu->set_global_position(b->get_global_position());
selection_menu->popup();
- selection_menu->call_deferred("grab_click_focus");
- selection_menu->set_invalidate_click_until_motion();
}
}
@@ -3353,14 +3351,14 @@ SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, Ed
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR);
view_menu->get_popup()->add_separator();
- view_menu->get_popup()->add_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE);
- view_menu->get_popup()->add_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL);
+ view_menu->get_popup()->add_radio_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE);
+ view_menu->get_popup()->add_radio_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);
view_menu->get_popup()->add_separator();
- view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTR("Display Normal")), VIEW_DISPLAY_NORMAL);
- view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);
- view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);
- view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS);
+ view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTR("Display Normal")), VIEW_DISPLAY_NORMAL);
+ view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);
+ view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);
+ view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT);
@@ -5111,12 +5109,12 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) {
accept = memnew(AcceptDialog);
editor->get_gui_base()->add_child(accept);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT);
- p->add_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT);
+ p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS);
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index d8d0a6f013..71a3c90795 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -458,8 +458,6 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) {
List<StringName> anim_names;
- anim_names.sort_custom<StringName::AlphCompare>();
-
frames->get_animation_list(&anim_names);
anim_names.sort_custom<StringName::AlphCompare>();
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index 4367fe8976..5ba3931689 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -805,12 +805,10 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) {
snap_mode_button->set_text(TTR("<None>"));
PopupMenu *p = snap_mode_button->get_popup();
p->set_hide_on_checkable_item_selection(false);
- p->add_item(TTR("<None>"), 0);
- p->add_item(TTR("Pixel Snap"), 1);
- p->add_item(TTR("Grid Snap"), 2);
- p->add_item(TTR("Auto Slice"), 3);
- for (int i = 0; i < 4; i++)
- p->set_item_as_checkable(i, true);
+ p->add_radio_check_item(TTR("<None>"), 0);
+ p->add_radio_check_item(TTR("Pixel Snap"), 1);
+ p->add_radio_check_item(TTR("Grid Snap"), 2);
+ p->add_radio_check_item(TTR("Auto Slice"), 3);
p->set_item_checked(0, true);
p->connect("id_pressed", this, "_set_snap_mode");
hb_grid = memnew(HBoxContainer);
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 295d9439ad..550dfb3ae1 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -692,6 +692,10 @@ ThemeEditor::ThemeEditor() {
test_menu_button->get_popup()->add_check_item(TTR("Check Item"));
test_menu_button->get_popup()->add_check_item(TTR("Checked Item"));
test_menu_button->get_popup()->set_item_checked(2, true);
+ test_menu_button->get_popup()->add_separator();
+ test_menu_button->get_popup()->add_check_item(TTR("Radio Item"));
+ test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item"));
+ test_menu_button->get_popup()->set_item_checked(5, true);
first_vb->add_child(test_menu_button);
OptionButton *test_option_button = memnew(OptionButton);
diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp
index c5c7272ed2..14c584fa35 100644
--- a/editor/plugins/tile_map_editor_plugin.cpp
+++ b/editor/plugins/tile_map_editor_plugin.cpp
@@ -78,6 +78,7 @@ void TileMapEditor::_notification(int p_what) {
p->set_item_icon(p->get_item_index(OPTION_PAINTING), get_icon("Edit", "EditorIcons"));
p->set_item_icon(p->get_item_index(OPTION_PICK_TILE), get_icon("ColorPick", "EditorIcons"));
p->set_item_icon(p->get_item_index(OPTION_SELECT), get_icon("ToolSelect", "EditorIcons"));
+ p->set_item_icon(p->get_item_index(OPTION_MOVE), get_icon("ToolMove", "EditorIcons"));
p->set_item_icon(p->get_item_index(OPTION_DUPLICATE), get_icon("Duplicate", "EditorIcons"));
p->set_item_icon(p->get_item_index(OPTION_ERASE_SELECTION), get_icon("Remove", "EditorIcons"));
@@ -156,6 +157,14 @@ void TileMapEditor::_menu_option(int p_option) {
undo_redo->commit_action();
} break;
+ case OPTION_MOVE: {
+
+ if (selection_active) {
+ _update_copydata();
+ tool = TOOL_MOVING;
+ canvas_item_editor->update();
+ }
+ } break;
}
}
@@ -829,6 +838,29 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
copydata.clear();
canvas_item_editor->update();
+ } else if (tool == TOOL_MOVING) {
+
+ Point2 ofs = over_tile - rectangle.position;
+
+ undo_redo->create_action(TTR("Move"));
+ undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+ for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
+ for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
+
+ _set_cell(Point2i(j, i), TileMap::INVALID_CELL, false, false, false);
+ }
+ }
+ for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
+
+ _set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose);
+ }
+ undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
+ undo_redo->commit_action();
+
+ copydata.clear();
+ selection_active = false;
+
+ canvas_item_editor->update();
} else if (tool == TOOL_SELECTING) {
@@ -888,6 +920,16 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
return true;
}
+ if (tool == TOOL_MOVING) {
+
+ tool = TOOL_NONE;
+ copydata.clear();
+
+ canvas_item_editor->update();
+
+ return true;
+ }
+
if (tool == TOOL_NONE) {
paint_undo.clear();
@@ -1095,7 +1137,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (k->get_scancode() == KEY_ESCAPE) {
- if (tool == TOOL_DUPLICATING)
+ if (tool == TOOL_DUPLICATING || tool == TOOL_MOVING)
copydata.clear();
else if (tool == TOOL_SELECTING || selection_active)
selection_active = false;
@@ -1150,6 +1192,14 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
return true;
}
}
+ if (ED_IS_SHORTCUT("tile_map_editor/move_selection", p_event)) {
+ if (selection_active) {
+ _update_copydata();
+ tool = TOOL_MOVING;
+ canvas_item_editor->update();
+ return true;
+ }
+ }
if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) {
search_box->select_all();
search_box->grab_focus();
@@ -1159,18 +1209,21 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("tile_map_editor/mirror_x", p_event)) {
flip_h = !flip_h;
mirror_x->set_pressed(flip_h);
+ _update_transform_buttons();
canvas_item_editor->update();
return true;
}
if (ED_IS_SHORTCUT("tile_map_editor/mirror_y", p_event)) {
flip_v = !flip_v;
mirror_y->set_pressed(flip_v);
+ _update_transform_buttons();
canvas_item_editor->update();
return true;
}
if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) {
transpose = !transpose;
transp->set_pressed(transpose);
+ _update_transform_buttons();
canvas_item_editor->update();
return true;
}
@@ -1343,7 +1396,7 @@ void TileMapEditor::forward_draw_over_viewport(Control *p_overlay) {
_draw_cell(id, Point2i(j, i), flip_h, flip_v, transpose, xform);
}
}
- } else if (tool == TOOL_DUPLICATING) {
+ } else if (tool == TOOL_DUPLICATING || tool == TOOL_MOVING) {
if (copydata.empty())
return;
@@ -1590,6 +1643,7 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) {
p->add_item(TTR("Pick Tile"), OPTION_PICK_TILE, KEY_CONTROL);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_MASK_CMD + KEY_B), OPTION_SELECT);
+ p->add_shortcut(ED_SHORTCUT("tile_map_editor/move_selection", TTR("Move Selection"), KEY_MASK_CMD + KEY_M), OPTION_MOVE);
p->add_shortcut(ED_SHORTCUT("tile_map_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_CMD + KEY_D), OPTION_DUPLICATE);
p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION);
p->add_separator();
diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h
index 2d582d030b..3257901c88 100644
--- a/editor/plugins/tile_map_editor_plugin.h
+++ b/editor/plugins/tile_map_editor_plugin.h
@@ -61,6 +61,7 @@ class TileMapEditor : public VBoxContainer {
TOOL_BUCKET,
TOOL_PICKING,
TOOL_DUPLICATING,
+ TOOL_MOVING
};
enum Options {
@@ -72,6 +73,7 @@ class TileMapEditor : public VBoxContainer {
OPTION_ERASE_SELECTION,
OPTION_PAINTING,
OPTION_FIX_INVALID,
+ OPTION_MOVE
};
TileMap *node;
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
index 2311439728..41692e805f 100644
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ b/editor/plugins/tile_set_editor_plugin.cpp
@@ -278,10 +278,11 @@ void TileSetEditor::_changed_callback(Object *p_changed, const char *p_prop) {
preview->set_region_rect(tileset->tile_get_region(get_current_tile()));
} else if (p_prop == StringName("name")) {
update_tile_list_icon();
- } else if (p_prop == StringName("texture") || p_prop == StringName("tile_mode")) {
+ } else if (p_prop == StringName("texture") || p_prop == StringName("modulate") || p_prop == StringName("tile_mode")) {
_on_tile_list_selected(get_current_tile());
workspace->update();
preview->set_texture(tileset->tile_get_texture(get_current_tile()));
+ preview->set_modulate(tileset->tile_get_modulate(get_current_tile()));
preview->set_region_rect(tileset->tile_get_region(get_current_tile()));
if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE)
property_editor->show();
@@ -578,6 +579,7 @@ void TileSetEditor::_on_tile_list_selected(int p_index) {
if (get_current_tile() >= 0) {
current_item_index = p_index;
preview->set_texture(tileset->tile_get_texture(get_current_tile()));
+ preview->set_modulate(tileset->tile_get_modulate(get_current_tile()));
preview->set_region_rect(tileset->tile_get_region(get_current_tile()));
workspace->set_custom_minimum_size(tileset->tile_get_region(get_current_tile()).size);
update_workspace_tile_mode();
@@ -1736,6 +1738,7 @@ void TileSetEditor::update_tile_list() {
region.position += pos;
}
tile_list->set_item_icon_region(tile_list->get_item_count() - 1, region);
+ tile_list->set_item_icon_modulate(tile_list->get_item_count() - 1, tileset->tile_get_modulate(E->get()));
}
if (tile_list->get_item_count() > 0 && selected_tile < tile_list->get_item_count()) {
tile_list->select(selected_tile);
@@ -1763,6 +1766,7 @@ void TileSetEditor::update_tile_list_icon() {
tile_list->set_item_metadata(current_idx, E->get());
tile_list->set_item_icon(current_idx, tileset->tile_get_texture(E->get()));
tile_list->set_item_icon_region(current_idx, region);
+ tile_list->set_item_icon_modulate(current_idx, tileset->tile_get_modulate(E->get()));
tile_list->set_item_text(current_idx, tileset->tile_get_name(E->get()));
current_idx += 1;
}
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 75523cd843..6e3be343ba 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -31,6 +31,7 @@
#include "project_settings_editor.h"
#include "core/global_constants.h"
+#include "core/input_map.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "core/translation.h"
@@ -212,7 +213,7 @@ void ProjectSettingsEditor::_device_input_add() {
Ref<InputEventMouseButton> mb;
mb.instance();
mb->set_button_index(device_index->get_selected() + 1);
- mb->set_device(device_id->get_value());
+ mb->set_device(_get_current_device());
for (int i = 0; i < arr.size(); i++) {
@@ -233,7 +234,7 @@ void ProjectSettingsEditor::_device_input_add() {
jm.instance();
jm->set_axis(device_index->get_selected() >> 1);
jm->set_axis_value(device_index->get_selected() & 1 ? 1 : -1);
- jm->set_device(device_id->get_value());
+ jm->set_device(_get_current_device());
for (int i = 0; i < arr.size(); i++) {
@@ -254,7 +255,7 @@ void ProjectSettingsEditor::_device_input_add() {
jb.instance();
jb->set_button_index(device_index->get_selected());
- jb->set_device(device_id->get_value());
+ jb->set_device(_get_current_device());
for (int i = 0; i < arr.size(); i++) {
@@ -289,6 +290,20 @@ void ProjectSettingsEditor::_device_input_add() {
_show_last_added(ie, name);
}
+void ProjectSettingsEditor::_set_current_device(int i_device) {
+ device_id->select(i_device + 1);
+}
+
+int ProjectSettingsEditor::_get_current_device() {
+ return device_id->get_selected() - 1;
+}
+
+String ProjectSettingsEditor::_get_device_string(int i_device) {
+ if (i_device == InputMap::ALL_DEVICES)
+ return TTR("All Devices");
+ return TTR("Device") + " " + itos(i_device);
+}
+
void ProjectSettingsEditor::_press_a_key_confirm() {
if (last_wait_for_key.is_null())
@@ -422,10 +437,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even
Ref<InputEventMouseButton> mb = p_exiting_event;
if (mb.is_valid()) {
device_index->select(mb->get_button_index() - 1);
- device_id->set_value(mb->get_device());
+ _set_current_device(mb->get_device());
device_input->get_ok()->set_text(TTR("Change"));
} else {
- device_id->set_value(0);
+ _set_current_device(0);
device_input->get_ok()->set_text(TTR("Add"));
}
} break;
@@ -443,10 +458,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even
Ref<InputEventJoypadMotion> jm = p_exiting_event;
if (jm.is_valid()) {
device_index->select(jm->get_axis() * 2 + (jm->get_axis_value() > 0 ? 1 : 0));
- device_id->set_value(jm->get_device());
+ _set_current_device(jm->get_device());
device_input->get_ok()->set_text(TTR("Change"));
} else {
- device_id->set_value(0);
+ _set_current_device(0);
device_input->get_ok()->set_text(TTR("Add"));
}
} break;
@@ -464,10 +479,10 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even
Ref<InputEventJoypadButton> jb = p_exiting_event;
if (jb.is_valid()) {
device_index->select(jb->get_button_index());
- device_id->set_value(jb->get_device());
+ _set_current_device(jb->get_device());
device_input->get_ok()->set_text(TTR("Change"));
} else {
- device_id->set_value(0);
+ _set_current_device(0);
device_input->get_ok()->set_text(TTR("Add"));
}
@@ -676,7 +691,7 @@ void ProjectSettingsEditor::_update_actions() {
if (jb.is_valid()) {
- String str = TTR("Device") + " " + itos(jb->get_device()) + ", " + TTR("Button") + " " + itos(jb->get_button_index());
+ String str = _get_device_string(jb->get_device()) + ", " + TTR("Button") + " " + itos(jb->get_button_index());
if (jb->get_button_index() >= 0 && jb->get_button_index() < JOY_BUTTON_MAX)
str += String() + " (" + _button_names[jb->get_button_index()] + ").";
else
@@ -689,7 +704,7 @@ void ProjectSettingsEditor::_update_actions() {
Ref<InputEventMouseButton> mb = ie;
if (mb.is_valid()) {
- String str = TTR("Device") + " " + itos(mb->get_device()) + ", ";
+ String str = _get_device_string(mb->get_device()) + ", ";
switch (mb->get_button_index()) {
case BUTTON_LEFT: str += TTR("Left Button."); break;
case BUTTON_RIGHT: str += TTR("Right Button."); break;
@@ -710,7 +725,7 @@ void ProjectSettingsEditor::_update_actions() {
int ax = jm->get_axis();
int n = 2 * ax + (jm->get_axis_value() < 0 ? 0 : 1);
String desc = _axis_names[n];
- String str = TTR("Device") + " " + itos(jm->get_device()) + ", " + TTR("Axis") + " " + itos(ax) + " " + (jm->get_axis_value() < 0 ? "-" : "+") + desc + ".";
+ String str = _get_device_string(jm->get_device()) + ", " + TTR("Axis") + " " + itos(ax) + " " + (jm->get_axis_value() < 0 ? "-" : "+") + desc + ".";
action->set_text(0, str);
action->set_icon(0, get_icon("JoyAxis", "EditorIcons"));
}
@@ -1781,8 +1796,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
l->set_text(TTR("Device:"));
vbc_left->add_child(l);
- device_id = memnew(SpinBox);
- device_id->set_value(0);
+ device_id = memnew(OptionButton);
+ for (int i = -1; i < 8; i++)
+ device_id->add_item(_get_device_string(i));
+ _set_current_device(0);
vbc_left->add_child(device_id);
VBoxContainer *vbc_right = memnew(VBoxContainer);
diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h
index 9364b0ff2c..0ced88d7f6 100644
--- a/editor/project_settings_editor.h
+++ b/editor/project_settings_editor.h
@@ -80,7 +80,7 @@ class ProjectSettingsEditor : public AcceptDialog {
ConfirmationDialog *press_a_key;
Label *press_a_key_label;
ConfirmationDialog *device_input;
- SpinBox *device_id;
+ OptionButton *device_id;
OptionButton *device_index;
Label *device_index_label;
MenuButton *popup_copy_to_feature;
@@ -170,6 +170,10 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ int _get_current_device();
+ void _set_current_device(int i_device);
+ String _get_device_string(int i_device);
+
public:
void add_translation(const String &p_translation);
static ProjectSettingsEditor *get_singleton() { return singleton; }
diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp
index 043add046e..4bd70d0c29 100644
--- a/editor/property_editor.cpp
+++ b/editor/property_editor.cpp
@@ -285,6 +285,11 @@ void CustomPropertyEditor::_menu_option(int p_which) {
}
Object *obj = ClassDB::instance(intype);
+
+ if (!obj) {
+ obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource");
+ }
+
ERR_BREAK(!obj);
Resource *res = Object::cast_to<Resource>(obj);
ERR_BREAK(!res);
@@ -877,6 +882,12 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
} else if (hint_text != "") {
int idx = 0;
+ Vector<EditorData::CustomType> custom_resources;
+
+ if (EditorNode::get_editor_data().get_custom_types().has("Resource")) {
+ custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"];
+ }
+
for (int i = 0; i < hint_text.get_slice_count(","); i++) {
String base = hint_text.get_slice(",", i);
@@ -885,6 +896,11 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
valid_inheritors.insert(base);
List<StringName> inheritors;
ClassDB::get_inheriters_from_class(base.strip_edges(), &inheritors);
+
+ for (int i = 0; i < custom_resources.size(); i++) {
+ inheritors.push_back(custom_resources[i].name);
+ }
+
List<StringName>::Element *E = inheritors.front();
while (E) {
valid_inheritors.insert(E->get());
@@ -893,14 +909,34 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
for (Set<String>::Element *E = valid_inheritors.front(); E; E = E->next()) {
String t = E->get();
- if (!ClassDB::can_instance(t))
+
+ bool is_custom_resource = false;
+ Ref<Texture> icon;
+ if (!custom_resources.empty()) {
+ for (int i = 0; i < custom_resources.size(); i++) {
+ if (custom_resources[i].name == t) {
+ is_custom_resource = true;
+ if (custom_resources[i].icon.is_valid())
+ icon = custom_resources[i].icon;
+ break;
+ }
+ }
+ }
+
+ if (!is_custom_resource && !ClassDB::can_instance(t))
continue;
+
inheritors_array.push_back(t);
int id = TYPE_BASE_ID + idx;
- if (has_icon(t, "EditorIcons")) {
- menu->add_icon_item(get_icon(t, "EditorIcons"), vformat(TTR("New %s"), t), id);
+ if (!icon.is_valid() && has_icon(t, "EditorIcons")) {
+ icon = get_icon(t, "EditorIcons");
+ }
+
+ if (icon.is_valid()) {
+
+ menu->add_icon_item(icon, vformat(TTR("New %s"), t), id);
} else {
menu->add_item(vformat(TTR("New %s"), t), id);
@@ -1094,6 +1130,10 @@ void CustomPropertyEditor::_type_create_selected(int p_idx) {
Object *obj = ClassDB::instance(intype);
+ if (!obj) {
+ obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource");
+ }
+
ERR_FAIL_COND(!obj);
Resource *res = Object::cast_to<Resource>(obj);
@@ -1291,6 +1331,11 @@ void CustomPropertyEditor::_action_pressed(int p_which) {
if (hint == PROPERTY_HINT_RESOURCE_TYPE) {
Object *obj = ClassDB::instance(intype);
+
+ if (!obj) {
+ obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource");
+ }
+
ERR_BREAK(!obj);
Resource *res = Object::cast_to<Resource>(obj);
ERR_BREAK(!res);
@@ -3939,7 +3984,7 @@ void PropertyEditor::_edit_button(Object *p_item, int p_column, int p_button) {
if (_might_be_in_instance() && _get_instanced_node_original_property(prop, vorig)) {
- _edit_set(prop, vorig);
+ _edit_set(prop, vorig.duplicate(true)); // Set, making sure to duplicate arrays properly
return;
}
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index d5ec858c37..b13b238fd7 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -916,9 +916,6 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP
if (!r_rem_anims)
r_rem_anims = &rem_anims;
- if (!bool(EDITOR_DEF("editors/animation/autorename_animation_tracks", true)))
- return;
-
if (!p_base) {
p_base = edited_scene;
@@ -927,7 +924,54 @@ void SceneTreeDock::perform_node_renames(Node *p_base, List<Pair<NodePath, NodeP
if (!p_base)
return;
- if (Object::cast_to<AnimationPlayer>(p_base)) {
+ // Renaming node paths used in script instances
+ if (p_base->get_script_instance()) {
+
+ ScriptInstance *si = p_base->get_script_instance();
+
+ if (si) {
+
+ List<PropertyInfo> properties;
+ si->get_property_list(&properties);
+
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+
+ String propertyname = E->get().name;
+ Variant p = p_base->get(propertyname);
+ if (p.get_type() == Variant::NODE_PATH) {
+
+ // Goes through all paths to check if its matching
+ for (List<Pair<NodePath, NodePath> >::Element *E = p_renames->front(); E; E = E->next()) {
+
+ NodePath root_path = p_base->get_path();
+
+ NodePath rel_path_old = root_path.rel_path_to(E->get().first);
+
+ NodePath rel_path_new = E->get().second;
+
+ // if not empty, get new relative path
+ if (E->get().second != NodePath()) {
+ rel_path_new = root_path.rel_path_to(E->get().second);
+ }
+
+ // if old path detected, then it needs to be replaced with the new one
+ if (p == rel_path_old) {
+
+ editor_data->get_undo_redo().add_do_property(p_base, propertyname, rel_path_new);
+ editor_data->get_undo_redo().add_undo_property(p_base, propertyname, rel_path_old);
+
+ p_base->set(propertyname, rel_path_new);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ bool autorename_animation_tracks = bool(EDITOR_DEF("editors/animation/autorename_animation_tracks", true));
+
+ if (autorename_animation_tracks && Object::cast_to<AnimationPlayer>(p_base)) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_base);
List<StringName> anims;
@@ -1158,7 +1202,30 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
editor_data->get_undo_redo().add_do_method(new_parent, "move_child", node, p_position_in_parent + inc);
ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger();
+ String old_name = former_names[ni];
String new_name = new_parent->validate_child_name(node);
+
+ // name was modified, fix the path renames
+ if (old_name.casecmp_to(new_name) != 0) {
+
+ // Fix the to name to have the new name
+ NodePath old_new_name = path_renames[ni].second;
+ NodePath new_path;
+
+ Vector<StringName> unfixed_new_names = old_new_name.get_names();
+ Vector<StringName> fixed_new_names;
+
+ // Get last name and replace with fixed new name
+ for (int a = 0; a < (unfixed_new_names.size() - 1); a++) {
+ fixed_new_names.push_back(unfixed_new_names[a]);
+ }
+ fixed_new_names.push_back(new_name);
+
+ NodePath fixed_node_path = NodePath(fixed_new_names, true);
+
+ path_renames[ni].second = fixed_node_path;
+ }
+
editor_data->get_undo_redo().add_do_method(sed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, -1);
editor_data->get_undo_redo().add_undo_method(sed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)) + "/" + new_name), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index());
@@ -1794,6 +1861,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
subresources.clear();
menu_subresources->clear();
+ menu_subresources->set_size(Size2(1, 1));
_add_children_to_popup(selection.front()->get(), 0);
if (menu->get_item_count() > 0)
menu->add_separator();
diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp
index d75ef5df8a..36d7a83930 100644
--- a/editor/settings_config_dialog.cpp
+++ b/editor/settings_config_dialog.cpp
@@ -199,6 +199,14 @@ void EditorSettingsDialog::_update_icons() {
void EditorSettingsDialog::_update_shortcuts() {
+ Map<String, bool> collapsed;
+
+ if (shortcuts->get_root() && shortcuts->get_root()->get_children()) {
+ for (TreeItem *item = shortcuts->get_root()->get_children(); item; item = item->get_next()) {
+ collapsed[item->get_text(0)] = item->is_collapsed();
+ }
+ }
+
shortcuts->clear();
List<String> slist;
@@ -223,7 +231,13 @@ void EditorSettingsDialog::_update_shortcuts() {
section = sections[section_name];
} else {
section = shortcuts->create_item(root);
- section->set_text(0, section_name.capitalize());
+
+ String item_name = section_name.capitalize();
+ section->set_text(0, item_name);
+
+ if (collapsed.has(item_name)) {
+ section->set_collapsed(collapsed[item_name]);
+ }
sections[section_name] = section;
section->set_custom_bg_color(0, get_color("prop_subsection", "Editor"));