Browse Source

[Windows] Implement native file selection dialog support.

bruvzg 2 years ago
parent
commit
d3ca91ad6a

+ 1 - 1
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.
 			</description>

+ 128 - 0
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 <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_
 

+ 2 - 0
platform/windows/display_server_windows.h

@@ -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;
 

+ 5 - 5
scene/gui/file_dialog.cpp

@@ -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"));
 	}
 }