From 210941991e573bb326e163920656cdb839ae3c7f Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Thu, 9 Oct 2025 23:25:04 +0200 Subject: [PATCH] chore: implement dialog handler for linux --- src/ModMan/Web/Binds/DialogBinds.cpp | 21 ++- src/ModMan/Web/Platform/DialogHandler.cpp | 24 +++ src/ModMan/Web/Platform/DialogHandler.h | 77 +++++++- .../Web/Platform/Linux/DialogHandlerLinux.cpp | 173 +++++++++++++----- .../Web/Platform/Linux/DialogHandlerLinux.h | 70 ------- .../Platform/Windows/DialogHandlerWindows.cpp | 52 ++---- .../Platform/Windows/DialogHandlerWindows.h | 80 -------- 7 files changed, 252 insertions(+), 245 deletions(-) create mode 100644 src/ModMan/Web/Platform/DialogHandler.cpp delete mode 100644 src/ModMan/Web/Platform/Linux/DialogHandlerLinux.h delete mode 100644 src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h diff --git a/src/ModMan/Web/Binds/DialogBinds.cpp b/src/ModMan/Web/Binds/DialogBinds.cpp index 091a9dea..3d72d9e3 100644 --- a/src/ModMan/Web/Binds/DialogBinds.cpp +++ b/src/ModMan/Web/Binds/DialogBinds.cpp @@ -5,8 +5,6 @@ #include "Json/JsonExtension.h" -using namespace PLATFORM_NAMESPACE; - namespace { class FileDialogFilterDto @@ -34,9 +32,12 @@ namespace NLOHMANN_DEFINE_TYPE_EXTENSION(SaveFileDialogDto, filters); - void ReplyWithDialogResult(webview::webview& wv, const std::string& id, const DialogCallbackResultType resultType, const std::optional& result) + void ReplyWithDialogResult(webview::webview& wv, + const std::string& id, + const ui::DialogCallbackResultType resultType, + const std::optional& result) { - if (resultType == FAILED) + if (resultType == ui::DialogCallbackResultType::FAILED) ui::PromiseReject(wv, id, result); else ui::PromiseResolve(wv, id, result); @@ -44,9 +45,9 @@ namespace void OpenFileDialogBind(webview::webview& wv, const std::string& id, const std::optional& dto) { - OpenFileDialog dialog; + ui::OpenFileDialog dialog; dialog.SetCallback( - [&wv, id](const DialogCallbackResultType resultType, const std::optional& result) + [&wv, id](const ui::DialogCallbackResultType resultType, const std::optional& result) { ReplyWithDialogResult(wv, id, resultType, result); }); @@ -64,9 +65,9 @@ namespace void SaveFileDialogBind(webview::webview& wv, const std::string& id, const std::optional& dto) { - SaveFileDialog dialog; + ui::SaveFileDialog dialog; dialog.SetCallback( - [&wv, id](const DialogCallbackResultType resultType, const std::optional& result) + [&wv, id](const ui::DialogCallbackResultType resultType, const std::optional& result) { ReplyWithDialogResult(wv, id, resultType, result); }); @@ -84,9 +85,9 @@ namespace void FolderSelectDialogBind(webview::webview& wv, const std::string& id) { - FolderSelectDialog dialog; + ui::FolderSelectDialog dialog; dialog.SetCallback( - [&wv, id](const DialogCallbackResultType resultType, const std::optional& result) + [&wv, id](const ui::DialogCallbackResultType resultType, const std::optional& result) { ReplyWithDialogResult(wv, id, resultType, result); }); diff --git a/src/ModMan/Web/Platform/DialogHandler.cpp b/src/ModMan/Web/Platform/DialogHandler.cpp new file mode 100644 index 00000000..f93df3bc --- /dev/null +++ b/src/ModMan/Web/Platform/DialogHandler.cpp @@ -0,0 +1,24 @@ +#include "DialogHandler.h" + +namespace ui +{ + DialogWithCallback::DialogWithCallback() = default; + + void DialogWithCallback::SetCallback(callback_t callback) + { + m_callback = std::move(callback); + } + + FileDialogFilter::FileDialogFilter(std::string name, std::string filter) + : m_name(std::move(name)), + m_filter(std::move(filter)) + { + } + + FileDialog::FileDialog() = default; + + void FileDialog::AddFilter(std::string name, std::string filter) + { + m_filters.emplace_back(std::move(name), std::move(filter)); + } +} // namespace ui diff --git a/src/ModMan/Web/Platform/DialogHandler.h b/src/ModMan/Web/Platform/DialogHandler.h index 09ed1eb8..f793ccc2 100644 --- a/src/ModMan/Web/Platform/DialogHandler.h +++ b/src/ModMan/Web/Platform/DialogHandler.h @@ -1,7 +1,74 @@ #pragma once -#ifdef _WIN32 -#include "Windows/DialogHandlerWindows.h" -#elif defined(__linux__) -#include "Linux/DialogHandlerLinux.h" -#endif +#include +#include +#include +#include +#include + +namespace ui +{ + enum class DialogCallbackResultType : std::uint8_t + { + SUCCESSFUL, + CANCELLED, + FAILED + }; + + class DialogWithCallback + { + public: + using callback_t = std::function result)>; + + DialogWithCallback(); + + void SetCallback(callback_t callback); + + protected: + callback_t m_callback; + }; + + class FileDialogFilter + { + public: + FileDialogFilter(std::string name, std::string filter); + + std::string m_name; + std::string m_filter; + }; + + class FileDialog : public DialogWithCallback + { + public: + FileDialog(); + + void AddFilter(std::string name, std::string filter); + + protected: + std::vector m_filters; + }; + + class OpenFileDialog : public FileDialog + { + public: + OpenFileDialog(); + + void OpenAsync(); + }; + + class SaveFileDialog : public FileDialog + { + public: + SaveFileDialog(); + + void OpenAsync(); + }; + + class FolderSelectDialog : public DialogWithCallback + { + public: + FolderSelectDialog(); + + void OpenAsync(); + }; +} // namespace ui diff --git a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp b/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp index 292d5e2e..50b14b46 100644 --- a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp +++ b/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp @@ -1,13 +1,33 @@ -#include "DialogHandlerLinux.h" +#include "Web/Platform/DialogHandler.h" #ifdef __linux__ +#include +#include #include -using namespace PLATFORM_NAMESPACE_LINUX; - namespace { + enum class DialogOperation : std::uint8_t + { + OPEN_FILE, + SAVE_FILE, + SELECT_FOLDER + }; + + class DialogData + { + public: + DialogData(DialogOperation operation, ui::DialogWithCallback::callback_t callback) + : m_operation(operation), + m_callback(std::move(callback)) + { + } + + DialogOperation m_operation; + ui::DialogWithCallback::callback_t m_callback; + }; + bool InitGtk() { #if GTK_MAJOR_VERSION >= 4 @@ -17,29 +37,77 @@ namespace #endif } - void OpenFileDialog() - { -#ifdef GDK_AVAILABLE_IN_4_10 - auto* dialog = gtk_file_dialog_new(); + void OpenFileDialog() {} - gtk_file_dialog_open(dialog, nullptr, nullptr, [](GObject *source, - GAsyncResult *result, - gpointer user_data - ) -> void { - - }, nullptr - ); - - g_object_unref(dialog); -#endif - } - - bool SetFilters(void* pFileOpen, const std::vector& filters) + void SetFilters(GtkFileDialog* pDialog, const std::vector& filters) { if (filters.empty()) - return true; + return; - return false; + auto* listStore = g_list_store_new(GTK_TYPE_FILE_FILTER); + + for (auto& filter : filters) + { + auto* fileFilter = gtk_file_filter_new(); + gtk_file_filter_set_name(fileFilter, filter.m_name.c_str()); + gtk_file_filter_add_pattern(fileFilter, filter.m_filter.c_str()); + g_list_store_append(listStore, fileFilter); + g_object_unref(fileFilter); + } + + gtk_file_dialog_set_filters(pDialog, G_LIST_MODEL(listStore)); + g_object_unref(listStore); + } + + void OnDialogResult(GObject* source, GAsyncResult* asyncResult, gpointer userData) + { + auto* dialogData = reinterpret_cast(userData); + + GError* error = nullptr; + GFile* file = nullptr; + + if (dialogData->m_operation == DialogOperation::OPEN_FILE) + { + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(source), asyncResult, &error); + } + else if (dialogData->m_operation == DialogOperation::SAVE_FILE) + { + file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), asyncResult, &error); + } + else if (dialogData->m_operation == DialogOperation::SELECT_FOLDER) + { + file = gtk_file_dialog_select_folder_finish(GTK_FILE_DIALOG(source), asyncResult, &error); + } + else + { + assert(false); + } + + std::optional result; + ui::DialogCallbackResultType resultType; + if (error) + { + if (error->code == GTK_DIALOG_ERROR_DISMISSED) + resultType = ui::DialogCallbackResultType::CANCELLED; + else + resultType = ui::DialogCallbackResultType::FAILED; + + g_error_free(error); + } + else + { + resultType = ui::DialogCallbackResultType::SUCCESSFUL; + result = std::string(g_file_get_path(file)); + } + + if (file) + { + g_object_unref(file); + } + + dialogData->m_callback(resultType, result); + + delete dialogData; } std::optional ShowFileDialog() @@ -50,43 +118,60 @@ namespace } } // namespace -namespace PLATFORM_NAMESPACE_LINUX +namespace ui { - FileDialogFilter::FileDialogFilter(std::string name, std::string filter) - : m_name(std::move(name)), - m_filter(std::move(filter)) - { - } - - FileDialog::FileDialog() = default; - - void FileDialog::AddFilter(std::string name, std::string filter) - { - m_filters.emplace_back(std::move(name), std::move(filter)); - } - +#if GTK_MAJOR_VERSION >= 4 OpenFileDialog::OpenFileDialog() = default; - std::optional OpenFileDialog::Open() const + void OpenFileDialog::OpenAsync() { if (!InitGtk()) - return std::nullopt; + return m_callback(DialogCallbackResultType::FAILED, std::nullopt); - return std::nullopt; + auto* dialog = gtk_file_dialog_new(); + + SetFilters(dialog, m_filters); + + // Move data out of the dialog object since it may be destroyed + auto* dialogData = new DialogData(DialogOperation::OPEN_FILE, std::move(m_callback)); + gtk_file_dialog_open(dialog, nullptr, nullptr, OnDialogResult, dialogData); + + g_object_unref(dialog); } SaveFileDialog::SaveFileDialog() = default; - std::optional SaveFileDialog::Open() const + void SaveFileDialog::OpenAsync() { - return std::nullopt; + if (!InitGtk()) + return m_callback(DialogCallbackResultType::FAILED, std::nullopt); + + auto* dialog = gtk_file_dialog_new(); + + SetFilters(dialog, m_filters); + + // Move data out of the dialog object since it may be destroyed + auto* dialogData = new DialogData(DialogOperation::SAVE_FILE, std::move(m_callback)); + gtk_file_dialog_save(dialog, nullptr, nullptr, OnDialogResult, dialogData); + + g_object_unref(dialog); } FolderSelectDialog::FolderSelectDialog() = default; - std::optional FolderSelectDialog::Open() const + void FolderSelectDialog::OpenAsync() { - return std::nullopt; + if (!InitGtk()) + return m_callback(DialogCallbackResultType::FAILED, std::nullopt); + + auto* dialog = gtk_file_dialog_new(); + + // Move data out of the dialog object since it may be destroyed + auto* dialogData = new DialogData(DialogOperation::SELECT_FOLDER, std::move(m_callback)); + gtk_file_dialog_select_folder(dialog, nullptr, nullptr, OnDialogResult, dialogData); + + g_object_unref(dialog); } -} // namespace PLATFORM_NAMESPACE_LINUX +#endif +} // namespace ui #endif diff --git a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.h b/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.h deleted file mode 100644 index cc7e75b6..00000000 --- a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#ifdef __linux__ - -#include "Web/Platform/Platform.h" - -#include -#include -#include -#include - -namespace PLATFORM_NAMESPACE_LINUX -{ - class FileDialogFilter - { - public: - FileDialogFilter(std::string name, std::string filter); - - std::string m_name; - std::string m_filter; - }; - - class DialogWithCallback - { - public: - DialogWithCallback(); - - void SetCallback(std::function result)> callback); - - protected: - std::function result)> m_callback; - }; - - class FileDialog : public DialogWithCallback - { - public: - FileDialog(); - - void AddFilter(std::string name, std::string filter); - - protected: - std::vector m_filters; - }; - - class OpenFileDialog : public FileDialog - { - public: - OpenFileDialog(); - - void OpenAsync() const; - }; - - class SaveFileDialog : public FileDialog - { - public: - SaveFileDialog(); - - void OpenAsync() const; - }; - - class FolderSelectDialog : public DialogWithCallback - { - public: - FolderSelectDialog(); - - void OpenAsync() const; - }; -} // namespace PLATFORM_NAMESPACE_LINUX - -#endif diff --git a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp b/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp index 68885db2..6a1766bf 100644 --- a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp +++ b/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp @@ -1,4 +1,4 @@ -#include "DialogHandlerWindows.h" +#include "Web/Platform/DialogHandler.h" #ifdef _WIN32 @@ -12,7 +12,7 @@ using namespace PLATFORM_NAMESPACE_WINDOWS; namespace { - bool SetFilters(IFileDialog* pFileOpen, const std::vector& filters) + bool SetFilters(IFileDialog* pFileOpen, const std::vector& filters) { if (filters.empty()) return true; @@ -48,9 +48,9 @@ namespace return SUCCEEDED(result); } - DialogCallbackResultType ShowFileDialog(IFileDialog* pFileDialog, std::optional& result) + ui::DialogCallbackResultType ShowFileDialog(IFileDialog* pFileDialog, std::optional& result) { - DialogCallbackResultType resultType = FAILED; + auto resultType = ui::DialogCallbackResultType::FAILED; auto hr = pFileDialog->Show(nullptr); if (SUCCEEDED(hr)) @@ -68,42 +68,22 @@ namespace result = WideStringToString(pszFilePath); CoTaskMemFree(pszFilePath); - resultType = SUCCESSFUL; + resultType = ui::DialogCallbackResultType::SUCCESSFUL; } pItem->Release(); } } else if (HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr) { - resultType = CANCELLED; + resultType = ui::DialogCallbackResultType::CANCELLED; } return resultType; } } // namespace -namespace PLATFORM_NAMESPACE_WINDOWS +namespace ui { - DialogWithCallback::DialogWithCallback() = default; - - void DialogWithCallback::SetCallback(callback_t callback) - { - m_callback = std::move(callback); - } - - FileDialogFilter::FileDialogFilter(std::string name, std::string filter) - : m_name(std::move(name)), - m_filter(std::move(filter)) - { - } - - FileDialog::FileDialog() = default; - - void FileDialog::AddFilter(std::string name, std::string filter) - { - m_filters.emplace_back(std::move(name), std::move(filter)); - } - OpenFileDialog::OpenFileDialog() = default; void OpenFileDialog::OpenAsync() @@ -119,11 +99,11 @@ namespace PLATFORM_NAMESPACE_WINDOWS const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (!SUCCEEDED(initResult)) { - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } - DialogCallbackResultType resultType = FAILED; + auto resultType = DialogCallbackResultType::FAILED; std::optional result = std::nullopt; IFileOpenDialog* pFileOpen; @@ -157,11 +137,11 @@ namespace PLATFORM_NAMESPACE_WINDOWS const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (!SUCCEEDED(initResult)) { - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } - DialogCallbackResultType resultType = FAILED; + auto resultType = DialogCallbackResultType::FAILED; std::optional result = std::nullopt; IFileSaveDialog* pFileSave; @@ -194,7 +174,7 @@ namespace PLATFORM_NAMESPACE_WINDOWS const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); if (!SUCCEEDED(initResult)) { - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } @@ -205,7 +185,7 @@ namespace PLATFORM_NAMESPACE_WINDOWS if (!SUCCEEDED(hr)) { CoUninitialize(); - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } @@ -214,7 +194,7 @@ namespace PLATFORM_NAMESPACE_WINDOWS { pFileOpen->Release(); CoUninitialize(); - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } @@ -222,7 +202,7 @@ namespace PLATFORM_NAMESPACE_WINDOWS { pFileOpen->Release(); CoUninitialize(); - callback(FAILED, std::nullopt); + callback(DialogCallbackResultType::FAILED, std::nullopt); return; } @@ -235,5 +215,5 @@ namespace PLATFORM_NAMESPACE_WINDOWS }) .detach(); } -} // namespace PLATFORM_NAMESPACE_WINDOWS +} // namespace ui #endif diff --git a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h b/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h deleted file mode 100644 index 0b60db85..00000000 --- a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#ifdef _WIN32 - -#include "Web/Platform/Platform.h" - -#include -#include -#include -#include -#include - -namespace PLATFORM_NAMESPACE_WINDOWS -{ - enum DialogCallbackResultType : std::uint8_t - { - SUCCESSFUL, - CANCELLED, - FAILED - }; - - class DialogWithCallback - { - public: - using callback_t = std::function result)>; - - DialogWithCallback(); - - void SetCallback(callback_t callback); - - protected: - callback_t m_callback; - }; - - class FileDialogFilter - { - public: - FileDialogFilter(std::string name, std::string filter); - - std::string m_name; - std::string m_filter; - }; - - class FileDialog : public DialogWithCallback - { - public: - FileDialog(); - - void AddFilter(std::string name, std::string filter); - - protected: - std::vector m_filters; - }; - - class OpenFileDialog : public FileDialog - { - public: - OpenFileDialog(); - - void OpenAsync(); - }; - - class SaveFileDialog : public FileDialog - { - public: - SaveFileDialog(); - - void OpenAsync(); - }; - - class FolderSelectDialog : public DialogWithCallback - { - public: - FolderSelectDialog(); - - void OpenAsync(); - }; -} // namespace PLATFORM_NAMESPACE_WINDOWS - -#endif