mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-04 13:42:55 +01:00
Refactor file / directory dialogs to UiContext
This commit is contained in:
@@ -16,11 +16,14 @@
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <sstream>
|
||||
#include <openrct2/common.h>
|
||||
#include <SDL.h>
|
||||
#include <openrct2/core/String.hpp>
|
||||
#include <openrct2/ui/UiContext.h>
|
||||
#include "UiContext.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
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<FileDialogDesc::Filter> 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<FileDialogDesc::Filter> 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()
|
||||
|
||||
@@ -16,18 +16,38 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <sstream>
|
||||
#include <openrct2/common.h>
|
||||
#include <openrct2/core/Math.hpp>
|
||||
#include <openrct2/core/Path.hpp>
|
||||
#include <openrct2/core/String.hpp>
|
||||
#include <openrct2/ui/UiContext.h>
|
||||
#include "UiContext.h"
|
||||
|
||||
#undef interface
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_syswm.h>
|
||||
#include <openrct2/core/String.hpp>
|
||||
#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<size_t>(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<FileDialogDesc::Filter> filters)
|
||||
{
|
||||
std::wstringstream filtersb;
|
||||
for (auto filter : filters)
|
||||
{
|
||||
filtersb << String::ToUtf16(filter.Name)
|
||||
<< '\0'
|
||||
<< String::ToUtf16(filter.Pattern)
|
||||
<< '\0';
|
||||
}
|
||||
return filtersb.str();
|
||||
}
|
||||
};
|
||||
|
||||
IPlatformUiContext * CreatePlatformUiContext()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Filter> 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;
|
||||
|
||||
Reference in New Issue
Block a user