|
@@ -1249,6 +1249,7 @@ struct ProjectListComparator {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+const char *ProjectList::SIGNAL_LIST_CHANGED = "list_changed";
|
|
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
|
|
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
|
|
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
|
|
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
|
|
|
|
|
|
@@ -1356,7 +1357,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
|
|
return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
|
|
return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
|
|
}
|
|
}
|
|
|
|
|
|
-void ProjectList::migrate_config() {
|
|
|
|
|
|
+void ProjectList::_migrate_config() {
|
|
// Proposal #1637 moved the project list from editor settings to a separate config file
|
|
// Proposal #1637 moved the project list from editor settings to a separate config file
|
|
// If the new config file doesn't exist, populate it from EditorSettings
|
|
// If the new config file doesn't exist, populate it from EditorSettings
|
|
if (FileAccess::exists(_config_path)) {
|
|
if (FileAccess::exists(_config_path)) {
|
|
@@ -1388,9 +1389,10 @@ void ProjectList::migrate_config() {
|
|
save_config();
|
|
save_config();
|
|
}
|
|
}
|
|
|
|
|
|
-void ProjectList::load_projects() {
|
|
|
|
|
|
+void ProjectList::update_project_list() {
|
|
// This is a full, hard reload of the list. Don't call this unless really required, it's expensive.
|
|
// This is a full, hard reload of the list. Don't call this unless really required, it's expensive.
|
|
// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.
|
|
// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.
|
|
|
|
+ // FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper.
|
|
|
|
|
|
// Clear whole list
|
|
// Clear whole list
|
|
for (int i = 0; i < _projects.size(); ++i) {
|
|
for (int i = 0; i < _projects.size(); ++i) {
|
|
@@ -1421,6 +1423,7 @@ void ProjectList::load_projects() {
|
|
update_dock_menu();
|
|
update_dock_menu();
|
|
|
|
|
|
set_v_scroll(0);
|
|
set_v_scroll(0);
|
|
|
|
+ emit_signal(SNAME(SIGNAL_LIST_CHANGED));
|
|
}
|
|
}
|
|
|
|
|
|
void ProjectList::update_dock_menu() {
|
|
void ProjectList::update_dock_menu() {
|
|
@@ -1678,6 +1681,48 @@ void ProjectList::erase_missing_projects() {
|
|
save_config();
|
|
save_config();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) {
|
|
|
|
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
|
+ Error error = da->change_dir(p_path);
|
|
|
|
+ ERR_FAIL_COND_MSG(error != OK, vformat("Failed to open the path \"%s\" for scanning (code %d).", p_path, error));
|
|
|
|
+
|
|
|
|
+ da->list_dir_begin();
|
|
|
|
+ String n = da->get_next();
|
|
|
|
+ while (!n.is_empty()) {
|
|
|
|
+ if (da->current_is_dir() && n[0] != '.') {
|
|
|
|
+ _scan_folder_recursive(da->get_current_dir().path_join(n), r_projects);
|
|
|
|
+ } else if (n == "project.godot") {
|
|
|
|
+ r_projects->push_back(da->get_current_dir());
|
|
|
|
+ }
|
|
|
|
+ n = da->get_next();
|
|
|
|
+ }
|
|
|
|
+ da->list_dir_end();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ProjectList::find_projects(const String &p_path) {
|
|
|
|
+ PackedStringArray paths = { p_path };
|
|
|
|
+ find_projects_multiple(paths);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) {
|
|
|
|
+ List<String> projects;
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < p_paths.size(); i++) {
|
|
|
|
+ const String &base_path = p_paths.get(i);
|
|
|
|
+ print_verbose(vformat("Scanning for projects in \"%s\".", base_path));
|
|
|
|
+
|
|
|
|
+ _scan_folder_recursive(base_path, &projects);
|
|
|
|
+ print_verbose(vformat("Found %d project(s).", projects.size()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (const String &E : projects) {
|
|
|
|
+ add_project(E, false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ save_config();
|
|
|
|
+ update_project_list();
|
|
|
|
+}
|
|
|
|
+
|
|
int ProjectList::refresh_project(const String &dir_path) {
|
|
int ProjectList::refresh_project(const String &dir_path) {
|
|
// Reloads information about a specific project.
|
|
// Reloads information about a specific project.
|
|
// If it wasn't loaded and should be in the list, it is added (i.e new project).
|
|
// If it wasn't loaded and should be in the list, it is added (i.e new project).
|
|
@@ -1916,6 +1961,7 @@ void ProjectList::_show_project(const String &p_path) {
|
|
}
|
|
}
|
|
|
|
|
|
void ProjectList::_bind_methods() {
|
|
void ProjectList::_bind_methods() {
|
|
|
|
+ ADD_SIGNAL(MethodInfo(SIGNAL_LIST_CHANGED));
|
|
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
|
|
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
|
|
ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
|
|
ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
|
|
}
|
|
}
|
|
@@ -1926,6 +1972,7 @@ ProjectList::ProjectList() {
|
|
add_child(_scroll_children);
|
|
add_child(_scroll_children);
|
|
|
|
|
|
_config_path = EditorPaths::get_singleton()->get_data_dir().path_join("projects.cfg");
|
|
_config_path = EditorPaths::get_singleton()->get_data_dir().path_join("projects.cfg");
|
|
|
|
+ _migrate_config();
|
|
}
|
|
}
|
|
|
|
|
|
/// Project Manager.
|
|
/// Project Manager.
|
|
@@ -2183,15 +2230,6 @@ void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void ProjectManager::_load_recent_projects() {
|
|
|
|
- _project_list->set_search_term(search_box->get_text().strip_edges());
|
|
|
|
- _project_list->load_projects();
|
|
|
|
-
|
|
|
|
- _update_project_buttons();
|
|
|
|
-
|
|
|
|
- tabs->set_current_tab(0);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void ProjectManager::_on_projects_updated() {
|
|
void ProjectManager::_on_projects_updated() {
|
|
Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects();
|
|
Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects();
|
|
int index = 0;
|
|
int index = 0;
|
|
@@ -2429,30 +2467,6 @@ void ProjectManager::_run_project() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void ProjectManager::_scan_dir(const String &path) {
|
|
|
|
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
|
- Error error = da->change_dir(path);
|
|
|
|
- ERR_FAIL_COND_MSG(error != OK, "Could not scan directory at: " + path);
|
|
|
|
- da->list_dir_begin();
|
|
|
|
- String n = da->get_next();
|
|
|
|
- while (!n.is_empty()) {
|
|
|
|
- if (da->current_is_dir() && !n.begins_with(".")) {
|
|
|
|
- _scan_dir(da->get_current_dir().path_join(n));
|
|
|
|
- } else if (n == "project.godot") {
|
|
|
|
- _project_list->add_project(da->get_current_dir(), false);
|
|
|
|
- }
|
|
|
|
- n = da->get_next();
|
|
|
|
- }
|
|
|
|
- da->list_dir_end();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void ProjectManager::_scan_begin(const String &p_base) {
|
|
|
|
- print_line("Scanning projects at: " + p_base);
|
|
|
|
- _scan_dir(p_base);
|
|
|
|
- _project_list->save_config();
|
|
|
|
- _load_recent_projects();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void ProjectManager::_scan_projects() {
|
|
void ProjectManager::_scan_projects() {
|
|
scan_dir->popup_file_dialog();
|
|
scan_dir->popup_file_dialog();
|
|
}
|
|
}
|
|
@@ -2652,54 +2666,26 @@ void ProjectManager::_install_project(const String &p_zip_path, const String &p_
|
|
}
|
|
}
|
|
|
|
|
|
void ProjectManager::_files_dropped(PackedStringArray p_files) {
|
|
void ProjectManager::_files_dropped(PackedStringArray p_files) {
|
|
|
|
+ // TODO: Support installing multiple ZIPs at the same time?
|
|
if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {
|
|
if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {
|
|
- const String file = p_files[0].get_file();
|
|
|
|
- _install_project(p_files[0], file.substr(0, file.length() - 4).capitalize());
|
|
|
|
|
|
+ const String &file = p_files[0];
|
|
|
|
+ _install_project(file, file.get_file().get_basename().capitalize());
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
+
|
|
HashSet<String> folders_set;
|
|
HashSet<String> folders_set;
|
|
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
for (int i = 0; i < p_files.size(); i++) {
|
|
for (int i = 0; i < p_files.size(); i++) {
|
|
- String file = p_files[i];
|
|
|
|
|
|
+ const String &file = p_files[i];
|
|
folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());
|
|
folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());
|
|
}
|
|
}
|
|
- if (folders_set.size() > 0) {
|
|
|
|
- PackedStringArray folders;
|
|
|
|
- for (const String &E : folders_set) {
|
|
|
|
- folders.push_back(E);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- bool confirm = true;
|
|
|
|
- if (folders.size() == 1) {
|
|
|
|
- Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
|
- if (dir->change_dir(folders[0]) == OK) {
|
|
|
|
- dir->list_dir_begin();
|
|
|
|
- String file = dir->get_next();
|
|
|
|
- while (confirm && !file.is_empty()) {
|
|
|
|
- if (!dir->current_is_dir() && file.ends_with("project.godot")) {
|
|
|
|
- confirm = false;
|
|
|
|
- }
|
|
|
|
- file = dir->get_next();
|
|
|
|
- }
|
|
|
|
- dir->list_dir_end();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (confirm) {
|
|
|
|
- multi_scan_ask->get_ok_button()->disconnect("pressed", callable_mp(this, &ProjectManager::_scan_multiple_folders));
|
|
|
|
- multi_scan_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_scan_multiple_folders).bind(folders));
|
|
|
|
- multi_scan_ask->set_text(
|
|
|
|
- vformat(TTR("Are you sure to scan %s folders for existing Godot projects?\nThis could take a while."), folders.size()));
|
|
|
|
- multi_scan_ask->popup_centered();
|
|
|
|
- } else {
|
|
|
|
- _scan_multiple_folders(folders);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+ ERR_FAIL_COND(folders_set.size() == 0); // This can't really happen, we consume every dropped file path above.
|
|
|
|
|
|
-void ProjectManager::_scan_multiple_folders(PackedStringArray p_files) {
|
|
|
|
- for (int i = 0; i < p_files.size(); i++) {
|
|
|
|
- _scan_begin(p_files.get(i));
|
|
|
|
|
|
+ PackedStringArray folders;
|
|
|
|
+ for (const String &E : folders_set) {
|
|
|
|
+ folders.push_back(E);
|
|
}
|
|
}
|
|
|
|
+ _project_list->find_projects_multiple(folders);
|
|
}
|
|
}
|
|
|
|
|
|
void ProjectManager::_on_order_option_changed(int p_idx) {
|
|
void ProjectManager::_on_order_option_changed(int p_idx) {
|
|
@@ -2740,11 +2726,6 @@ void ProjectManager::_on_search_term_submitted(const String &p_text) {
|
|
_open_selected_projects_ask();
|
|
_open_selected_projects_ask();
|
|
}
|
|
}
|
|
|
|
|
|
-void ProjectManager::_bind_methods() {
|
|
|
|
- ClassDB::bind_method("_update_project_buttons", &ProjectManager::_update_project_buttons);
|
|
|
|
- ClassDB::bind_method("_version_button_pressed", &ProjectManager::_version_button_pressed);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void ProjectManager::_open_asset_library() {
|
|
void ProjectManager::_open_asset_library() {
|
|
asset_library->disable_community_support();
|
|
asset_library->disable_community_support();
|
|
tabs->set_current_tab(1);
|
|
tabs->set_current_tab(1);
|
|
@@ -2870,8 +2851,8 @@ ProjectManager::ProjectManager() {
|
|
vb->add_child(center_box);
|
|
vb->add_child(center_box);
|
|
|
|
|
|
tabs = memnew(TabContainer);
|
|
tabs = memnew(TabContainer);
|
|
- center_box->add_child(tabs);
|
|
|
|
tabs->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
|
tabs->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
|
|
|
+ center_box->add_child(tabs);
|
|
tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed));
|
|
tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed));
|
|
|
|
|
|
local_projects_vb = memnew(VBoxContainer);
|
|
local_projects_vb = memnew(VBoxContainer);
|
|
@@ -2950,10 +2931,11 @@ ProjectManager::ProjectManager() {
|
|
search_tree_hb->add_child(search_panel);
|
|
search_tree_hb->add_child(search_panel);
|
|
|
|
|
|
_project_list = memnew(ProjectList);
|
|
_project_list = memnew(ProjectList);
|
|
- _project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
|
|
|
|
- _project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));
|
|
|
|
_project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
|
_project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
|
search_panel->add_child(_project_list);
|
|
search_panel->add_child(_project_list);
|
|
|
|
+ _project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
|
|
|
|
+ _project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
|
|
|
|
+ _project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));
|
|
|
|
|
|
// The side bar with the edit, run, rename, etc. buttons.
|
|
// The side bar with the edit, run, rename, etc. buttons.
|
|
VBoxContainer *tree_vb = memnew(VBoxContainer);
|
|
VBoxContainer *tree_vb = memnew(VBoxContainer);
|
|
@@ -3099,7 +3081,7 @@ ProjectManager::ProjectManager() {
|
|
scan_dir->set_title(TTR("Select a Folder to Scan")); // must be after mode or it's overridden
|
|
scan_dir->set_title(TTR("Select a Folder to Scan")); // must be after mode or it's overridden
|
|
scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));
|
|
scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));
|
|
add_child(scan_dir);
|
|
add_child(scan_dir);
|
|
- scan_dir->connect("dir_selected", callable_mp(this, &ProjectManager::_scan_begin));
|
|
|
|
|
|
+ scan_dir->connect("dir_selected", callable_mp(_project_list, &ProjectList::find_projects));
|
|
|
|
|
|
erase_missing_ask = memnew(ConfirmationDialog);
|
|
erase_missing_ask = memnew(ConfirmationDialog);
|
|
erase_missing_ask->set_ok_button_text(TTR("Remove All"));
|
|
erase_missing_ask->set_ok_button_text(TTR("Remove All"));
|
|
@@ -3133,10 +3115,6 @@ ProjectManager::ProjectManager() {
|
|
multi_run_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_run_project_confirm));
|
|
multi_run_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_run_project_confirm));
|
|
add_child(multi_run_ask);
|
|
add_child(multi_run_ask);
|
|
|
|
|
|
- multi_scan_ask = memnew(ConfirmationDialog);
|
|
|
|
- multi_scan_ask->set_ok_button_text(TTR("Scan"));
|
|
|
|
- add_child(multi_scan_ask);
|
|
|
|
-
|
|
|
|
ask_update_settings = memnew(ConfirmationDialog);
|
|
ask_update_settings = memnew(ConfirmationDialog);
|
|
ask_update_settings->set_autowrap(true);
|
|
ask_update_settings->set_autowrap(true);
|
|
ask_update_settings->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_confirm_update_settings));
|
|
ask_update_settings->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_confirm_update_settings));
|
|
@@ -3245,29 +3223,36 @@ ProjectManager::ProjectManager() {
|
|
create_tag_btn->connect("pressed", callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));
|
|
create_tag_btn->connect("pressed", callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));
|
|
}
|
|
}
|
|
|
|
|
|
- _project_list->migrate_config();
|
|
|
|
- _load_recent_projects();
|
|
|
|
-
|
|
|
|
- Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
|
|
|
|
|
|
+ // Initialize project list.
|
|
|
|
+ {
|
|
|
|
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
|
|
|
|
|
|
- String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
|
|
|
|
- if (!dir_access->dir_exists(default_project_path)) {
|
|
|
|
- Error error = dir_access->make_dir_recursive(default_project_path);
|
|
|
|
- if (error != OK) {
|
|
|
|
- ERR_PRINT("Could not create default project directory at: " + default_project_path);
|
|
|
|
|
|
+ String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
|
|
|
|
+ if (!default_project_path.is_empty() && !dir_access->dir_exists(default_project_path)) {
|
|
|
|
+ Error error = dir_access->make_dir_recursive(default_project_path);
|
|
|
|
+ if (error != OK) {
|
|
|
|
+ ERR_PRINT("Could not create default project directory at: " + default_project_path);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
|
|
|
|
- if (!autoscan_path.is_empty()) {
|
|
|
|
- if (dir_access->dir_exists(autoscan_path)) {
|
|
|
|
- _scan_begin(autoscan_path);
|
|
|
|
- } else {
|
|
|
|
- Error error = dir_access->make_dir_recursive(autoscan_path);
|
|
|
|
- if (error != OK) {
|
|
|
|
- ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);
|
|
|
|
|
|
+ bool scanned_for_projects = false; // Scanning will update the list automatically.
|
|
|
|
+
|
|
|
|
+ String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
|
|
|
|
+ if (!autoscan_path.is_empty()) {
|
|
|
|
+ if (dir_access->dir_exists(autoscan_path)) {
|
|
|
|
+ _project_list->find_projects(autoscan_path);
|
|
|
|
+ scanned_for_projects = true;
|
|
|
|
+ } else {
|
|
|
|
+ Error error = dir_access->make_dir_recursive(autoscan_path);
|
|
|
|
+ if (error != OK) {
|
|
|
|
+ ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (!scanned_for_projects) {
|
|
|
|
+ _project_list->update_project_list();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));
|
|
SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));
|