mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-17 12:03:07 +01:00
`typedef struct/union/enum name { ... } name_again;` is not needed whe compiling C++, moving the name at the back to be in front of the object and removing `typedef` makes it usable the very same way.
This also replaces typedefs with the using keyword. They have better readability, especially for function pointer types, and would allow more flexibility when used with templates.
1170 lines
40 KiB
C++
1170 lines
40 KiB
C++
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
|
|
/*****************************************************************************
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
|
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* A full copy of the GNU General Public License can be found in licence.txt
|
|
*****************************************************************************/
|
|
#pragma endregion
|
|
|
|
#include <algorithm>
|
|
#include <ctime>
|
|
#include <memory>
|
|
#include <openrct2-ui/interface/Widget.h>
|
|
#include <openrct2-ui/windows/Window.h>
|
|
#include <openrct2/config/Config.h>
|
|
#include <openrct2/Context.h>
|
|
#include <openrct2/core/FileScanner.h>
|
|
#include <openrct2/core/Guard.hpp>
|
|
#include <openrct2/core/Path.hpp>
|
|
#include <openrct2/core/String.hpp>
|
|
#include <openrct2/core/Util.hpp>
|
|
#include <openrct2/Editor.h>
|
|
#include <openrct2/Game.h>
|
|
#include <openrct2/localisation/Localisation.h>
|
|
#include <openrct2/platform/platform.h>
|
|
#include <openrct2/platform/Platform2.h>
|
|
#include <openrct2/title/TitleScreen.h>
|
|
#include <openrct2/util/Util.h>
|
|
#include <openrct2/windows/Intent.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#pragma region Widgets
|
|
|
|
#define WW 340
|
|
#define WH 400
|
|
|
|
enum
|
|
{
|
|
WIDX_BACKGROUND,
|
|
WIDX_TITLE,
|
|
WIDX_CLOSE,
|
|
WIDX_RESIZE,
|
|
WIDX_DEFAULT,
|
|
WIDX_UP,
|
|
WIDX_NEW_FOLDER,
|
|
WIDX_NEW_FILE,
|
|
WIDX_SORT_NAME,
|
|
WIDX_SORT_DATE,
|
|
WIDX_SCROLL,
|
|
WIDX_BROWSE,
|
|
};
|
|
|
|
// 0x9DE48C
|
|
static rct_widget window_loadsave_widgets[] =
|
|
{
|
|
{ WWT_FRAME, 0, 0, WW - 1, 0, WH - 1, STR_NONE, STR_NONE },
|
|
{ WWT_CAPTION, 0, 1, WW - 2, 1, 14, STR_NONE, STR_WINDOW_TITLE_TIP },
|
|
{ WWT_CLOSEBOX, 0, WW - 13, WW - 3, 2, 13, STR_CLOSE_X, STR_CLOSE_WINDOW_TIP }, //Window close button
|
|
{ WWT_RESIZE, 1, 0, WW - 1, WH - 1, WH - 1, 0xFFFFFFFF, STR_NONE }, // tab content panel
|
|
{ WWT_BUTTON, 0, 4, 85, 36, 49, STR_LOADSAVE_DEFAULT, STR_LOADSAVE_DEFAULT_TIP }, // Go to default directory
|
|
{ WWT_BUTTON, 0, 86, 167, 36, 49, STR_FILEBROWSER_ACTION_UP, STR_NONE}, // Up
|
|
{ WWT_BUTTON, 0, 168, 251, 36, 49, STR_FILEBROWSER_ACTION_NEW_FOLDER, STR_NONE }, // New
|
|
{ WWT_BUTTON, 0, 252, 334, 36, 49, STR_FILEBROWSER_ACTION_NEW_FILE, STR_NONE }, // New
|
|
{ WWT_TABLE_HEADER, 0, 4, (WW - 5) / 2, 55, 68, STR_NONE, STR_NONE }, // Name
|
|
{ WWT_TABLE_HEADER, 0, (WW - 5) / 2 + 1, WW - 5 - 1, 55, 68, STR_NONE, STR_NONE }, // Date
|
|
{ WWT_SCROLL, 0, 4, WW - 5, 68, WH - 30, SCROLL_VERTICAL, STR_NONE }, // File list
|
|
{ WWT_BUTTON, 0, 4, 200, WH - 24, WH - 6, STR_FILEBROWSER_USE_SYSTEM_WINDOW, STR_NONE }, // Use native browser
|
|
{ WIDGETS_END }
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Events
|
|
|
|
static void window_loadsave_close(rct_window *w);
|
|
static void window_loadsave_mouseup(rct_window *w, rct_widgetindex widgetIndex);
|
|
static void window_loadsave_resize(rct_window *w);
|
|
static void window_loadsave_scrollgetsize(rct_window *w, sint32 scrollIndex, sint32 *width, sint32 *height);
|
|
static void window_loadsave_scrollmousedown(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y);
|
|
static void window_loadsave_scrollmouseover(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y);
|
|
static void window_loadsave_textinput(rct_window *w, rct_widgetindex widgetIndex, char *text);
|
|
static void window_loadsave_tooltip(rct_window* w, rct_widgetindex widgetIndex, rct_string_id *stringId);
|
|
static void window_loadsave_compute_max_date_width();
|
|
static void window_loadsave_invalidate(rct_window *w);
|
|
static void window_loadsave_paint(rct_window *w, rct_drawpixelinfo *dpi);
|
|
static void window_loadsave_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, sint32 scrollIndex);
|
|
|
|
static rct_window_event_list window_loadsave_events =
|
|
{
|
|
window_loadsave_close,
|
|
window_loadsave_mouseup,
|
|
window_loadsave_resize,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
window_loadsave_scrollgetsize,
|
|
window_loadsave_scrollmousedown,
|
|
nullptr,
|
|
window_loadsave_scrollmouseover,
|
|
window_loadsave_textinput,
|
|
nullptr,
|
|
nullptr,
|
|
window_loadsave_tooltip,
|
|
nullptr,
|
|
nullptr,
|
|
window_loadsave_invalidate,
|
|
window_loadsave_paint,
|
|
window_loadsave_scrollpaint
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
enum
|
|
{
|
|
TYPE_DIRECTORY,
|
|
TYPE_FILE,
|
|
};
|
|
|
|
struct LoadSaveListItem
|
|
{
|
|
std::string name;
|
|
std::string path;
|
|
time_t date_modified;
|
|
std::string date_formatted;
|
|
std::string time_formatted;
|
|
uint8 type;
|
|
bool loaded;
|
|
};
|
|
|
|
static loadsave_callback _loadSaveCallback;
|
|
|
|
static std::vector<LoadSaveListItem> _listItems;
|
|
static char _directory[MAX_PATH];
|
|
static char _shortenedDirectory[MAX_PATH];
|
|
static char _parentDirectory[MAX_PATH];
|
|
static char _extension[32];
|
|
static char _defaultName[MAX_PATH];
|
|
static sint32 _type;
|
|
|
|
static sint32 maxDateWidth = 0;
|
|
static sint32 maxTimeWidth = 0;
|
|
|
|
static void window_loadsave_populate_list(rct_window *w, sint32 includeNewItem, const char *directory, const char *extension);
|
|
static void window_loadsave_select(rct_window *w, const char *path);
|
|
static void window_loadsave_sort_list();
|
|
|
|
static rct_window *window_overwrite_prompt_open(const char *name, const char *path);
|
|
|
|
void window_loadsave_set_loadsave_callback(loadsave_callback cb)
|
|
{
|
|
_loadSaveCallback = cb;
|
|
}
|
|
|
|
static sint32 window_loadsave_get_dir(utf8 *last_save, char *path, const char *subdir, size_t pathSize)
|
|
{
|
|
if (last_save && platform_ensure_directory_exists(last_save))
|
|
safe_strcpy(path, last_save, pathSize);
|
|
else
|
|
platform_get_user_directory(path, subdir, pathSize);
|
|
|
|
if (!platform_ensure_directory_exists(path))
|
|
{
|
|
log_error("Unable to create save directory.");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
rct_window *window_loadsave_open(sint32 type, const char *defaultName)
|
|
{
|
|
_loadSaveCallback = nullptr;
|
|
_type = type;
|
|
_defaultName[0] = '\0';
|
|
|
|
if (!str_is_null_or_empty(defaultName))
|
|
{
|
|
safe_strcpy(_defaultName, defaultName, sizeof(_defaultName));
|
|
}
|
|
|
|
rct_window *w = window_bring_to_front_by_class(WC_LOADSAVE);
|
|
if (w == nullptr)
|
|
{
|
|
w = window_create_centred(WW, WH, &window_loadsave_events, WC_LOADSAVE, WF_STICK_TO_FRONT | WF_RESIZABLE);
|
|
w->widgets = window_loadsave_widgets;
|
|
w->enabled_widgets =
|
|
(1 << WIDX_CLOSE) |
|
|
(1 << WIDX_UP) |
|
|
(1 << WIDX_NEW_FOLDER) |
|
|
(1 << WIDX_NEW_FILE) |
|
|
(1 << WIDX_SORT_NAME) |
|
|
(1 << WIDX_SORT_DATE) |
|
|
(1 << WIDX_BROWSE) |
|
|
(1 << WIDX_DEFAULT);
|
|
|
|
w->min_width = WW;
|
|
w->min_height = WH / 2;
|
|
w->max_width = WW * 2;
|
|
w->max_height = WH * 2;
|
|
}
|
|
|
|
w->no_list_items = 0;
|
|
w->selected_list_item = -1;
|
|
|
|
bool isSave = (type & 0x01) == LOADSAVETYPE_SAVE;
|
|
bool success = false;
|
|
char path[MAX_PATH];
|
|
|
|
switch (type & 0x0E)
|
|
{
|
|
case LOADSAVETYPE_GAME:
|
|
w->widgets[WIDX_TITLE].text = isSave ? STR_FILE_DIALOG_TITLE_SAVE_GAME : STR_FILE_DIALOG_TITLE_LOAD_GAME;
|
|
if (window_loadsave_get_dir(gConfigGeneral.last_save_game_directory, path, "save", sizeof(path)))
|
|
{
|
|
window_loadsave_populate_list(w, isSave, path, isSave ? ".sv6" : ".sv6;.sc6;.sv4;.sc4");
|
|
success = true;
|
|
}
|
|
break;
|
|
|
|
case LOADSAVETYPE_LANDSCAPE:
|
|
w->widgets[WIDX_TITLE].text = isSave ? STR_FILE_DIALOG_TITLE_SAVE_LANDSCAPE : STR_FILE_DIALOG_TITLE_LOAD_LANDSCAPE;
|
|
if (window_loadsave_get_dir(gConfigGeneral.last_save_landscape_directory, path, "landscape", sizeof(path)))
|
|
{
|
|
window_loadsave_populate_list(w, isSave, path, isSave ? ".sc6" : ".sc6;.sv6;.sc4;.sv4");
|
|
success = true;
|
|
}
|
|
break;
|
|
|
|
case LOADSAVETYPE_SCENARIO:
|
|
w->widgets[WIDX_TITLE].text = STR_FILE_DIALOG_TITLE_SAVE_SCENARIO;
|
|
if (window_loadsave_get_dir(gConfigGeneral.last_save_scenario_directory, path, "scenario", sizeof(path)))
|
|
{
|
|
window_loadsave_populate_list(w, isSave, path, ".sc6");
|
|
success = true;
|
|
}
|
|
break;
|
|
|
|
case LOADSAVETYPE_TRACK:
|
|
w->widgets[WIDX_TITLE].text = isSave ? STR_FILE_DIALOG_TITLE_SAVE_TRACK : STR_FILE_DIALOG_TITLE_INSTALL_NEW_TRACK_DESIGN;
|
|
if (window_loadsave_get_dir(gConfigGeneral.last_save_track_directory, path, "track", sizeof(path)))
|
|
{
|
|
window_loadsave_populate_list(w, isSave, path, isSave ? ".td6" : ".td6;.td4");
|
|
success = true;
|
|
}
|
|
break;
|
|
|
|
case LOADSAVETYPE_IMAGE:
|
|
openrct2_assert(!isSave, "Cannot save images through loadsave window");
|
|
w->widgets[WIDX_TITLE].text = STR_FILE_DIALOG_TITLE_LOAD_HEIGHTMAP;
|
|
if (window_loadsave_get_dir(gConfigGeneral.last_save_track_directory, path, "", sizeof(path)))
|
|
{
|
|
window_loadsave_populate_list(w, false, path, ".bmp;.png");
|
|
success = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
log_error("Unsupported load/save type: %d", type & 0x0F);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
window_close(w);
|
|
return nullptr;
|
|
}
|
|
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
window_init_scroll_widgets(w);
|
|
window_loadsave_compute_max_date_width();
|
|
|
|
return w;
|
|
}
|
|
|
|
static void window_loadsave_close(rct_window *w)
|
|
{
|
|
_listItems.clear();
|
|
window_close_by_class(WC_LOADSAVE_OVERWRITE_PROMPT);
|
|
}
|
|
|
|
static void window_loadsave_resize(rct_window *w)
|
|
{
|
|
if (w->width < w->min_width)
|
|
{
|
|
window_invalidate(w);
|
|
w->width = w->min_width;
|
|
}
|
|
if (w->height < w->min_height)
|
|
{
|
|
window_invalidate(w);
|
|
w->height = w->min_height;
|
|
}
|
|
}
|
|
|
|
static bool browse(bool isSave, char *path, size_t pathSize)
|
|
{
|
|
safe_strcpy(path, _directory, pathSize);
|
|
if (isSave)
|
|
safe_strcat_path(path, _defaultName, pathSize);
|
|
|
|
file_dialog_desc desc = { 0 };
|
|
desc.initial_directory = _directory;
|
|
desc.type = isSave ? FD_SAVE : FD_OPEN;
|
|
desc.default_filename = isSave ? path : nullptr;
|
|
|
|
rct_string_id title = STR_NONE;
|
|
switch (_type & 0x0E)
|
|
{
|
|
case LOADSAVETYPE_GAME:
|
|
title = isSave ? STR_FILE_DIALOG_TITLE_SAVE_GAME : STR_FILE_DIALOG_TITLE_LOAD_GAME;
|
|
desc.filters[0].name = language_get_string(STR_OPENRCT2_SAVED_GAME);
|
|
desc.filters[0].pattern = isSave ? "*.sv6" : "*.sv6;*.sc6;*.sv4;*.sc4";
|
|
break;
|
|
|
|
case LOADSAVETYPE_LANDSCAPE:
|
|
title = isSave ? STR_FILE_DIALOG_TITLE_SAVE_LANDSCAPE : STR_FILE_DIALOG_TITLE_LOAD_LANDSCAPE;
|
|
desc.filters[0].name = language_get_string(STR_OPENRCT2_LANDSCAPE_FILE);
|
|
desc.filters[0].pattern = isSave ? "*.sc6" : "*.sc6;*.sv6;*.sc4;*.sv4";
|
|
break;
|
|
|
|
case LOADSAVETYPE_SCENARIO:
|
|
title = STR_FILE_DIALOG_TITLE_SAVE_SCENARIO;
|
|
desc.filters[0].name = language_get_string(STR_OPENRCT2_SCENARIO_FILE);
|
|
desc.filters[0].pattern = "*.sc6";
|
|
break;
|
|
|
|
case LOADSAVETYPE_TRACK:
|
|
title = isSave ? STR_FILE_DIALOG_TITLE_SAVE_TRACK : STR_FILE_DIALOG_TITLE_INSTALL_NEW_TRACK_DESIGN;
|
|
desc.filters[0].name = language_get_string(STR_OPENRCT2_TRACK_DESIGN_FILE);
|
|
desc.filters[0].pattern = isSave ? "*.td6" : "*.td6;*.td4";
|
|
break;
|
|
|
|
case LOADSAVETYPE_IMAGE:
|
|
title = STR_FILE_DIALOG_TITLE_LOAD_HEIGHTMAP;
|
|
desc.filters[0].name = language_get_string(STR_OPENRCT2_HEIGHTMAP_FILE);
|
|
desc.filters[0].pattern = "*.jpg;*.png;*.bmp";
|
|
break;
|
|
}
|
|
|
|
// Add 'all files' filter. If the number of filters is increased, this code will need to be adjusted.
|
|
desc.filters[1].name = language_get_string(STR_ALL_FILES);
|
|
desc.filters[1].pattern = "*";
|
|
|
|
desc.title = language_get_string(title);
|
|
return platform_open_common_file_dialog(path, &desc, pathSize);
|
|
}
|
|
|
|
static void window_loadsave_mouseup(rct_window *w, rct_widgetindex widgetIndex)
|
|
{
|
|
char path[MAX_PATH];
|
|
|
|
bool isSave = (_type & 0x01) == LOADSAVETYPE_SAVE;
|
|
switch (widgetIndex)
|
|
{
|
|
case WIDX_CLOSE:
|
|
window_close(w);
|
|
break;
|
|
|
|
case WIDX_UP:
|
|
safe_strcpy(path, _parentDirectory, sizeof(path));
|
|
window_loadsave_populate_list(w, isSave, path, _extension);
|
|
window_init_scroll_widgets(w);
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
break;
|
|
|
|
case WIDX_NEW_FILE:
|
|
window_text_input_open(w, WIDX_NEW_FILE, STR_NONE, STR_FILEBROWSER_FILE_NAME_PROMPT, STR_STRING, (uintptr_t)&_defaultName, 64);
|
|
break;
|
|
|
|
case WIDX_NEW_FOLDER:
|
|
window_text_input_raw_open(w, WIDX_NEW_FOLDER, STR_NONE, STR_FILEBROWSER_FOLDER_NAME_PROMPT, nullptr, 64);
|
|
break;
|
|
|
|
case WIDX_BROWSE:
|
|
if (browse(isSave, path, sizeof(path)))
|
|
{
|
|
window_loadsave_select(w, path);
|
|
}
|
|
else
|
|
{
|
|
// If user cancels file dialog, refresh list
|
|
safe_strcpy(path, _directory, sizeof(path));
|
|
window_loadsave_populate_list(w, isSave, path, _extension);
|
|
window_init_scroll_widgets(w);
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
}
|
|
break;
|
|
|
|
case WIDX_SORT_NAME:
|
|
if (gConfigGeneral.load_save_sort == SORT_NAME_ASCENDING)
|
|
{
|
|
gConfigGeneral.load_save_sort = SORT_NAME_DESCENDING;
|
|
}
|
|
else
|
|
{
|
|
gConfigGeneral.load_save_sort = SORT_NAME_ASCENDING;
|
|
}
|
|
config_save_default();
|
|
window_loadsave_sort_list();
|
|
window_invalidate(w);
|
|
break;
|
|
|
|
case WIDX_SORT_DATE:
|
|
if (gConfigGeneral.load_save_sort == SORT_DATE_DESCENDING)
|
|
{
|
|
gConfigGeneral.load_save_sort = SORT_DATE_ASCENDING;
|
|
}
|
|
else
|
|
{
|
|
gConfigGeneral.load_save_sort = SORT_DATE_DESCENDING;
|
|
}
|
|
config_save_default();
|
|
window_loadsave_sort_list();
|
|
window_invalidate(w);
|
|
break;
|
|
|
|
case WIDX_DEFAULT:
|
|
switch (_type & 0x0E)
|
|
{
|
|
case LOADSAVETYPE_GAME:
|
|
platform_get_user_directory(path, "save", sizeof(path));
|
|
break;
|
|
case LOADSAVETYPE_LANDSCAPE:
|
|
platform_get_user_directory(path, "landscape", sizeof(path));
|
|
break;
|
|
case LOADSAVETYPE_SCENARIO:
|
|
platform_get_user_directory(path, "scenario", sizeof(path));
|
|
break;
|
|
case LOADSAVETYPE_TRACK:
|
|
platform_get_user_directory(path, "track", sizeof(path));
|
|
break;
|
|
}
|
|
|
|
window_loadsave_populate_list(w, isSave, path, _extension);
|
|
window_init_scroll_widgets(w);
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void window_loadsave_scrollgetsize(rct_window *w, sint32 scrollIndex, sint32 *width, sint32 *height)
|
|
{
|
|
*height = w->no_list_items * SCROLLABLE_ROW_HEIGHT;
|
|
}
|
|
|
|
static void window_loadsave_scrollmousedown(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y)
|
|
{
|
|
sint32 selectedItem;
|
|
|
|
selectedItem = y / SCROLLABLE_ROW_HEIGHT;
|
|
if (selectedItem >= w->no_list_items)
|
|
return;
|
|
|
|
if (_listItems[selectedItem].type == TYPE_DIRECTORY)
|
|
{
|
|
// The selected item is a folder
|
|
sint32 includeNewItem;
|
|
|
|
w->no_list_items = 0;
|
|
w->selected_list_item = -1;
|
|
includeNewItem = (_type & 1) == LOADSAVETYPE_SAVE;
|
|
|
|
char directory[MAX_PATH];
|
|
safe_strcpy(directory, _listItems[selectedItem].path.c_str(), sizeof(directory));
|
|
|
|
window_loadsave_populate_list(w, includeNewItem, directory, _extension);
|
|
window_init_scroll_widgets(w);
|
|
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
}
|
|
else
|
|
{
|
|
// TYPE_FILE
|
|
// Load or overwrite
|
|
if ((_type & 0x01) == LOADSAVETYPE_SAVE)
|
|
window_overwrite_prompt_open(_listItems[selectedItem].name.c_str(), _listItems[selectedItem].path.c_str());
|
|
else
|
|
window_loadsave_select(w, _listItems[selectedItem].path.c_str());
|
|
}
|
|
}
|
|
|
|
static void window_loadsave_scrollmouseover(rct_window *w, sint32 scrollIndex, sint32 x, sint32 y)
|
|
{
|
|
sint32 selectedItem;
|
|
|
|
selectedItem = y / SCROLLABLE_ROW_HEIGHT;
|
|
if (selectedItem >= w->no_list_items)
|
|
return;
|
|
|
|
w->selected_list_item = selectedItem;
|
|
|
|
window_invalidate(w);
|
|
}
|
|
|
|
static void window_loadsave_textinput(rct_window *w, rct_widgetindex widgetIndex, char *text)
|
|
{
|
|
char path[MAX_PATH];
|
|
bool overwrite;
|
|
|
|
if (text == nullptr || text[0] == 0)
|
|
return;
|
|
|
|
switch (widgetIndex)
|
|
{
|
|
case WIDX_NEW_FOLDER:
|
|
if (!filename_valid_characters(text))
|
|
{
|
|
context_show_error(STR_ERROR_INVALID_CHARACTERS, STR_NONE);
|
|
return;
|
|
}
|
|
|
|
safe_strcpy(path, _directory, sizeof(path));
|
|
safe_strcat_path(path, text, sizeof(path));
|
|
|
|
if (!platform_ensure_directory_exists(path))
|
|
{
|
|
context_show_error(STR_UNABLE_TO_CREATE_FOLDER, STR_NONE);
|
|
return;
|
|
}
|
|
|
|
w->no_list_items = 0;
|
|
w->selected_list_item = -1;
|
|
|
|
window_loadsave_populate_list(w, (_type & 1) == LOADSAVETYPE_SAVE, path, _extension);
|
|
window_init_scroll_widgets(w);
|
|
|
|
w->no_list_items = static_cast<uint16>(_listItems.size());
|
|
window_invalidate(w);
|
|
break;
|
|
|
|
case WIDX_NEW_FILE:
|
|
safe_strcpy(path, _directory, sizeof(path));
|
|
safe_strcat_path(path, text, sizeof(path));
|
|
path_append_extension(path, _extension, sizeof(path));
|
|
|
|
overwrite = false;
|
|
for (auto &item : _listItems)
|
|
{
|
|
if (_stricmp(item.path.c_str(), path) == 0)
|
|
{
|
|
overwrite = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (overwrite)
|
|
window_overwrite_prompt_open(text, path);
|
|
else
|
|
window_loadsave_select(w, path);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void window_loadsave_tooltip(rct_window* w, rct_widgetindex widgetIndex, rct_string_id *stringId)
|
|
{
|
|
set_format_arg(0, rct_string_id, STR_LIST);
|
|
}
|
|
|
|
static void window_loadsave_compute_max_date_width()
|
|
{
|
|
// Generate a time object for a relatively wide time: 2000-10-20 00:00:00
|
|
std::tm tm;
|
|
tm.tm_sec = 0;
|
|
tm.tm_min = 0;
|
|
tm.tm_hour = 0;
|
|
tm.tm_mday = 20;
|
|
tm.tm_mon = 9;
|
|
tm.tm_year = 100;
|
|
tm.tm_wday = 5;
|
|
tm.tm_yday = 294;
|
|
tm.tm_isdst = -1;
|
|
|
|
std::time_t long_time = mktime(&tm);
|
|
|
|
std::string date = Platform::FormatShortDate(long_time);
|
|
maxDateWidth = gfx_get_string_width(date.c_str());
|
|
|
|
std::string time = Platform::FormatTime(long_time);
|
|
maxTimeWidth = gfx_get_string_width(time.c_str());
|
|
}
|
|
|
|
static void window_loadsave_invalidate(rct_window *w)
|
|
{
|
|
window_loadsave_widgets[WIDX_TITLE].right = w->width - 2;
|
|
window_loadsave_widgets[WIDX_CLOSE].left = w->width - 13;
|
|
window_loadsave_widgets[WIDX_CLOSE].right = w->width - 3;
|
|
window_loadsave_widgets[WIDX_BACKGROUND].right = w->width - 1;
|
|
window_loadsave_widgets[WIDX_BACKGROUND].bottom = w->height - 1;
|
|
window_loadsave_widgets[WIDX_RESIZE].top = w->height - 1;
|
|
window_loadsave_widgets[WIDX_RESIZE].right = w->width - 1;
|
|
window_loadsave_widgets[WIDX_RESIZE].bottom = w->height - 1;
|
|
|
|
rct_widget * date_widget = &window_loadsave_widgets[WIDX_SORT_DATE];
|
|
date_widget->left = w->width - maxDateWidth - maxTimeWidth - 24;
|
|
date_widget->right = w->width - 5;
|
|
|
|
window_loadsave_widgets[WIDX_SORT_NAME].left = 4;
|
|
window_loadsave_widgets[WIDX_SORT_NAME].right = window_loadsave_widgets[WIDX_SORT_DATE].left - 1;
|
|
|
|
window_loadsave_widgets[WIDX_SCROLL].right = w->width - 4;
|
|
window_loadsave_widgets[WIDX_SCROLL].bottom = w->height - 30;
|
|
|
|
window_loadsave_widgets[WIDX_BROWSE].top = w->height - 24;
|
|
window_loadsave_widgets[WIDX_BROWSE].bottom = w->height - 6;
|
|
}
|
|
|
|
static void window_loadsave_paint(rct_window *w, rct_drawpixelinfo *dpi)
|
|
{
|
|
window_draw_widgets(w, dpi);
|
|
|
|
if (_shortenedDirectory[0] == '\0')
|
|
{
|
|
shorten_path(_shortenedDirectory, sizeof(_shortenedDirectory), _directory, w->width - 8);
|
|
}
|
|
|
|
utf8 buffer[256];
|
|
|
|
// Format text
|
|
utf8 *ch = buffer;
|
|
ch = utf8_write_codepoint(ch, FORMAT_MEDIUMFONT);
|
|
ch = utf8_write_codepoint(ch, FORMAT_BLACK);
|
|
safe_strcpy(ch, _shortenedDirectory, sizeof(buffer) - (ch - buffer));
|
|
|
|
// Draw path text
|
|
#ifdef __APPLE__
|
|
set_format_arg(0, uintptr_t, (uintptr_t) macos_str_decomp_to_precomp(buffer));
|
|
#else
|
|
set_format_arg(0, uintptr_t, (uintptr_t) buffer);
|
|
#endif
|
|
gfx_draw_string_left_clipped(dpi, STR_STRING, gCommonFormatArgs, COLOUR_BLACK, w->x + 4, w->y + 20, w->width - 8);
|
|
|
|
// Name button text
|
|
rct_string_id id = STR_NONE;
|
|
if (gConfigGeneral.load_save_sort == SORT_NAME_ASCENDING)
|
|
id = STR_UP;
|
|
else if (gConfigGeneral.load_save_sort == SORT_NAME_DESCENDING)
|
|
id = STR_DOWN;
|
|
|
|
// Draw name button indicator.
|
|
rct_widget sort_name_widget = window_loadsave_widgets[WIDX_SORT_NAME];
|
|
gfx_draw_string_left(dpi, STR_NAME, &id, COLOUR_GREY, w->x + sort_name_widget.left + 11,
|
|
w->y + sort_name_widget.top + 1);
|
|
|
|
// Date button text
|
|
if (gConfigGeneral.load_save_sort == SORT_DATE_ASCENDING)
|
|
id = STR_UP;
|
|
else if (gConfigGeneral.load_save_sort == SORT_DATE_DESCENDING)
|
|
id = STR_DOWN;
|
|
else
|
|
id = STR_NONE;
|
|
|
|
rct_widget sort_date_widget = window_loadsave_widgets[WIDX_SORT_DATE];
|
|
gfx_draw_string_left(dpi, STR_DATE, &id, COLOUR_GREY, w->x + sort_date_widget.left + 5,
|
|
w->y + sort_date_widget.top + 1);
|
|
}
|
|
|
|
static void window_loadsave_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, sint32 scrollIndex)
|
|
{
|
|
gfx_fill_rect(dpi, dpi->x, dpi->y, dpi->x + dpi->width - 1, dpi->y + dpi->height - 1, ColourMapA[w->colours[1]].mid_light);
|
|
const sint32 listWidth = w->widgets[WIDX_SCROLL].right - w->widgets[WIDX_SCROLL].left;
|
|
|
|
for (sint32 i = 0; i < w->no_list_items; i++)
|
|
{
|
|
sint32 y = i * SCROLLABLE_ROW_HEIGHT;
|
|
if (y > dpi->y + dpi->height)
|
|
break;
|
|
|
|
if (y + SCROLLABLE_ROW_HEIGHT < dpi->y)
|
|
continue;
|
|
|
|
rct_string_id stringId = STR_BLACK_STRING;
|
|
|
|
// If hovering over item, change the color and fill the backdrop.
|
|
if (i == w->selected_list_item)
|
|
{
|
|
stringId = STR_WINDOW_COLOUR_2_STRINGID;
|
|
gfx_filter_rect(dpi, 0, y, listWidth, y + SCROLLABLE_ROW_HEIGHT, PALETTE_DARKEN_1);
|
|
}
|
|
// display a marker next to the currently loaded game file
|
|
if (_listItems[i].loaded)
|
|
{
|
|
set_format_arg(0, rct_string_id, STR_RIGHTGUILLEMET);
|
|
gfx_draw_string_left(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, 0, y);
|
|
}
|
|
|
|
// Print filename
|
|
set_format_arg(0, rct_string_id, STR_STRING);
|
|
set_format_arg(2, char*, _listItems[i].name.c_str());
|
|
sint32 max_file_width = w->widgets[WIDX_SORT_NAME].right - w->widgets[WIDX_SORT_NAME].left - 10;
|
|
gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, 10, y, max_file_width);
|
|
|
|
// Print formatted modified date, if this is a file
|
|
if (_listItems[i].type == TYPE_FILE)
|
|
{
|
|
sint32 offset = w->widgets[WIDX_SORT_DATE].left + maxDateWidth;
|
|
|
|
set_format_arg(0, rct_string_id, STR_STRING);
|
|
set_format_arg(2, char*, _listItems[i].date_formatted.c_str());
|
|
gfx_draw_string_right_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, offset - 2, y, maxDateWidth);
|
|
|
|
set_format_arg(2, char*, _listItems[i].time_formatted.c_str());
|
|
gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, offset + 2, y, maxTimeWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool list_item_sort(LoadSaveListItem &a, LoadSaveListItem &b)
|
|
{
|
|
if (a.type != b.type)
|
|
return a.type - b.type < 0;
|
|
|
|
switch (gConfigGeneral.load_save_sort)
|
|
{
|
|
case SORT_NAME_ASCENDING:
|
|
return strcicmp(a.name.c_str(), b.name.c_str()) < 0;
|
|
case SORT_NAME_DESCENDING:
|
|
return -strcicmp(a.name.c_str(), b.name.c_str()) < 0;
|
|
case SORT_DATE_DESCENDING:
|
|
return -difftime(a.date_modified, b.date_modified) < 0;
|
|
case SORT_DATE_ASCENDING:
|
|
return difftime(a.date_modified, b.date_modified) < 0;
|
|
default:
|
|
return strcicmp(a.name.c_str(), b.name.c_str()) < 0;
|
|
}
|
|
}
|
|
|
|
static void window_loadsave_sort_list()
|
|
{
|
|
std::sort(_listItems.begin(), _listItems.end(), list_item_sort);
|
|
}
|
|
|
|
static void window_loadsave_populate_list(rct_window *w, sint32 includeNewItem, const char *directory, const char *extension)
|
|
{
|
|
utf8 absoluteDirectory[MAX_PATH];
|
|
Path::GetAbsolute(absoluteDirectory, Util::CountOf(absoluteDirectory), directory);
|
|
safe_strcpy(_directory, absoluteDirectory, Util::CountOf(_directory));
|
|
if (_extension != extension)
|
|
{
|
|
safe_strcpy(_extension, extension, Util::CountOf(_extension));
|
|
}
|
|
_shortenedDirectory[0] = '\0';
|
|
|
|
_listItems.clear();
|
|
|
|
// Show "new" buttons when saving
|
|
window_loadsave_widgets[WIDX_NEW_FILE].type = includeNewItem ? WWT_BUTTON : WWT_EMPTY;
|
|
window_loadsave_widgets[WIDX_NEW_FOLDER].type = includeNewItem ? WWT_BUTTON : WWT_EMPTY;
|
|
|
|
sint32 drives = platform_get_drives();
|
|
if (str_is_null_or_empty(directory) && drives)
|
|
{
|
|
// List Windows drives
|
|
w->disabled_widgets |= (1 << WIDX_NEW_FILE) | (1 << WIDX_NEW_FOLDER) | (1 << WIDX_UP);
|
|
for (sint32 x = 0; x < 26; x++)
|
|
{
|
|
if (drives & (1 << x))
|
|
{
|
|
// If the drive exists, list it
|
|
LoadSaveListItem newListItem;
|
|
newListItem.path = std::string(1, 'A' + x) + ":" PATH_SEPARATOR;
|
|
newListItem.name = newListItem.path;
|
|
newListItem.type = TYPE_DIRECTORY;
|
|
|
|
_listItems.push_back(newListItem);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove the separator at the end of the path, if present
|
|
safe_strcpy(_parentDirectory, absoluteDirectory, Util::CountOf(_parentDirectory));
|
|
if (_parentDirectory[strlen(_parentDirectory) - 1] == *PATH_SEPARATOR
|
|
|| _parentDirectory[strlen(_parentDirectory) - 1] == '/')
|
|
_parentDirectory[strlen(_parentDirectory) - 1] = '\0';
|
|
|
|
// Remove everything past the now last separator
|
|
char *ch = strrchr(_parentDirectory, *PATH_SEPARATOR);
|
|
char *posix_ch = strrchr(_parentDirectory, '/');
|
|
ch = ch < posix_ch ? posix_ch : ch;
|
|
if (ch != nullptr)
|
|
{
|
|
*(ch + 1) = '\0';
|
|
}
|
|
else if (drives)
|
|
{
|
|
// If on Windows, clear the entire path to show the drives
|
|
_parentDirectory[0] = '\0';
|
|
}
|
|
else
|
|
{
|
|
// Else, go to the root directory
|
|
snprintf(_parentDirectory, MAX_PATH, "%c", *PATH_SEPARATOR);
|
|
}
|
|
|
|
// Disable the Up button if the current directory is the root directory
|
|
if (str_is_null_or_empty(_parentDirectory) && !drives)
|
|
w->disabled_widgets |= (1 << WIDX_UP);
|
|
else
|
|
w->disabled_widgets &= ~(1 << WIDX_UP);
|
|
|
|
// Re-enable the "new" buttons if these were disabled
|
|
w->disabled_widgets &= ~(1 << WIDX_NEW_FILE);
|
|
w->disabled_widgets &= ~(1 << WIDX_NEW_FOLDER);
|
|
|
|
// List all directories
|
|
auto subDirectories = Path::GetDirectories(absoluteDirectory);
|
|
for (const auto &sdName : subDirectories)
|
|
{
|
|
auto subDir = sdName + PATH_SEPARATOR;
|
|
|
|
LoadSaveListItem newListItem;
|
|
newListItem.path = Path::Combine(absoluteDirectory, subDir);
|
|
newListItem.name = subDir;
|
|
newListItem.type = TYPE_DIRECTORY;
|
|
newListItem.loaded = false;
|
|
|
|
_listItems.push_back(newListItem);
|
|
}
|
|
|
|
// List all files with the wanted extensions
|
|
char filter[MAX_PATH];
|
|
char extCopy[64];
|
|
safe_strcpy(extCopy, extension, Util::CountOf(extCopy));
|
|
char * extToken;
|
|
bool showExtension = false;
|
|
extToken = strtok(extCopy, ";");
|
|
while (extToken != nullptr)
|
|
{
|
|
safe_strcpy(filter, directory, Util::CountOf(filter));
|
|
safe_strcat_path(filter, "*", Util::CountOf(filter));
|
|
path_append_extension(filter, extToken, Util::CountOf(filter));
|
|
|
|
auto scanner = std::unique_ptr<IFileScanner>(Path::ScanDirectory(filter, false));
|
|
while (scanner->Next())
|
|
{
|
|
LoadSaveListItem newListItem;
|
|
newListItem.path = scanner->GetPath();
|
|
newListItem.type = TYPE_FILE;
|
|
newListItem.date_modified = platform_file_get_modified_time(newListItem.path.c_str());
|
|
|
|
// Cache a human-readable version of the modified date.
|
|
newListItem.date_formatted = Platform::FormatShortDate(newListItem.date_modified);
|
|
newListItem.time_formatted = Platform::FormatTime(newListItem.date_modified);
|
|
|
|
// Mark if file is the currently loaded game
|
|
newListItem.loaded = newListItem.path.compare(gCurrentLoadedPath) == 0;
|
|
|
|
// Remove the extension (but only the first extension token)
|
|
if (!showExtension)
|
|
{
|
|
newListItem.name = Path::GetFileNameWithoutExtension(newListItem.path);
|
|
}
|
|
else
|
|
{
|
|
newListItem.name = Path::GetFileName(newListItem.path);
|
|
}
|
|
|
|
_listItems.push_back(newListItem);
|
|
}
|
|
|
|
extToken = strtok(nullptr, ";");
|
|
showExtension = true; //Show any extension after the first iteration
|
|
}
|
|
|
|
window_loadsave_sort_list();
|
|
}
|
|
}
|
|
|
|
static void window_loadsave_invoke_callback(sint32 result, const utf8 * path)
|
|
{
|
|
if (_loadSaveCallback != nullptr)
|
|
{
|
|
_loadSaveCallback(result, path);
|
|
}
|
|
}
|
|
|
|
static void save_path(utf8 **config_str, const char *path)
|
|
{
|
|
free(*config_str);
|
|
*config_str = path_get_directory(path);
|
|
config_save_default();
|
|
}
|
|
|
|
static bool is_valid_path(const char * path)
|
|
{
|
|
char filename[MAX_PATH];
|
|
safe_strcpy(filename, path_get_filename(path), sizeof(filename));
|
|
|
|
// HACK This is needed because tracks get passed through with td?
|
|
// I am sure this will change eventually to use the new FileScanner
|
|
// which handles multiple patterns
|
|
path_remove_extension(filename);
|
|
|
|
return filename_valid_characters(filename);
|
|
}
|
|
|
|
static void window_loadsave_select(rct_window *w, const char *path)
|
|
{
|
|
if (!is_valid_path(path))
|
|
{
|
|
context_show_error(STR_ERROR_INVALID_CHARACTERS, STR_NONE);
|
|
return;
|
|
}
|
|
|
|
char pathBuffer[MAX_PATH];
|
|
safe_strcpy(pathBuffer, path, sizeof(pathBuffer));
|
|
|
|
switch (_type & 0x0F) {
|
|
case (LOADSAVETYPE_LOAD | LOADSAVETYPE_GAME):
|
|
save_path(&gConfigGeneral.last_save_game_directory, pathBuffer);
|
|
safe_strcpy(gScenarioSavePath, pathBuffer, MAX_PATH);
|
|
safe_strcpy(gCurrentLoadedPath, pathBuffer, MAX_PATH);
|
|
gFirstTimeSaving = true;
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
window_close_by_class(WC_LOADSAVE);
|
|
gfx_invalidate_screen();
|
|
break;
|
|
|
|
case (LOADSAVETYPE_SAVE | LOADSAVETYPE_GAME):
|
|
save_path(&gConfigGeneral.last_save_game_directory, pathBuffer);
|
|
if (scenario_save(pathBuffer, gConfigGeneral.save_plugin_data ? 1 : 0))
|
|
{
|
|
safe_strcpy(gScenarioSavePath, pathBuffer, MAX_PATH);
|
|
safe_strcpy(gCurrentLoadedPath, pathBuffer, MAX_PATH);
|
|
gFirstTimeSaving = false;
|
|
|
|
window_close_by_class(WC_LOADSAVE);
|
|
gfx_invalidate_screen();
|
|
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
}
|
|
else
|
|
{
|
|
context_show_error(STR_SAVE_GAME, STR_GAME_SAVE_FAILED);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_FAIL, pathBuffer);
|
|
}
|
|
break;
|
|
|
|
case (LOADSAVETYPE_LOAD | LOADSAVETYPE_LANDSCAPE):
|
|
save_path(&gConfigGeneral.last_save_landscape_directory, pathBuffer);
|
|
if (Editor::LoadLandscape(pathBuffer))
|
|
{
|
|
safe_strcpy(gCurrentLoadedPath, pathBuffer, MAX_PATH);
|
|
gfx_invalidate_screen();
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
}
|
|
else
|
|
{
|
|
// Not the best message...
|
|
context_show_error(STR_LOAD_LANDSCAPE, STR_FAILED_TO_LOAD_FILE_CONTAINS_INVALID_DATA);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_FAIL, pathBuffer);
|
|
}
|
|
break;
|
|
|
|
case (LOADSAVETYPE_SAVE | LOADSAVETYPE_LANDSCAPE):
|
|
save_path(&gConfigGeneral.last_save_landscape_directory, pathBuffer);
|
|
safe_strcpy(gScenarioFileName, pathBuffer, sizeof(gScenarioFileName));
|
|
if (scenario_save(pathBuffer, gConfigGeneral.save_plugin_data ? 3 : 2))
|
|
{
|
|
safe_strcpy(gCurrentLoadedPath, pathBuffer, MAX_PATH);
|
|
window_close_by_class(WC_LOADSAVE);
|
|
gfx_invalidate_screen();
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
}
|
|
else
|
|
{
|
|
context_show_error(STR_SAVE_LANDSCAPE, STR_LANDSCAPE_SAVE_FAILED);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_FAIL, pathBuffer);
|
|
}
|
|
break;
|
|
|
|
case (LOADSAVETYPE_SAVE | LOADSAVETYPE_SCENARIO):
|
|
{
|
|
save_path(&gConfigGeneral.last_save_scenario_directory, pathBuffer);
|
|
sint32 parkFlagsBackup = gParkFlags;
|
|
gParkFlags &= ~PARK_FLAGS_SPRITES_INITIALISED;
|
|
gS6Info.editor_step = 255;
|
|
safe_strcpy(gScenarioFileName, pathBuffer, sizeof(gScenarioFileName));
|
|
sint32 success = scenario_save(pathBuffer, gConfigGeneral.save_plugin_data ? 3 : 2);
|
|
gParkFlags = parkFlagsBackup;
|
|
|
|
if (success)
|
|
{
|
|
window_close_by_class(WC_LOADSAVE);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
title_load();
|
|
}
|
|
else
|
|
{
|
|
context_show_error(STR_FILE_DIALOG_TITLE_SAVE_SCENARIO, STR_SCENARIO_SAVE_FAILED);
|
|
gS6Info.editor_step = EDITOR_STEP_OBJECTIVE_SELECTION;
|
|
window_loadsave_invoke_callback(MODAL_RESULT_FAIL, pathBuffer);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case (LOADSAVETYPE_LOAD | LOADSAVETYPE_TRACK):
|
|
{
|
|
save_path(&gConfigGeneral.last_save_track_directory, pathBuffer);
|
|
auto intent = Intent(WC_INSTALL_TRACK);
|
|
intent.putExtra(INTENT_EXTRA_PATH, std::string { pathBuffer });
|
|
context_open_intent(&intent);
|
|
window_close_by_class(WC_LOADSAVE);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
break;
|
|
}
|
|
|
|
case (LOADSAVETYPE_SAVE | LOADSAVETYPE_TRACK):
|
|
{
|
|
path_set_extension(pathBuffer, "td6", sizeof(pathBuffer));
|
|
sint32 success = track_design_save_to_file(pathBuffer);
|
|
|
|
if (success)
|
|
{
|
|
window_close_by_class(WC_LOADSAVE);
|
|
window_ride_measurements_design_cancel();
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, path);
|
|
}
|
|
else
|
|
{
|
|
context_show_error(STR_FILE_DIALOG_TITLE_SAVE_TRACK, STR_TRACK_SAVE_FAILED);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_FAIL, path);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case (LOADSAVETYPE_LOAD | LOADSAVETYPE_IMAGE):
|
|
window_close_by_class(WC_LOADSAVE);
|
|
window_loadsave_invoke_callback(MODAL_RESULT_OK, pathBuffer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#pragma region Overwrite prompt
|
|
|
|
#define OVERWRITE_WW 200
|
|
#define OVERWRITE_WH 100
|
|
|
|
enum {
|
|
WIDX_OVERWRITE_BACKGROUND,
|
|
WIDX_OVERWRITE_TITLE,
|
|
WIDX_OVERWRITE_CLOSE,
|
|
WIDX_OVERWRITE_OVERWRITE,
|
|
WIDX_OVERWRITE_CANCEL
|
|
};
|
|
|
|
static rct_widget window_overwrite_prompt_widgets[] =
|
|
{
|
|
{ WWT_FRAME, 0, 0, OVERWRITE_WW - 1, 0, OVERWRITE_WH - 1, STR_NONE, STR_NONE },
|
|
{ WWT_CAPTION, 0, 1, OVERWRITE_WW - 2, 1, 14, STR_FILEBROWSER_OVERWRITE_TITLE, STR_WINDOW_TITLE_TIP },
|
|
{ WWT_CLOSEBOX, 0, OVERWRITE_WW - 13, OVERWRITE_WW - 3, 2, 13, STR_CLOSE_X, STR_CLOSE_WINDOW_TIP },
|
|
{ WWT_BUTTON, 0, 10, 94, OVERWRITE_WH - 20, OVERWRITE_WH - 9, STR_FILEBROWSER_OVERWRITE_TITLE, STR_NONE },
|
|
{ WWT_BUTTON, 0, OVERWRITE_WW - 95, OVERWRITE_WW - 11, OVERWRITE_WH - 20, OVERWRITE_WH - 9, STR_SAVE_PROMPT_CANCEL, STR_NONE },
|
|
{ WIDGETS_END }
|
|
};
|
|
|
|
static void window_overwrite_prompt_mouseup(rct_window *w, rct_widgetindex widgetIndex);
|
|
static void window_overwrite_prompt_paint(rct_window *w, rct_drawpixelinfo *dpi);
|
|
|
|
static rct_window_event_list window_overwrite_prompt_events =
|
|
{
|
|
nullptr,
|
|
window_overwrite_prompt_mouseup,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
window_overwrite_prompt_paint,
|
|
nullptr
|
|
};
|
|
|
|
static char _window_overwrite_prompt_name[256];
|
|
static char _window_overwrite_prompt_path[MAX_PATH];
|
|
|
|
static rct_window *window_overwrite_prompt_open(const char *name, const char *path)
|
|
{
|
|
rct_window *w;
|
|
|
|
window_close_by_class(WC_LOADSAVE_OVERWRITE_PROMPT);
|
|
|
|
w = window_create_centred(OVERWRITE_WW, OVERWRITE_WH, &window_overwrite_prompt_events, WC_LOADSAVE_OVERWRITE_PROMPT, WF_STICK_TO_FRONT);
|
|
w->widgets = window_overwrite_prompt_widgets;
|
|
w->enabled_widgets =
|
|
(1 << WIDX_CLOSE) |
|
|
(1 << WIDX_OVERWRITE_CANCEL) |
|
|
(1 << WIDX_OVERWRITE_OVERWRITE);
|
|
|
|
window_init_scroll_widgets(w);
|
|
|
|
w->flags |= WF_TRANSPARENT;
|
|
w->colours[0] = TRANSLUCENT(COLOUR_BORDEAUX_RED);
|
|
|
|
safe_strcpy(_window_overwrite_prompt_name, name, sizeof(_window_overwrite_prompt_name));
|
|
safe_strcpy(_window_overwrite_prompt_path, path, sizeof(_window_overwrite_prompt_path));
|
|
|
|
return w;
|
|
}
|
|
|
|
static void window_overwrite_prompt_mouseup(rct_window *w, rct_widgetindex widgetIndex)
|
|
{
|
|
rct_window *loadsaveWindow;
|
|
|
|
switch (widgetIndex)
|
|
{
|
|
case WIDX_OVERWRITE_OVERWRITE:
|
|
loadsaveWindow = window_find_by_class(WC_LOADSAVE);
|
|
if (loadsaveWindow != nullptr)
|
|
window_loadsave_select(loadsaveWindow, _window_overwrite_prompt_path);
|
|
// As the window_loadsave_select function can change the order of the
|
|
// windows we can't use window_close(w).
|
|
window_close_by_class(WC_LOADSAVE_OVERWRITE_PROMPT);
|
|
break;
|
|
|
|
case WIDX_OVERWRITE_CANCEL:
|
|
case WIDX_OVERWRITE_CLOSE:
|
|
window_close(w);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void window_overwrite_prompt_paint(rct_window *w, rct_drawpixelinfo *dpi)
|
|
{
|
|
window_draw_widgets(w, dpi);
|
|
|
|
set_format_arg(0, rct_string_id, STR_STRING);
|
|
set_format_arg(2, char *, _window_overwrite_prompt_name);
|
|
|
|
sint32 x = w->x + w->width / 2;
|
|
sint32 y = w->y + (w->height / 2) - 3;
|
|
gfx_draw_string_centred_wrapped(dpi, gCommonFormatArgs, x, y, w->width - 4, STR_FILEBROWSER_OVERWRITE_PROMPT, COLOUR_BLACK);
|
|
}
|
|
|
|
#pragma endregion
|