diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 920fe901a822..9c075a979a7e 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -109,7 +109,7 @@ Displays OS native dialog for selecting files or directories in the file system. Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code]. [b]Note:[/b] This method is implemented if the display server has the [code]FEATURE_NATIVE_DIALOG[/code] feature. - [b]Note:[/b] This method is implemented on macOS. + [b]Note:[/b] This method is implemented on Windows and macOS. [b]Note:[/b] On macOS, native file dialogs have no title. [b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks. diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 86b909234a6e..79b9769cc81d 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -32,6 +32,7 @@ #include "os_windows.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" #include "main/main.h" #include "scene/resources/atlas_texture.h" @@ -42,6 +43,9 @@ #include #include +#include +#include +#include #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -87,6 +91,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_HIDPI: case FEATURE_ICON: case FEATURE_NATIVE_ICON: + case FEATURE_NATIVE_DIALOG: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: @@ -213,6 +218,129 @@ void DisplayServerWindows::tts_stop() { tts->stop(); } +Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector &p_filters, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + Vector filter_names; + Vector filter_exts; + for (const String &E : p_filters) { + Vector tokens = E.split(";"); + if (tokens.size() == 2) { + filter_exts.push_back(tokens[0].strip_edges().utf16()); + filter_names.push_back(tokens[1].strip_edges().utf16()); + } else if (tokens.size() == 1) { + filter_exts.push_back(tokens[0].strip_edges().utf16()); + filter_names.push_back(tokens[0].strip_edges().utf16()); + } + } + + Vector filters; + for (int i = 0; i < filter_names.size(); i++) { + filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() }); + } + + HRESULT hr = S_OK; + IFileDialog *pfd = nullptr; + if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { + hr = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, (void **)&pfd); + } else { + hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd); + } + if (SUCCEEDED(hr)) { + DWORD flags; + pfd->GetOptions(&flags); + if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { + flags |= FOS_ALLOWMULTISELECT; + } + if (p_mode == FILE_DIALOG_MODE_OPEN_DIR) { + flags |= FOS_PICKFOLDERS; + } + if (p_show_hidden) { + flags |= FOS_FORCESHOWHIDDEN; + } + pfd->SetOptions(flags | FOS_FORCEFILESYSTEM); + pfd->SetTitle((LPCWSTR)p_title.utf16().ptr()); + + String dir = ProjectSettings::get_singleton()->globalize_path(p_current_directory); + if (dir == ".") { + dir = OS::get_singleton()->get_executable_path().get_base_dir(); + } + dir = dir.replace("/", "\\"); + + IShellItem *shellitem = nullptr; + hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem); + if (SUCCEEDED(hr)) { + pfd->SetDefaultFolder(shellitem); + pfd->SetFolder(shellitem); + } + + pfd->SetFileName((LPCWSTR)p_filename.utf16().ptr()); + pfd->SetFileTypes(filters.size(), filters.ptr()); + pfd->SetFileTypeIndex(0); + + hr = pfd->Show(nullptr); + if (SUCCEEDED(hr)) { + Vector file_names; + + if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { + IShellItemArray *results; + hr = static_cast(pfd)->GetResults(&results); + if (SUCCEEDED(hr)) { + DWORD count = 0; + results->GetCount(&count); + for (DWORD i = 0; i < count; i++) { + IShellItem *result; + results->GetItemAt(i, &result); + + PWSTR file_path = nullptr; + hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + if (SUCCEEDED(hr)) { + file_names.push_back(String::utf16((const char16_t *)file_path)); + CoTaskMemFree(file_path); + } + result->Release(); + } + results->Release(); + } + } else { + IShellItem *result; + hr = pfd->GetResult(&result); + if (SUCCEEDED(hr)) { + PWSTR file_path = nullptr; + hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + if (SUCCEEDED(hr)) { + file_names.push_back(String::utf16((const char16_t *)file_path)); + CoTaskMemFree(file_path); + } + result->Release(); + } + } + if (!p_callback.is_null()) { + Variant v_status = true; + Variant v_files = file_names; + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + p_callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } else { + if (!p_callback.is_null()) { + Variant v_status = false; + Variant v_files = Vector(); + Variant *v_args[2] = { &v_status, &v_files }; + Variant ret; + Callable::CallError ce; + p_callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + pfd->Release(); + + return OK; + } else { + return ERR_CANT_OPEN; + } +} + void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 9d1088675b37..bd47dee9ec9a 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -511,6 +511,8 @@ class DisplayServerWindows : public DisplayServer { virtual bool is_dark_mode() const override; virtual Color get_accent_color() const override; + virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector &p_filters, const Callable &p_callback) override; + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index fe32e406e8a8..d4da4797ebb4 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -67,14 +67,14 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector &p_files) { if (p_files.size() > 0) { String f = p_files[0]; if (mode == FILE_MODE_OPEN_FILES) { - emit_signal("files_selected", p_files); + emit_signal(SNAME("files_selected"), p_files); } else { if (mode == FILE_MODE_SAVE_FILE) { - emit_signal("file_selected", f); + emit_signal(SNAME("file_selected"), f); } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { - emit_signal("file_selected", f); + emit_signal(SNAME("file_selected"), f); } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { - emit_signal("dir_selected", f); + emit_signal(SNAME("dir_selected"), f); } } file->set_text(f); @@ -82,7 +82,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector &p_files) { } } else { file->set_text(""); - emit_signal("cancelled"); + emit_signal(SNAME("canceled")); } }