diff --git a/src/openrct2-ui/UiContext.Linux.cpp b/src/openrct2-ui/UiContext.Linux.cpp index a23cf050ac..43febb6b74 100644 --- a/src/openrct2-ui/UiContext.Linux.cpp +++ b/src/openrct2-ui/UiContext.Linux.cpp @@ -16,11 +16,14 @@ #ifdef __linux__ +#include #include -#include #include +#include #include "UiContext.h" +#include + namespace OpenRCT2 { namespace Ui { enum class DIALOG_TYPE @@ -107,6 +110,100 @@ namespace OpenRCT2 { namespace Ui } } + std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) override + { + std::string result; + std::string executablePath; + DIALOG_TYPE dtype = GetDialogApp(&executablePath); + switch (dtype) { + case DIALOG_TYPE::KDIALOG: + { + std::string action = + (desc.Type == FILE_DIALOG_TYPE::OPEN) ? "--getopenfilename" : + "--getsavefilename"; + std::string filter = GetKDialogFilterString(desc.Filters); + std::string cmd = String::StdFormat("%s --title '%s' %s '%s/' ~ '%s'", executablePath, desc.Title, action, desc.InitialDirectory, filter); + std::string output; + if (Execute(cmd, &output) != 0) + { + result = output; + } + break; + } + case DIALOG_TYPE::ZENITY: + { + std::string action = "--file-selection"; + std::string flags; + if (desc.Type == FILE_DIALOG_TYPE::SAVE) + { + flags = "--confirm-overwrite --save"; + } + std::string filters = GetZenityFilterString(desc.Filters); + std::string cmd = String::StdFormat("%s %s --filename='%s/' %s --title='%s' / %s", executablePath, action, desc.InitialDirectory, flags, desc.Title, filters); + std::string output; + if (Execute(cmd, &output) != 0) + { + result = output; + } + break; + } + default: + log_error("KDialog or Zenity not installed."); + break; + } + + log_verbose("filename = %s", result.c_str()); + + if (desc.Type == FILE_DIALOG_TYPE::OPEN && access(result, F_OK) == -1) + { + std::string msg = String::StdFormat("\"%s\" not found: %s, please choose another file\n", result.c_str(), strerror(errno)); + ShowMessageBox(window, msg); + return ShowFileDialog(window, desc); + } + else if (desc.Type == FILE_DIALOG_TYPE::SAVE && access(result, F_OK) != -1 && dtype == DIALOG_TYPE::KDIALOG) + { + std::string cmd = String::StdFormat("%s --yesno \"Overwrite %s?\"", executablePath, result.c_str()); + if (Execute(cmd) != 0) + { + result = std::string(); + } + } + return result; + } + + std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) override + { + std::string result; + std::string executablePath; + DIALOG_TYPE dtype = GetDialogApp(&executablePath); + switch (dtype) { + case DIALOG_TYPE::KDIALOG: + { + std::string output; + std::string cmd = String::Format("%s --title '%s' --getexistingdirectory /", executablePath.c_str(), title); + if (Execute(cmd, &output) == 0) + { + result = output; + } + break; + } + case DIALOG_TYPE::ZENITY: + { + std::string output; + std::string cmd = String::Format("%s --title='%s' --file-selection --directory /", executablePath.c_str(), title); + if (Execute(cmd, &output) == 0) + { + result = output; + } + break; + } + default: + log_error("KDialog or Zenity not installed."); + break; + } + return result; + } + private: static DIALOG_TYPE GetDialogApp(std::string * executablePath) { @@ -168,6 +265,66 @@ namespace OpenRCT2 { namespace Ui // Return exit code return pclose(fpipe); } + + static std::string GetKDialogFilterString(const std::vector filters) + { + std::stringstream filtersb; + bool first = true; + for (const auto &filter : filters) + { + // KDialog wants filters space-delimited and we don't expect ';' anywhere else + std::string pattern = filter.Pattern; + for (sint32 i = 0; i < pattern.size(); i++) + { + if (pattern[i] == ';') + { + pattern[i] = ' '; + } + } + + if (first) + { + filtersb << String::StdFormat("%s | %s", pattern.c_str(), filter.Name.c_str()); + first = false; + } + else + { + filtersb << String::StdFormat("\\n%s | %s", pattern.c_str(), filter.Name.c_str()); + } + } + return filtersb.str(); + } + + static std::string GetZenityFilterString(const std::vector filters) + { + // Zenity seems to be case sensitive, while KDialog isn't + std::stringstream filtersb; + bool first = true; + for (const auto &filter : filters) + { + filtersb << " --file-filter='" << filter.Name << " | "; + for (char c : filter.Pattern) + { + if (c == ';') + { + filtersb << ' '; + } + else if (isalpha(c)) + { + filtersb << '[' + << toupper(c) + << tolower(c) + << ']'; + } + else + { + filtersb << c; + } + } + filtersb << "'"; + } + return filtersb.str(); + } }; IPlatformUiContext * CreatePlatformUiContext() diff --git a/src/openrct2-ui/UiContext.Win32.cpp b/src/openrct2-ui/UiContext.Win32.cpp index e3f8ea942a..78f266e8bf 100644 --- a/src/openrct2-ui/UiContext.Win32.cpp +++ b/src/openrct2-ui/UiContext.Win32.cpp @@ -16,18 +16,38 @@ #ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN - +#include #include +#include +#include +#include +#include +#include "UiContext.h" + +#undef interface #include +#include #include #include -#include -#include "UiContext.h" // Native resource IDs #include "../../../resources/resource.h" +static std::wstring SHGetPathFromIDListLongPath(LPCITEMIDLIST pidl) +{ + std::wstring pszPath(MAX_PATH, 0); + while (!SHGetPathFromIDListEx(pidl, &pszPath[0], (DWORD)pszPath.size(), 0)) + { + if (pszPath.size() >= SHRT_MAX) + { + // Clearly not succeeding at all, bail + return std::wstring(); + } + pszPath.resize(pszPath.size() * 2); + } + return pszPath; +} + namespace OpenRCT2 { namespace Ui { class Win32Context : public IPlatformUiContext @@ -69,6 +89,93 @@ namespace OpenRCT2 { namespace Ui MessageBoxW(hwnd, messageW.c_str(), L"OpenRCT2", MB_OK); } + std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) override + { + std::wstring wcFilename = String::ToUtf16(desc.DefaultFilename); + wcFilename.resize(Math::Max(wcFilename.size(), MAX_PATH)); + + std::wstring wcTitle = String::ToUtf16(desc.Title); + std::wstring wcInitialDirectory = String::ToUtf16(desc.InitialDirectory); + std::wstring wcFilters = GetFilterString(desc.Filters); + + // Set open file name options + OPENFILENAMEW openFileName = { 0 }; + openFileName.lStructSize = sizeof(OPENFILENAMEW); + openFileName.hwndOwner = GetHWND(window); + openFileName.lpstrTitle = wcTitle.c_str(); + openFileName.lpstrInitialDir = wcInitialDirectory.c_str(); + openFileName.lpstrFilter = wcFilters.c_str(); + openFileName.lpstrFile = &wcFilename[0]; + openFileName.nMaxFile = (DWORD)wcFilename.size(); + + // Open dialog + BOOL dialogResult = FALSE; + DWORD commonFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; + if (desc.Type == FILE_DIALOG_TYPE::OPEN) + { + openFileName.Flags = commonFlags | OFN_NONETWORKBUTTON | OFN_FILEMUSTEXIST; + dialogResult = GetOpenFileNameW(&openFileName); + } + else if (desc.Type == FILE_DIALOG_TYPE::SAVE) + { + 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()) + { + sint32 filterIndex = openFileName.nFilterIndex - 1; + + assert(filterIndex >= 0); + assert(filterIndex < desc.Filters.size()); + + std::string pattern = desc.Filters[filterIndex].Pattern; + std::string patternExtension = Path::GetExtension(pattern); + if (!patternExtension.empty()) + { + resultFilename += patternExtension; + } + } + } + return resultFilename; + } + + std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) override + { + std::string result; + + // Initialize COM and get a pointer to the shell memory allocator + LPMALLOC lpMalloc; + if (SUCCEEDED(CoInitializeEx(0, COINIT_APARTMENTTHREADED)) && + SUCCEEDED(SHGetMalloc(&lpMalloc))) + { + std::wstring titleW = String::ToUtf16(title); + BROWSEINFOW bi = { 0 }; + bi.hwndOwner = GetHWND(window); + 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)); + } + } + else + { + log_error("Error opening directory browse window"); + } + CoUninitialize(); + return result; + } + private: HWND GetHWND(SDL_Window * window) { @@ -96,6 +203,19 @@ namespace OpenRCT2 { namespace Ui } return result; } + + static std::wstring GetFilterString(const std::vector filters) + { + std::wstringstream filtersb; + for (auto filter : filters) + { + filtersb << String::ToUtf16(filter.Name) + << '\0' + << String::ToUtf16(filter.Pattern) + << '\0'; + } + return filtersb.str(); + } }; IPlatformUiContext * CreatePlatformUiContext() diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index 1adca08a8b..495e5597a3 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -521,6 +521,16 @@ public: _platformUiContext->ShowMessageBox(_window, message); } + std::string ShowFileDialog(const FileDialogDesc &desc) override + { + return _platformUiContext->ShowFileDialog(_window, desc); + } + + std::string ShowDirectoryDialog(const std::string &title) override + { + return _platformUiContext->ShowDirectoryDialog(_window, title); + } + private: void OnResize(sint32 width, sint32 height) { diff --git a/src/openrct2-ui/UiContext.h b/src/openrct2-ui/UiContext.h index 7293491e55..f713d76596 100644 --- a/src/openrct2-ui/UiContext.h +++ b/src/openrct2-ui/UiContext.h @@ -27,14 +27,18 @@ namespace OpenRCT2 namespace Ui { - interface IUiContext; + struct FileDialogDesc; + interface IUiContext; interface IPlatformUiContext { virtual ~IPlatformUiContext() = default; virtual void SetWindowIcon(SDL_Window * window) abstract; virtual bool IsSteamOverlayAttached() abstract; - virtual void ShowMessageBox(SDL_Window * window, const std::string &message) abstract; + + virtual void ShowMessageBox(SDL_Window * window, const std::string &message) abstract; + virtual std::string ShowFileDialog(SDL_Window * window, const FileDialogDesc &desc) abstract; + virtual std::string ShowDirectoryDialog(SDL_Window * window, const std::string &title) abstract; }; IUiContext * CreateUiContext(); diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index c0db8be37b..244749363b 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -680,4 +680,29 @@ extern "C" { GetContext()->GetUiContext()->SetCursorTrap(value); } + + bool platform_open_common_file_dialog(utf8 * outFilename, file_dialog_desc * desc, size_t outSize) + { + FileDialogDesc desc2; + desc2.Type = (FILE_DIALOG_TYPE)desc->type; + desc2.Title = String::ToStd(desc->title); + desc2.InitialDirectory = String::ToStd(desc->initial_directory); + desc2.DefaultFilename = String::ToStd(desc->default_filename); + for (const auto &filter : desc->filters) + { + if (filter.name != nullptr) + { + desc2.Filters.push_back({ String::ToStd(filter.name), String::ToStd(filter.pattern) }); + } + } + std::string result = GetContext()->GetUiContext()->ShowFileDialog(desc2); + String::Set(outFilename, outSize, result.c_str()); + return !result.empty(); + } + + utf8 * platform_open_directory_browser(const utf8 * title) + { + std::string result = GetContext()->GetUiContext()->ShowDirectoryDialog(title); + return String::Duplicate(result.c_str()); + } } diff --git a/src/openrct2/platform/linux.c b/src/openrct2/platform/linux.c index 8c28e0a215..f6da9bf162 100644 --- a/src/openrct2/platform/linux.c +++ b/src/openrct2/platform/linux.c @@ -39,8 +39,6 @@ #include "../util/util.h" #include "platform.h" -typedef enum { DT_NONE, DT_KDIALOG, DT_ZENITY } dialog_type; - void platform_get_exe_path(utf8 *outPath, size_t outSize) { char exePath[MAX_PATH]; @@ -222,217 +220,6 @@ uint8 platform_get_locale_measurement_format(){ return MEASUREMENT_FORMAT_METRIC; } -static void execute_cmd(char *command, sint32 *exit_value, char *buf, size_t *buf_size) { - FILE *f; - size_t n_chars; - - log_verbose("executing \"%s\"...\n", command); - f = popen(command, "r"); - - if (buf && buf_size) { - n_chars = fread(buf, 1, *buf_size, f); - - // some commands may return a new-line terminated result, trim that - if (n_chars > 0 && buf[n_chars - 1] == '\n') { - buf[n_chars - 1] = '\0'; - } - // make sure string is null-terminated - if (n_chars == *buf_size) { - n_chars--; - } - buf[n_chars] = '\0'; - - // account for null terminator - *buf_size = n_chars + 1; - } else { - fflush(f); - } - - if (exit_value) - *exit_value = pclose(f); - else - pclose(f); -} - -bool platform_open_common_file_dialog(utf8 *outFilename, file_dialog_desc *desc, size_t outSize) { - sint32 exit_value; - char executable[MAX_PATH]; - char cmd[OPENRCT2_MAX_COMMAND_LENGTH]; - char result[OPENRCT2_MAX_COMMAND_LENGTH]; - size_t size; - dialog_type dtype; - char *action = NULL; - char *flags = NULL; - char filter[OPENRCT2_MAX_COMMAND_LENGTH] = { 0 }; - char filterPatternRegex[64]; - - size = OPENRCT2_MAX_COMMAND_LENGTH; - dtype = get_dialog_app(executable, &size); - - switch (dtype) { - case DT_KDIALOG: - switch (desc->type) { - case FD_OPEN: - action = "--getopenfilename"; - break; - case FD_SAVE: - action = "--getsavefilename"; - break; - } - - { - bool first = true; - for (sint32 j = 0; j < countof(desc->filters); j++) { - if (desc->filters[j].pattern && desc->filters[j].name) { - char filterTemp[100] = { 0 }; - if (first) { - snprintf(filterTemp, countof(filterTemp), "%s | %s", desc->filters[j].pattern, desc->filters[j].name); - first = false; - } else { - snprintf(filterTemp, countof(filterTemp), "\\n%s | %s", desc->filters[j].pattern, desc->filters[j].name); - } - safe_strcat(filter, filterTemp, countof(filter)); - } - } - char filterTemp[100] = { 0 }; - if (first) { - snprintf(filterTemp, countof(filterTemp), "*|%s", (char *)language_get_string(STR_ALL_FILES)); - } else { - snprintf(filterTemp, countof(filterTemp), "\\n*|%s", (char *)language_get_string(STR_ALL_FILES)); - } - safe_strcat(filter, filterTemp, countof(filter)); - - // kdialog wants filters space-delimited and we don't expect ';' anywhere else, - // this is much easier and quicker to do than being overly careful about replacing - // it only where truly needed. - sint32 filterSize = strlen(filter); - for (sint32 i = 0; i < filterSize + 3; i++) { - if (filter[i] == ';') { - filter[i] = ' '; - } - } - } - - snprintf(cmd, OPENRCT2_MAX_COMMAND_LENGTH, "%s --title '%s' %s '%s/' ~ '%s'", executable, desc->title, action, desc->initial_directory, filter); - break; - case DT_ZENITY: - action = "--file-selection"; - switch (desc->type) { - case FD_SAVE: - flags = "--confirm-overwrite --save"; - break; - case FD_OPEN: - flags = ""; - break; - } - - // Zenity seems to be case sensitive, while Kdialog isn't. - for (sint32 j = 0; j < countof(desc->filters); j++) { - if (desc->filters[j].pattern && desc->filters[j].name) { - sint32 regexIterator = 0; - for(sint32 i = 0; i <= strlen(desc->filters[j].pattern); i++) { - if (isalpha(desc->filters[j].pattern[i])) { - filterPatternRegex[regexIterator+0] = '['; - filterPatternRegex[regexIterator+1] = (char)toupper(desc->filters[j].pattern[i]); - filterPatternRegex[regexIterator+2] = (char)tolower(desc->filters[j].pattern[i]); - filterPatternRegex[regexIterator+3] = ']'; - regexIterator += 3; - } - else if(desc->filters[j].pattern[i] == ';') { - filterPatternRegex[regexIterator] = ' '; - } - else { - filterPatternRegex[regexIterator] = (char)desc->filters[j].pattern[i]; - } - regexIterator++; - } - filterPatternRegex[regexIterator+1] = 0; - - char filterTemp[100] = { 0 }; - snprintf(filterTemp, countof(filterTemp), " --file-filter='%s | %s'", desc->filters[j].name, filterPatternRegex); - safe_strcat(filter, filterTemp, countof(filter)); - } - } - char filterTemp[100] = { 0 }; - snprintf(filterTemp, countof(filterTemp), " --file-filter='%s | *'", (char *)language_get_string(STR_ALL_FILES)); - safe_strcat(filter, filterTemp, countof(filter)); - - snprintf(cmd, OPENRCT2_MAX_COMMAND_LENGTH, "%s %s --filename='%s/' %s --title='%s' / %s", executable, action, desc->initial_directory, flags, desc->title, filter); - break; - default: return 0; - } - - size = OPENRCT2_MAX_COMMAND_LENGTH; - execute_cmd(cmd, &exit_value, result, &size); - - if (exit_value != 0) { - return 0; - } - - result[size-1] = '\0'; - log_verbose("filename = %s", result); - - if (desc->type == FD_OPEN && access(result, F_OK) == -1) { - char msg[OPENRCT2_MAX_COMMAND_LENGTH]; - - snprintf(msg, OPENRCT2_MAX_COMMAND_LENGTH, "\"%s\" not found: %s, please choose another file\n", result, strerror(errno)); - platform_show_messagebox(msg); - - return platform_open_common_file_dialog(outFilename, desc, outSize); - } else - if (desc->type == FD_SAVE && access(result, F_OK) != -1 && dtype == DT_KDIALOG) { - snprintf(cmd, OPENRCT2_MAX_COMMAND_LENGTH, "%s --yesno \"Overwrite %s?\"", executable, result); - - size = OPENRCT2_MAX_COMMAND_LENGTH; - execute_cmd(cmd, &exit_value, 0, 0); - - if (exit_value != 0) { - return 0; - } - } - - safe_strcpy(outFilename, result, outSize); - - return 1; -} - -utf8 *platform_open_directory_browser(const utf8 *title) { - size_t size; - dialog_type dtype; - sint32 exit_value; - char cmd[OPENRCT2_MAX_COMMAND_LENGTH]; - char executable[MAX_PATH]; - char result[OPENRCT2_MAX_COMMAND_LENGTH]; - char *return_value; - - size = MAX_PATH; - dtype = get_dialog_app(executable, &size); - - switch (dtype) { - case DT_KDIALOG: - snprintf(cmd, OPENRCT2_MAX_COMMAND_LENGTH, "%s --title '%s' --getexistingdirectory /", executable, title); - break; - case DT_ZENITY: - snprintf(cmd, OPENRCT2_MAX_COMMAND_LENGTH, "%s --title='%s' --file-selection --directory /", executable, title); - break; - default: return 0; - } - - size = OPENRCT2_MAX_COMMAND_LENGTH; - execute_cmd(cmd, &exit_value, result, &size); - - if (exit_value != 0) { - return NULL; - } - - assert(size - 1 < countof(result)); - result[size-1] = '\0'; - - return_value = _strdup(result); - - return return_value; -} - #ifndef NO_TTF bool platform_get_font_path(TTFFontDescriptor *font, utf8 *buffer, size_t size) { diff --git a/src/openrct2/platform/windows.c b/src/openrct2/platform/windows.c index 79b77baab4..4d3a6c32be 100644 --- a/src/openrct2/platform/windows.c +++ b/src/openrct2/platform/windows.c @@ -584,151 +584,6 @@ void platform_get_user_directory(utf8 *outPath, const utf8 *subDirectory, size_t } } -/** - * - * rct2: 0x004080EA - */ -bool platform_open_common_file_dialog(utf8 *outFilename, file_dialog_desc *desc, size_t outSize) -{ - OPENFILENAMEW openFileName; - wchar_t wcFilename[MAX_PATH]; - - // Copy default filename to result filename buffer - if (desc->default_filename == NULL) { - wcFilename[0] = 0; - } else { - wchar_t *wcDefaultFilename = utf8_to_widechar(desc->default_filename); - lstrcpyW(wcFilename, wcDefaultFilename); - free(wcDefaultFilename); - } - - // Set open file name options - memset(&openFileName, 0, sizeof(OPENFILENAMEW)); - openFileName.lStructSize = sizeof(OPENFILENAMEW); - openFileName.hwndOwner = NULL; // windows_get_window_handle(); - openFileName.nMaxFile = MAX_PATH; - openFileName.lpstrTitle = utf8_to_widechar(desc->title); - openFileName.lpstrInitialDir = utf8_to_widechar(desc->initial_directory); - openFileName.lpstrFile = wcFilename; - - utf8 filters[256]; - utf8 *ch = filters; - for (sint32 i = 0; i < countof(desc->filters); i++) { - if (desc->filters[i].name != NULL) { - safe_strcpy(ch, desc->filters[i].name, sizeof(filters) - (ch - filters)); - ch = strchr(ch, 0) + 1; - safe_strcpy(ch, desc->filters[i].pattern, sizeof(filters) - (ch - filters)); - ch = strchr(ch, 0) + 1; - } - } - assert(ch != filters); - *ch = 0; - - // HACK: Replace all null terminators with 0x01 so that we convert the entire string - size_t fullLength = (size_t)(ch - filters); - for (size_t i = 0; i < fullLength; i++) { - if (filters[i] == '\0') { - filters[i] = 1; - } - } - wchar_t *wcFilter = utf8_to_widechar(filters); - fullLength = lstrlenW(wcFilter); - for (size_t i = 0; i < fullLength; i++) { - if (wcFilter[i] == 1) { - wcFilter[i] = '\0'; - } - } - - openFileName.lpstrFilter = wcFilter; - - // Open dialog - BOOL result = false; - DWORD commonFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; - if (desc->type == FD_OPEN) { - openFileName.Flags = commonFlags | OFN_NONETWORKBUTTON | OFN_FILEMUSTEXIST; - result = GetOpenFileNameW(&openFileName); - } else if (desc->type == FD_SAVE) { - openFileName.Flags = commonFlags | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT; - result = GetSaveFileNameW(&openFileName); - } - - // Clean up - free((void*)openFileName.lpstrTitle); - free((void*)openFileName.lpstrInitialDir); - free((void*)openFileName.lpstrFilter); - - if (result) { - utf8 *resultFilename = widechar_to_utf8(openFileName.lpstrFile); - safe_strcpy(outFilename, resultFilename, outSize); - free(resultFilename); - - // If there is no extension, append the pattern - const utf8 *outFilenameExtension = path_get_extension(outFilename); - if (str_is_null_or_empty(outFilenameExtension)) { - sint32 filterIndex = openFileName.nFilterIndex - 1; - - assert(filterIndex >= 0); - assert(filterIndex < countof(desc->filters)); - - const utf8 *pattern = desc->filters[filterIndex].pattern; - const utf8 *patternExtension = path_get_extension(pattern); - if (!str_is_null_or_empty(patternExtension)) { - safe_strcat(outFilename, patternExtension, outSize); - } - } - } - - return result; -} - -utf8 *platform_open_directory_browser(const utf8 *title) -{ - BROWSEINFOW bi; - wchar_t pszBuffer[MAX_PATH], wctitle[256]; - LPITEMIDLIST pidl; - LPMALLOC lpMalloc; - - MultiByteToWideChar(CP_UTF8, 0, title, -1, wctitle, countof(wctitle)); - - // Initialize COM - if (FAILED(CoInitializeEx(0, COINIT_APARTMENTTHREADED))) { - CoUninitialize(); - - log_error("Error opening directory browse window"); - return 0; - } - - // Get a pointer to the shell memory allocator - if (FAILED(SHGetMalloc(&lpMalloc))) { - CoUninitialize(); - - log_error("Error opening directory browse window"); - return 0; - } - - bi.hwndOwner = NULL; - bi.pidlRoot = NULL; - bi.pszDisplayName = pszBuffer; - bi.lpszTitle = wctitle; - bi.ulFlags = BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS; - bi.lpfn = NULL; - bi.lParam = 0; - - utf8 *outPath = NULL; - - if ((pidl = SHBrowseForFolderW(&bi)) != NULL) { - // Copy the path directory to the buffer - if (SHGetPathFromIDListW(pidl, pszBuffer)) { - // Store pszBuffer (and the path) in the outPath - sint32 outPathCapacity = lstrlenW(pszBuffer) * 4 + 1; - outPath = (utf8*)malloc(outPathCapacity); - WideCharToMultiByte(CP_UTF8, 0, pszBuffer, countof(pszBuffer), outPath, outPathCapacity, NULL, NULL); - } - } - CoUninitialize(); - return outPath; -} - /** * * rct2: 0x00407978 diff --git a/src/openrct2/ui/UiContext.h b/src/openrct2/ui/UiContext.h index 49d7e00c4e..48079a2f92 100644 --- a/src/openrct2/ui/UiContext.h +++ b/src/openrct2/ui/UiContext.h @@ -62,6 +62,27 @@ namespace OpenRCT2 return !(lhs == rhs); } + enum class FILE_DIALOG_TYPE + { + OPEN, + SAVE, + }; + + struct FileDialogDesc + { + struct Filter + { + std::string Name; // E.g. "Image Files" + std::string Pattern; // E.g. "*.png;*.jpg;*.gif" + }; + + FILE_DIALOG_TYPE Type = FILE_DIALOG_TYPE::OPEN; + std::string Title; + std::string InitialDirectory; + std::string DefaultFilename; + std::vector Filters; + }; + /** * Represents the window or screen that OpenRCT2 is presented on. */ @@ -82,7 +103,10 @@ namespace OpenRCT2 virtual bool IsSteamOverlayActive() abstract; virtual void ProcessMessages() abstract; virtual void TriggerResize() abstract; - virtual void ShowMessageBox(const std::string &message) abstract; + + virtual void ShowMessageBox(const std::string &message) abstract; + virtual std::string ShowFileDialog(const FileDialogDesc &desc) abstract; + virtual std::string ShowDirectoryDialog(const std::string &title) abstract; // Input virtual const CursorState * GetCursorState() abstract;