Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Windows] Implement native file selection dialog support. #79574

Merged
merged 1 commit into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
128 changes: 128 additions & 0 deletions platform/windows/display_server_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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_

Expand Down
2 changes: 2 additions & 0 deletions platform/windows/display_server_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> &p_filters, const Callable &p_callback) override;

virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;

Expand Down
10 changes: 5 additions & 5 deletions scene/gui/file_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,22 @@ 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);
dir->set_text(f.get_base_dir());
}
} else {
file->set_text("");
emit_signal("cancelled");
emit_signal(SNAME("canceled"));
}
}

Expand Down
Loading