[Windows] Implement native file selection dialog support.
This commit is contained in:
parent
851bc640dd
commit
d3ca91ad6a
4 changed files with 136 additions and 6 deletions
|
@ -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.
|
||||
</description>
|
||||
|
|
|
@ -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 <avrt.h>
|
||||
#include <dwmapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shobjidl.h>
|
||||
#include <shobjidl_core.h>
|
||||
|
||||
#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<String> &p_filters, const Callable &p_callback) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
|
||||
Vector<Char16String> filter_names;
|
||||
Vector<Char16String> filter_exts;
|
||||
for (const String &E : p_filters) {
|
||||
Vector<String> 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<COMDLG_FILTERSPEC> 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<String> file_names;
|
||||
|
||||
if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
|
||||
IShellItemArray *results;
|
||||
hr = static_cast<IFileOpenDialog *>(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<String>();
|
||||
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_
|
||||
|
||||
|
|
|
@ -511,6 +511,8 @@ public:
|
|||
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<String> &p_filters, const Callable &p_callback) override;
|
||||
|
||||
virtual void mouse_set_mode(MouseMode p_mode) override;
|
||||
virtual MouseMode mouse_get_mode() const override;
|
||||
|
||||
|
|
|
@ -67,14 +67,14 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &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<String> &p_files) {
|
|||
}
|
||||
} else {
|
||||
file->set_text("");
|
||||
emit_signal("cancelled");
|
||||
emit_signal(SNAME("canceled"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue