/***************************************************************************** * Copyright (c) 2014-2022 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #ifdef _WIN32 // Windows.h needs to be included first // clang-format off # include # include // clang-format on # undef CreateWindow // Then the rest # include "UiContext.h" # include # include # include # include # include # include # include # include # include // Native resource IDs # include "../../resources/resource.h" using namespace Microsoft::WRL; class CCoInitialize { public: CCoInitialize(DWORD dwCoInit) : m_hr(CoInitializeEx(nullptr, dwCoInit)) { } ~CCoInitialize() { if (SUCCEEDED(m_hr)) { CoUninitialize(); } } operator bool() const { return SUCCEEDED(m_hr); } private: HRESULT m_hr; }; namespace OpenRCT2::Ui { class Win32Context : public IPlatformUiContext { private: HMODULE _win32module; public: Win32Context() { _win32module = GetModuleHandle(nullptr); } void SetWindowIcon(SDL_Window* window) override { if (_win32module != nullptr) { HICON icon = LoadIcon(_win32module, MAKEINTRESOURCE(IDI_ICON)); if (icon != nullptr) { HWND hwnd = GetHWND(window); if (hwnd != nullptr) { SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(icon)); } } } } bool IsSteamOverlayAttached() override { return (GetModuleHandleW(L"GameOverlayRenderer.dll") != nullptr); } void ShowMessageBox(SDL_Window* window, const std::string& message) override { HWND hwnd = GetHWND(window); std::wstring messageW = String::ToWideChar(message); MessageBoxW(hwnd, messageW.c_str(), L"OpenRCT2", MB_OK); } bool HasMenuSupport() override { return false; } int32_t ShowMenuDialog( const std::vector& options, const std::string& title, const std::string& text) override { return -1; } void OpenFolder(const std::string& path) override { std::wstring pathW = String::ToWideChar(path); ShellExecuteW(NULL, L"open", pathW.c_str(), NULL, NULL, SW_SHOWNORMAL); } void OpenURL(const std::string& url) override { std::wstring urlW = String::ToWideChar(url); ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWNORMAL); } std::string ShowFileDialogInternal(SDL_Window* window, const FileDialogDesc& desc, bool isFolder) { std::string resultFilename; CCoInitialize coInitialize(COINIT_APARTMENTTHREADED); if (coInitialize) { CLSID dialogId = CLSID_FileOpenDialog; DWORD flagsToSet = FOS_FORCEFILESYSTEM; if (desc.Type == FileDialogType::Save) { dialogId = CLSID_FileSaveDialog; flagsToSet |= FOS_OVERWRITEPROMPT | FOS_CREATEPROMPT | FOS_STRICTFILETYPES; } if (isFolder) { flagsToSet |= FOS_PICKFOLDERS; } ComPtr fileDialog; if (SUCCEEDED( CoCreateInstance(dialogId, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(fileDialog.GetAddressOf())))) { DWORD flags; if (SUCCEEDED(fileDialog->GetOptions(&flags)) && SUCCEEDED(fileDialog->SetOptions(flags | flagsToSet))) { fileDialog->SetTitle(String::ToWideChar(desc.Title).c_str()); fileDialog->SetFileName(String::ToWideChar(Path::GetFileName(desc.DefaultFilename)).c_str()); // Set default directory (optional, don't fail the operation if it fails to set) ComPtr defaultDirectory; if (SUCCEEDED(SHCreateItemFromParsingName( String::ToWideChar(desc.InitialDirectory).c_str(), nullptr, IID_PPV_ARGS(defaultDirectory.GetAddressOf())))) { fileDialog->SetFolder(defaultDirectory.Get()); } // Opt-in to automatic extensions, this will ensure extension of the selected file matches the filter // Setting it to an empty string so "All Files" does not get anything appended fileDialog->SetDefaultExtension(L""); // Filters need an "auxillary" storage for wide strings std::vector filtersStorage; auto filters = GetFilters(desc.Filters, filtersStorage); bool filtersSet = true; if (!filters.empty()) { filtersSet = SUCCEEDED(fileDialog->SetFileTypes(static_cast(filters.size()), filters.data())); } if (filtersSet && SUCCEEDED(fileDialog->Show(nullptr))) { ComPtr resultItem; if (SUCCEEDED(fileDialog->GetResult(resultItem.GetAddressOf()))) { PWSTR filePath = nullptr; if (SUCCEEDED(resultItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath))) { resultFilename = String::ToUtf8(filePath); CoTaskMemFree(filePath); } } } } } } return resultFilename; } std::string ShowFileDialog(SDL_Window* window, const FileDialogDesc& desc) override { return ShowFileDialogInternal(window, desc, false); } std::string ShowDirectoryDialog(SDL_Window* window, const std::string& title) override { FileDialogDesc desc; desc.Title = title; return ShowFileDialogInternal(window, desc, true); } bool HasFilePicker() const override { return true; } private: HWND GetHWND(SDL_Window* window) { HWND result = nullptr; if (window != nullptr) { SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); if (SDL_GetWindowWMInfo(window, &wmInfo) != SDL_TRUE) { log_error("SDL_GetWindowWMInfo failed %s", SDL_GetError()); exit(-1); } result = wmInfo.info.win.window; } return result; } static std::vector GetFilters( const std::vector& filters, std::vector& outFiltersStorage) { std::vector result; for (const auto& filter : filters) { outFiltersStorage.emplace_back(String::ToWideChar(filter.Name)); outFiltersStorage.emplace_back(String::ToWideChar(filter.Pattern)); } for (auto it = outFiltersStorage.begin(); it != outFiltersStorage.end();) { const wchar_t* Name = (*it++).c_str(); const wchar_t* Pattern = (*it++).c_str(); result.push_back({ Name, Pattern }); } return result; } }; std::unique_ptr CreatePlatformUiContext() { return std::make_unique(); } } // namespace OpenRCT2::Ui #endif // _WIN32