2017-03-25 15:57:02 +01:00
|
|
|
/*****************************************************************************
|
2022-10-01 09:42:14 +02:00
|
|
|
* Copyright (c) 2014-2022 OpenRCT2 developers
|
2018-06-15 14:07:34 +02:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*****************************************************************************/
|
2017-03-25 15:57:02 +01:00
|
|
|
|
2017-04-01 14:38:52 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
2018-06-18 18:46:49 +02:00
|
|
|
// Windows.h needs to be included first
|
2020-07-30 22:18:23 +02:00
|
|
|
// clang-format off
|
2018-07-21 16:17:06 +02:00
|
|
|
# include <windows.h>
|
2020-07-30 22:18:23 +02:00
|
|
|
# include <shellapi.h>
|
|
|
|
# include <commdlg.h>
|
|
|
|
// clang-format on
|
2018-07-21 16:17:06 +02:00
|
|
|
# undef CreateWindow
|
2018-06-18 18:46:49 +02:00
|
|
|
|
|
|
|
// Then the rest
|
2018-07-21 16:17:06 +02:00
|
|
|
# include "UiContext.h"
|
2018-06-22 23:22:29 +02:00
|
|
|
|
2019-01-02 21:23:58 +01:00
|
|
|
# include <SDL.h>
|
|
|
|
# include <SDL_syswm.h>
|
2018-08-12 13:50:40 +02:00
|
|
|
# include <algorithm>
|
2018-07-21 16:17:06 +02:00
|
|
|
# include <openrct2/common.h>
|
|
|
|
# include <openrct2/core/Path.hpp>
|
|
|
|
# include <openrct2/core/String.hpp>
|
|
|
|
# include <openrct2/ui/UiContext.h>
|
|
|
|
# include <shlobj.h>
|
|
|
|
# include <sstream>
|
2017-03-25 15:57:02 +01:00
|
|
|
|
|
|
|
// Native resource IDs
|
2018-07-21 16:17:06 +02:00
|
|
|
# include "../../resources/resource.h"
|
2017-03-25 15:57:02 +01:00
|
|
|
|
2017-04-01 18:27:39 +02:00
|
|
|
static std::wstring SHGetPathFromIDListLongPath(LPCITEMIDLIST pidl)
|
|
|
|
{
|
2022-01-31 22:35:48 +01:00
|
|
|
// Limit path length to 32K
|
|
|
|
std::wstring pszPath(std::numeric_limits<int16_t>().max(), 0);
|
|
|
|
auto result = SHGetPathFromIDListEx(pidl, pszPath.data(), static_cast<DWORD>(pszPath.size()), GPFIDL_DEFAULT);
|
|
|
|
if (result)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
2022-01-31 22:35:48 +01:00
|
|
|
// Truncate at first null terminator
|
|
|
|
auto length = pszPath.find(L'\0');
|
|
|
|
if (length != std::wstring::npos)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
2022-01-31 22:35:48 +01:00
|
|
|
pszPath.resize(length);
|
|
|
|
pszPath.shrink_to_fit();
|
2017-04-01 18:27:39 +02:00
|
|
|
}
|
2022-01-31 22:35:48 +01:00
|
|
|
return pszPath;
|
2017-04-01 18:27:39 +02:00
|
|
|
}
|
2022-01-31 22:35:48 +01:00
|
|
|
return std::wstring();
|
2017-04-01 18:27:39 +02:00
|
|
|
}
|
|
|
|
|
2018-02-16 00:43:16 +01:00
|
|
|
namespace OpenRCT2::Ui
|
2017-03-25 15:57:02 +01:00
|
|
|
{
|
|
|
|
class Win32Context : public IPlatformUiContext
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
HMODULE _win32module;
|
|
|
|
|
|
|
|
public:
|
|
|
|
Win32Context()
|
|
|
|
{
|
2022-10-05 23:47:07 +02:00
|
|
|
_win32module = GetModuleHandle(nullptr);
|
2017-03-25 15:57:02 +01:00
|
|
|
}
|
|
|
|
|
2018-06-22 23:22:29 +02:00
|
|
|
void SetWindowIcon(SDL_Window* window) override
|
2017-03-25 15:57:02 +01:00
|
|
|
{
|
|
|
|
if (_win32module != nullptr)
|
|
|
|
{
|
2022-10-05 23:47:07 +02:00
|
|
|
HICON icon = LoadIcon(_win32module, MAKEINTRESOURCE(IDI_ICON));
|
2017-03-25 15:57:02 +01:00
|
|
|
if (icon != nullptr)
|
|
|
|
{
|
|
|
|
HWND hwnd = GetHWND(window);
|
|
|
|
if (hwnd != nullptr)
|
|
|
|
{
|
2022-10-05 23:47:07 +02:00
|
|
|
SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon));
|
2017-03-25 15:57:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsSteamOverlayAttached() override
|
|
|
|
{
|
2022-10-05 23:47:07 +02:00
|
|
|
return (GetModuleHandleW(L"GameOverlayRenderer.dll") != nullptr);
|
2017-03-25 15:57:02 +01:00
|
|
|
}
|
|
|
|
|
2018-06-22 23:22:29 +02:00
|
|
|
void ShowMessageBox(SDL_Window* window, const std::string& message) override
|
2017-04-01 14:38:52 +02:00
|
|
|
{
|
|
|
|
HWND hwnd = GetHWND(window);
|
2019-07-23 00:11:12 +02:00
|
|
|
std::wstring messageW = String::ToWideChar(message);
|
2017-04-01 14:38:52 +02:00
|
|
|
MessageBoxW(hwnd, messageW.c_str(), L"OpenRCT2", MB_OK);
|
|
|
|
}
|
|
|
|
|
2020-10-21 19:53:22 +02:00
|
|
|
bool HasMenuSupport() override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t ShowMenuDialog(
|
|
|
|
const std::vector<std::string>& options, const std::string& title, const std::string& text) override
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-09-04 11:40:51 +02:00
|
|
|
void OpenFolder(const std::string& path) override
|
|
|
|
{
|
2019-07-23 00:11:12 +02:00
|
|
|
std::wstring pathW = String::ToWideChar(path);
|
2018-09-04 11:40:51 +02:00
|
|
|
ShellExecuteW(NULL, L"open", pathW.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
}
|
|
|
|
|
2020-07-26 21:57:56 +02:00
|
|
|
void OpenURL(const std::string& url) override
|
|
|
|
{
|
|
|
|
std::wstring urlW = String::ToWideChar(url);
|
|
|
|
ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
}
|
|
|
|
|
2018-06-22 23:22:29 +02:00
|
|
|
std::string ShowFileDialog(SDL_Window* window, const FileDialogDesc& desc) override
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
2019-07-23 00:11:12 +02:00
|
|
|
std::wstring wcFilename = String::ToWideChar(desc.DefaultFilename);
|
2018-06-20 17:11:35 +02:00
|
|
|
wcFilename.resize(std::max<size_t>(wcFilename.size(), MAX_PATH));
|
2017-04-01 18:27:39 +02:00
|
|
|
|
2019-07-23 00:11:12 +02:00
|
|
|
std::wstring wcTitle = String::ToWideChar(desc.Title);
|
|
|
|
std::wstring wcInitialDirectory = String::ToWideChar(desc.InitialDirectory);
|
2017-04-01 18:27:39 +02:00
|
|
|
std::wstring wcFilters = GetFilterString(desc.Filters);
|
|
|
|
|
|
|
|
// Set open file name options
|
2018-06-05 16:06:18 +02:00
|
|
|
OPENFILENAMEW openFileName = {};
|
2017-04-01 18:27:39 +02:00
|
|
|
openFileName.lStructSize = sizeof(OPENFILENAMEW);
|
|
|
|
openFileName.lpstrTitle = wcTitle.c_str();
|
|
|
|
openFileName.lpstrInitialDir = wcInitialDirectory.c_str();
|
|
|
|
openFileName.lpstrFilter = wcFilters.c_str();
|
|
|
|
openFileName.lpstrFile = &wcFilename[0];
|
2020-04-18 12:15:07 +02:00
|
|
|
openFileName.nMaxFile = static_cast<DWORD>(wcFilename.size());
|
2017-04-01 18:27:39 +02:00
|
|
|
|
|
|
|
// Open dialog
|
|
|
|
BOOL dialogResult = FALSE;
|
|
|
|
DWORD commonFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
2022-01-28 23:50:24 +01:00
|
|
|
if (desc.Type == FileDialogType::Open)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
|
|
|
openFileName.Flags = commonFlags | OFN_NONETWORKBUTTON | OFN_FILEMUSTEXIST;
|
|
|
|
dialogResult = GetOpenFileNameW(&openFileName);
|
|
|
|
}
|
2022-01-28 23:50:24 +01:00
|
|
|
else if (desc.Type == FileDialogType::Save)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
|
|
|
openFileName.Flags = commonFlags | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT;
|
|
|
|
dialogResult = GetSaveFileNameW(&openFileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string resultFilename;
|
|
|
|
if (dialogResult)
|
|
|
|
{
|
|
|
|
resultFilename = String::ToUtf8(openFileName.lpstrFile);
|
|
|
|
|
|
|
|
// If there is no extension, append the pattern
|
|
|
|
std::string resultExtension = Path::GetExtension(resultFilename);
|
|
|
|
if (resultExtension.empty())
|
|
|
|
{
|
2018-06-20 17:28:51 +02:00
|
|
|
int32_t filterIndex = openFileName.nFilterIndex - 1;
|
2017-04-01 18:27:39 +02:00
|
|
|
|
|
|
|
assert(filterIndex >= 0);
|
2020-04-18 12:15:07 +02:00
|
|
|
assert(filterIndex < static_cast<int32_t>(desc.Filters.size()));
|
2017-04-01 18:27:39 +02:00
|
|
|
|
|
|
|
std::string pattern = desc.Filters[filterIndex].Pattern;
|
|
|
|
std::string patternExtension = Path::GetExtension(pattern);
|
|
|
|
if (!patternExtension.empty())
|
|
|
|
{
|
|
|
|
resultFilename += patternExtension;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resultFilename;
|
|
|
|
}
|
|
|
|
|
2018-06-22 23:22:29 +02:00
|
|
|
std::string ShowDirectoryDialog(SDL_Window* window, const std::string& title) override
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
|
|
|
std::string result;
|
|
|
|
|
2022-01-30 12:32:00 +01:00
|
|
|
// Initialize COM
|
|
|
|
if (SUCCEEDED(CoInitializeEx(0, COINIT_APARTMENTTHREADED)))
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
2019-07-23 00:11:12 +02:00
|
|
|
std::wstring titleW = String::ToWideChar(title);
|
2018-06-05 16:06:18 +02:00
|
|
|
BROWSEINFOW bi = {};
|
2017-04-01 18:27:39 +02:00
|
|
|
bi.lpszTitle = titleW.c_str();
|
|
|
|
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON;
|
|
|
|
|
|
|
|
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
|
|
|
|
if (pidl != nullptr)
|
|
|
|
{
|
|
|
|
result = String::ToUtf8(SHGetPathFromIDListLongPath(pidl));
|
|
|
|
}
|
2017-08-22 23:07:36 +02:00
|
|
|
CoTaskMemFree(pidl);
|
2022-01-30 12:32:00 +01:00
|
|
|
|
|
|
|
CoUninitialize();
|
2017-04-01 18:27:39 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
log_error("Error opening directory browse window");
|
|
|
|
}
|
2017-08-22 23:07:36 +02:00
|
|
|
|
|
|
|
// SHBrowseForFolderW might minimize the main window,
|
|
|
|
// so make sure that it's visible again.
|
|
|
|
ShowWindow(GetHWND(window), SW_RESTORE);
|
|
|
|
|
2017-04-01 18:27:39 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-11-08 13:29:44 +01:00
|
|
|
bool HasFilePicker() const override
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-03-25 15:57:02 +01:00
|
|
|
private:
|
2018-06-22 23:22:29 +02:00
|
|
|
HWND GetHWND(SDL_Window* window)
|
2017-03-25 15:57:02 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2017-04-01 18:27:39 +02:00
|
|
|
|
2021-07-24 23:41:50 +02:00
|
|
|
static std::wstring GetFilterString(const std::vector<FileDialogDesc::Filter>& filters)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
|
|
|
std::wstringstream filtersb;
|
2021-01-11 22:14:15 +01:00
|
|
|
for (const auto& filter : filters)
|
2017-04-01 18:27:39 +02:00
|
|
|
{
|
2019-07-23 00:11:12 +02:00
|
|
|
filtersb << String::ToWideChar(filter.Name) << '\0' << String::ToWideChar(filter.Pattern) << '\0';
|
2017-04-01 18:27:39 +02:00
|
|
|
}
|
|
|
|
return filtersb.str();
|
|
|
|
}
|
2017-03-25 15:57:02 +01:00
|
|
|
};
|
|
|
|
|
2022-07-28 19:19:38 +02:00
|
|
|
std::unique_ptr<IPlatformUiContext> CreatePlatformUiContext()
|
2017-03-25 15:57:02 +01:00
|
|
|
{
|
2022-07-28 19:19:38 +02:00
|
|
|
return std::make_unique<Win32Context>();
|
2017-03-25 15:57:02 +01:00
|
|
|
}
|
2018-05-04 22:40:09 +02:00
|
|
|
} // namespace OpenRCT2::Ui
|
2017-03-25 15:57:02 +01:00
|
|
|
|
2017-04-01 14:38:52 +02:00
|
|
|
#endif // _WIN32
|