Add: INR currency (#8136)
[openttd-github.git] / src / fios_gui.cpp
blob9f8b45413f5f3fad1f9121a27126fe514b0f9cdc
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file fios_gui.cpp GUIs for loading/saving games, scenarios, heightmaps, ... */
10 #include "stdafx.h"
11 #include "saveload/saveload.h"
12 #include "error.h"
13 #include "gui.h"
14 #include "gfx_func.h"
15 #include "command_func.h"
16 #include "network/network.h"
17 #include "network/network_content.h"
18 #include "strings_func.h"
19 #include "fileio_func.h"
20 #include "fios.h"
21 #include "window_func.h"
22 #include "tilehighlight_func.h"
23 #include "querystring_gui.h"
24 #include "engine_func.h"
25 #include "landscape_type.h"
26 #include "date_func.h"
27 #include "core/geometry_func.hpp"
28 #include "gamelog.h"
29 #include "stringfilter_type.h"
31 #include "widgets/fios_widget.h"
33 #include "table/sprites.h"
34 #include "table/strings.h"
36 #include "safeguards.h"
38 LoadCheckData _load_check_data; ///< Data loaded from save during SL_LOAD_CHECK.
40 static bool _fios_path_changed;
41 static bool _savegame_sort_dirty;
44 /**
45 * Reset read data.
47 void LoadCheckData::Clear()
49 this->checkable = false;
50 this->error = INVALID_STRING_ID;
51 free(this->error_data);
52 this->error_data = nullptr;
54 this->map_size_x = this->map_size_y = 256; // Default for old savegames which do not store mapsize.
55 this->current_date = 0;
56 memset(&this->settings, 0, sizeof(this->settings));
58 for (auto &pair : this->companies) {
59 delete pair.second;
61 companies.clear();
63 GamelogFree(this->gamelog_action, this->gamelog_actions);
64 this->gamelog_action = nullptr;
65 this->gamelog_actions = 0;
67 ClearGRFConfigList(&this->grfconfig);
70 /** Load game/scenario with optional content download */
71 static const NWidgetPart _nested_load_dialog_widgets[] = {
72 NWidget(NWID_HORIZONTAL),
73 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
74 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
75 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
76 EndContainer(),
77 /* Current directory and free space */
78 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
80 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
81 /* Left side : filter box and available files */
82 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
83 /* Filter box with label */
84 NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
85 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
86 NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
87 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
88 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
89 EndContainer(),
90 /* Sort buttons */
91 NWidget(NWID_HORIZONTAL),
92 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
93 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
94 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
95 EndContainer(),
96 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
97 EndContainer(),
98 /* Files */
99 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
100 NWidget(NWID_HORIZONTAL),
101 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
102 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
103 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
104 EndContainer(),
105 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SL_CONTENT_DOWNLOAD_SEL),
106 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0),
107 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
108 EndContainer(),
109 EndContainer(),
110 EndContainer(),
112 /* Right side : game details */
113 NWidget(WWT_PANEL, COLOUR_GREY),
114 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
115 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_MISSING_NEWGRFS), SetDataTip(STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON, STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
116 NWidget(NWID_HORIZONTAL),
117 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
118 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_NEWGRF_INFO), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL), SetFill(1, 0), SetResize(1, 0),
119 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
120 EndContainer(),
121 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
122 EndContainer(),
123 EndContainer(),
124 EndContainer(),
127 /** Load heightmap with content download */
128 static const NWidgetPart _nested_load_heightmap_dialog_widgets[] = {
129 NWidget(NWID_HORIZONTAL),
130 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
131 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
132 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
133 EndContainer(),
134 /* Current directory and free space */
135 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
137 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
138 /* Filter box with label */
139 NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
140 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
141 NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
142 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
143 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
144 EndContainer(),
145 /* Sort Buttons */
146 NWidget(NWID_HORIZONTAL),
147 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
148 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
149 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
150 EndContainer(),
151 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
152 EndContainer(),
153 /* Files */
154 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
155 NWidget(NWID_HORIZONTAL),
156 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
157 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
158 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
159 EndContainer(),
160 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
161 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
162 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
163 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetResize(1, 0), SetFill(1, 0),
164 SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_HEIGHTMAP_TOOLTIP),
165 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
166 EndContainer(),
167 EndContainer(),
168 EndContainer(),
171 /** Save game/scenario */
172 static const NWidgetPart _nested_save_dialog_widgets[] = {
173 NWidget(NWID_HORIZONTAL),
174 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
175 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
176 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
177 EndContainer(),
178 /* Current directory and free space */
179 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
180 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
181 /* Left side : filter box and available files */
182 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
183 /* Filter box with label */
184 NWidget(NWID_HORIZONTAL), SetPadding(WD_FRAMERECT_TOP, 0, WD_FRAMERECT_BOTTOM, 0),
185 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, 0),
186 NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
187 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
188 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
189 EndContainer(),
190 /* Sort buttons */
191 NWidget(NWID_HORIZONTAL),
192 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
193 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
194 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
195 EndContainer(),
196 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
197 EndContainer(),
198 /* Files */
199 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
200 NWidget(NWID_HORIZONTAL),
201 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetPadding(2, 1, 0, 2),
202 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
203 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
204 EndContainer(),
205 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
206 SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP),
207 EndContainer(),
208 /* Save/delete buttons */
209 NWidget(NWID_HORIZONTAL),
210 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
211 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SAVE_GAME), SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
212 EndContainer(),
213 EndContainer(),
215 /* Right side : game details */
216 NWidget(WWT_PANEL, COLOUR_GREY),
217 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
218 NWidget(NWID_HORIZONTAL),
219 NWidget(NWID_SPACER), SetResize(1, 0), SetFill(1, 1),
220 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
221 EndContainer(),
222 EndContainer(),
223 EndContainer(),
226 /** Text colours of #DetailedFileType fios entries in the window. */
227 static const TextColour _fios_colours[] = {
228 TC_LIGHT_BROWN, // DFT_OLD_GAME_FILE
229 TC_ORANGE, // DFT_GAME_FILE
230 TC_YELLOW, // DFT_HEIGHTMAP_BMP
231 TC_ORANGE, // DFT_HEIGHTMAP_PNG
232 TC_LIGHT_BLUE, // DFT_FIOS_DRIVE
233 TC_DARK_GREEN, // DFT_FIOS_PARENT
234 TC_DARK_GREEN, // DFT_FIOS_DIR
235 TC_ORANGE, // DFT_FIOS_DIRECT
240 * Sort the collected list save games prior to displaying it in the save/load gui.
241 * @param[in,out] file_list List of save game files found in the directory.
243 static void SortSaveGameList(FileList &file_list)
245 size_t sort_start = 0;
246 size_t sort_end = 0;
248 /* Directories are always above the files (FIOS_TYPE_DIR)
249 * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
250 * Only sort savegames/scenarios, not directories
252 for (const FiosItem *item = file_list.Begin(); item != file_list.End(); item++) {
253 switch (item->type) {
254 case FIOS_TYPE_DIR: sort_start++; break;
255 case FIOS_TYPE_PARENT: sort_start++; break;
256 case FIOS_TYPE_DRIVE: sort_end++; break;
257 default: break;
261 std::sort(file_list.files.begin() + sort_start, file_list.files.end() - sort_end);
264 struct SaveLoadWindow : public Window {
265 private:
266 static const uint EDITBOX_MAX_SIZE = 50;
268 QueryString filename_editbox; ///< Filename editbox.
269 AbstractFileType abstract_filetype; /// Type of file to select.
270 SaveLoadOperation fop; ///< File operation to perform.
271 FileList fios_items; ///< Save game list.
272 FiosItem o_dir; ///< Original dir (home dir for this browser)
273 const FiosItem *selected; ///< Selected game in #fios_items, or \c nullptr.
274 const FiosItem *highlighted; ///< Item in fios_items highlighted by mouse pointer, or \c nullptr.
275 Scrollbar *vscroll;
277 StringFilter string_filter; ///< Filter for available games.
278 QueryString filter_editbox; ///< Filter editbox;
279 std::vector<bool> fios_items_shown; ///< Map of the filtered out fios items
281 static void SaveGameConfirmationCallback(Window *w, bool confirmed)
283 /* File name has already been written to _file_to_saveload */
284 if (confirmed) _switch_mode = SM_SAVE_GAME;
287 static void SaveHeightmapConfirmationCallback(Window *w, bool confirmed)
289 /* File name has already been written to _file_to_saveload */
290 if (confirmed) _switch_mode = SM_SAVE_HEIGHTMAP;
293 public:
295 /** Generate a default save filename. */
296 void GenerateFileName()
298 GenerateDefaultSaveName(this->filename_editbox.text.buf, &this->filename_editbox.text.buf[this->filename_editbox.text.max_bytes - 1]);
299 this->filename_editbox.text.UpdateSize();
302 SaveLoadWindow(WindowDesc *desc, AbstractFileType abstract_filetype, SaveLoadOperation fop)
303 : Window(desc), filename_editbox(64), abstract_filetype(abstract_filetype), fop(fop), filter_editbox(EDITBOX_MAX_SIZE)
305 assert(this->fop == SLO_SAVE || this->fop == SLO_LOAD);
307 /* For saving, construct an initial file name. */
308 if (this->fop == SLO_SAVE) {
309 switch (this->abstract_filetype) {
310 case FT_SAVEGAME:
311 this->GenerateFileName();
312 break;
314 case FT_SCENARIO:
315 case FT_HEIGHTMAP:
316 this->filename_editbox.text.Assign("UNNAMED");
317 break;
319 default:
320 NOT_REACHED();
323 this->querystrings[WID_SL_SAVE_OSK_TITLE] = &this->filename_editbox;
324 this->filename_editbox.ok_button = WID_SL_SAVE_GAME;
326 this->CreateNestedTree(true);
327 if (this->fop == SLO_LOAD && this->abstract_filetype == FT_SAVEGAME) {
328 this->GetWidget<NWidgetStacked>(WID_SL_CONTENT_DOWNLOAD_SEL)->SetDisplayedPlane(SZSP_HORIZONTAL);
331 /* Select caption string of the window. */
332 StringID caption_string;
333 switch (this->abstract_filetype) {
334 case FT_SAVEGAME:
335 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_CAPTION : STR_SAVELOAD_LOAD_CAPTION;
336 break;
338 case FT_SCENARIO:
339 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_SCENARIO : STR_SAVELOAD_LOAD_SCENARIO;
340 break;
342 case FT_HEIGHTMAP:
343 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_HEIGHTMAP : STR_SAVELOAD_LOAD_HEIGHTMAP;
344 break;
346 default:
347 NOT_REACHED();
349 this->GetWidget<NWidgetCore>(WID_SL_CAPTION)->widget_data = caption_string;
351 this->vscroll = this->GetScrollbar(WID_SL_SCROLLBAR);
352 this->FinishInitNested(0);
354 this->LowerWidget(WID_SL_DRIVES_DIRECTORIES_LIST);
355 this->querystrings[WID_SL_FILTER] = &this->filter_editbox;
356 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
358 /* pause is only used in single-player, non-editor mode, non-menu mode. It
359 * will be unpaused in the WE_DESTROY event handler. */
360 if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
361 DoCommandP(0, PM_PAUSED_SAVELOAD, 1, CMD_PAUSE);
363 SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
365 this->OnInvalidateData(SLIWD_RESCAN_FILES);
367 ResetObjectToPlace();
369 /* Select the initial directory. */
370 o_dir.type = FIOS_TYPE_DIRECT;
371 switch (this->abstract_filetype) {
372 case FT_SAVEGAME:
373 FioGetDirectory(o_dir.name, lastof(o_dir.name), SAVE_DIR);
374 break;
376 case FT_SCENARIO:
377 FioGetDirectory(o_dir.name, lastof(o_dir.name), SCENARIO_DIR);
378 break;
380 case FT_HEIGHTMAP:
381 FioGetDirectory(o_dir.name, lastof(o_dir.name), HEIGHTMAP_DIR);
382 break;
384 default:
385 strecpy(o_dir.name, _personal_dir, lastof(o_dir.name));
388 switch (this->fop) {
389 case SLO_SAVE:
390 /* Focus the edit box by default in the save window */
391 this->SetFocusedWidget(WID_SL_SAVE_OSK_TITLE);
392 break;
394 default:
395 this->SetFocusedWidget(WID_SL_FILTER);
399 virtual ~SaveLoadWindow()
401 /* pause is only used in single-player, non-editor mode, non menu mode */
402 if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
403 DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE);
407 void DrawWidget(const Rect &r, int widget) const override
409 switch (widget) {
410 case WID_SL_SORT_BYNAME:
411 case WID_SL_SORT_BYDATE:
412 if (((_savegame_sort_order & SORT_BY_NAME) != 0) == (widget == WID_SL_SORT_BYNAME)) {
413 this->DrawSortButtonState(widget, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
415 break;
417 case WID_SL_BACKGROUND: {
418 static const char *path = nullptr;
419 static StringID str = STR_ERROR_UNABLE_TO_READ_DRIVE;
420 static uint64 tot = 0;
422 if (_fios_path_changed) {
423 str = FiosGetDescText(&path, &tot);
424 _fios_path_changed = false;
427 if (str != STR_ERROR_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
428 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP, str);
429 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, path, TC_BLACK);
430 break;
433 case WID_SL_DRIVES_DIRECTORIES_LIST: {
434 GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, PC_BLACK);
436 uint y = r.top + WD_FRAMERECT_TOP;
437 uint scroll_pos = this->vscroll->GetPosition();
438 for (uint row = 0; row < this->fios_items.Length(); row++) {
439 if (!this->fios_items_shown[row]) {
440 /* The current item is filtered out : we do not show it */
441 scroll_pos++;
442 continue;
444 if (row < scroll_pos) continue;
445 const FiosItem *item = this->fios_items.Get(row);
447 if (item == this->selected) {
448 GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_DARK_BLUE);
449 } else if (item == this->highlighted) {
450 GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_VERY_DARK_BLUE);
452 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, item->title, _fios_colours[GetDetailedFileType(item->type)]);
453 y += this->resize.step_height;
454 if (y >= this->vscroll->GetCapacity() * this->resize.step_height + r.top + WD_FRAMERECT_TOP) break;
456 break;
459 case WID_SL_DETAILS: {
460 GfxFillRect(r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP,
461 r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL * 2 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM, PC_GREY);
462 DrawString(r.left, r.right, r.top + FONT_HEIGHT_NORMAL / 2 + WD_FRAMERECT_TOP, STR_SAVELOAD_DETAIL_CAPTION, TC_FROMSTRING, SA_HOR_CENTER);
464 if (this->selected == nullptr) break;
466 uint y = r.top + FONT_HEIGHT_NORMAL * 2 + WD_PAR_VSEP_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
467 uint y_max = r.bottom - FONT_HEIGHT_NORMAL - WD_FRAMERECT_BOTTOM;
469 if (y > y_max) break;
470 if (!_load_check_data.checkable) {
471 /* Old savegame, no information available */
472 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_NOT_AVAILABLE);
473 y += FONT_HEIGHT_NORMAL;
474 } else if (_load_check_data.error != INVALID_STRING_ID) {
475 /* Incompatible / broken savegame */
476 SetDParamStr(0, _load_check_data.error_data);
477 y = DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT,
478 y, r.bottom - WD_FRAMERECT_BOTTOM, _load_check_data.error, TC_RED);
479 } else {
480 /* Mapsize */
481 SetDParam(0, _load_check_data.map_size_x);
482 SetDParam(1, _load_check_data.map_size_y);
483 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_MAP_SIZE);
484 y += FONT_HEIGHT_NORMAL;
485 if (y > y_max) break;
487 /* Climate */
488 byte landscape = _load_check_data.settings.game_creation.landscape;
489 if (landscape < NUM_LANDSCAPE) {
490 SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + landscape);
491 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANDSCAPE);
492 y += FONT_HEIGHT_NORMAL;
495 y += WD_PAR_VSEP_NORMAL;
496 if (y > y_max) break;
498 /* Start date (if available) */
499 if (_load_check_data.settings.game_creation.starting_year != 0) {
500 SetDParam(0, ConvertYMDToDate(_load_check_data.settings.game_creation.starting_year, 0, 1));
501 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_START_DATE);
502 y += FONT_HEIGHT_NORMAL;
504 if (y > y_max) break;
506 /* Hide current date for scenarios */
507 if (this->abstract_filetype != FT_SCENARIO) {
508 /* Current date */
509 SetDParam(0, _load_check_data.current_date);
510 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE);
511 y += FONT_HEIGHT_NORMAL;
514 /* Hide the NewGRF stuff when saving. We also hide the button. */
515 if (this->fop == SLO_LOAD && (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO)) {
516 y += WD_PAR_VSEP_NORMAL;
517 if (y > y_max) break;
519 /* NewGrf compatibility */
520 SetDParam(0, _load_check_data.grfconfig == nullptr ? STR_NEWGRF_LIST_NONE :
521 STR_NEWGRF_LIST_ALL_FOUND + _load_check_data.grf_compatibility);
522 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_GRFSTATUS);
523 y += FONT_HEIGHT_NORMAL;
525 if (y > y_max) break;
527 /* Hide the company stuff for scenarios */
528 if (this->abstract_filetype != FT_SCENARIO) {
529 y += FONT_HEIGHT_NORMAL;
530 if (y > y_max) break;
532 /* Companies / AIs */
533 for (auto &pair : _load_check_data.companies) {
534 SetDParam(0, pair.first + 1);
535 const CompanyProperties &c = *pair.second;
536 if (c.name != nullptr) {
537 SetDParam(1, STR_JUST_RAW_STRING);
538 SetDParamStr(2, c.name);
539 } else {
540 SetDParam(1, c.name_1);
541 SetDParam(2, c.name_2);
543 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_COMPANY_INDEX);
544 y += FONT_HEIGHT_NORMAL;
545 if (y > y_max) break;
549 break;
554 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
556 switch (widget) {
557 case WID_SL_BACKGROUND:
558 size->height = 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
559 break;
561 case WID_SL_DRIVES_DIRECTORIES_LIST:
562 resize->height = FONT_HEIGHT_NORMAL;
563 size->height = resize->height * 10 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
564 break;
565 case WID_SL_SORT_BYNAME:
566 case WID_SL_SORT_BYDATE: {
567 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
568 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
569 d.height += padding.height;
570 *size = maxdim(*size, d);
571 break;
576 void OnPaint() override
578 if (_savegame_sort_dirty) {
579 _savegame_sort_dirty = false;
580 SortSaveGameList(this->fios_items);
581 this->OnInvalidateData(SLIWD_FILTER_CHANGES);
584 this->DrawWidgets();
587 void OnClick(Point pt, int widget, int click_count) override
589 switch (widget) {
590 case WID_SL_SORT_BYNAME: // Sort save names by name
591 _savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
592 SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
593 _savegame_sort_dirty = true;
594 this->SetDirty();
595 break;
597 case WID_SL_SORT_BYDATE: // Sort save names by date
598 _savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
599 SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
600 _savegame_sort_dirty = true;
601 this->SetDirty();
602 break;
604 case WID_SL_HOME_BUTTON: // OpenTTD 'button', jumps to OpenTTD directory
605 FiosBrowseTo(&o_dir);
606 this->InvalidateData(SLIWD_RESCAN_FILES);
607 break;
609 case WID_SL_LOAD_BUTTON: {
610 if (this->selected == nullptr || _load_check_data.HasErrors()) break;
612 const char *name = FiosBrowseTo(this->selected);
613 _file_to_saveload.SetMode(this->selected->type);
614 _file_to_saveload.SetName(name);
615 _file_to_saveload.SetTitle(this->selected->title);
617 if (this->abstract_filetype == FT_HEIGHTMAP) {
618 delete this;
619 ShowHeightmapLoad();
620 } else if (!_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility != GLC_NOT_FOUND || _settings_client.gui.UserIsAllowedToChangeNewGRFs()) {
621 _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD_GAME;
622 ClearErrorMessages();
623 delete this;
625 break;
628 case WID_SL_NEWGRF_INFO:
629 if (_load_check_data.HasNewGrfs()) {
630 ShowNewGRFSettings(false, false, false, &_load_check_data.grfconfig);
632 break;
634 case WID_SL_MISSING_NEWGRFS:
635 if (!_network_available) {
636 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
637 } else if (_load_check_data.HasNewGrfs()) {
638 ShowMissingContentWindow(_load_check_data.grfconfig);
640 break;
642 case WID_SL_DRIVES_DIRECTORIES_LIST: { // Click the listbox
643 int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WD_FRAMERECT_TOP);
644 if (y == INT_MAX) return;
646 /* Get the corresponding non-filtered out item from the list */
647 int i = 0;
648 while (i <= y) {
649 if (!this->fios_items_shown[i]) y++;
650 i++;
652 const FiosItem *file = this->fios_items.Get(y);
654 const char *name = FiosBrowseTo(file);
655 if (name == nullptr) {
656 /* Changed directory, need refresh. */
657 this->InvalidateData(SLIWD_RESCAN_FILES);
658 break;
661 if (click_count == 1) {
662 if (this->selected != file) {
663 this->selected = file;
664 _load_check_data.Clear();
666 if (GetDetailedFileType(file->type) == DFT_GAME_FILE) {
667 /* Other detailed file types cannot be checked before. */
668 SaveOrLoad(name, SLO_CHECK, DFT_GAME_FILE, NO_DIRECTORY, false);
671 this->InvalidateData(SLIWD_SELECTION_CHANGES);
673 if (this->fop == SLO_SAVE) {
674 /* Copy clicked name to editbox */
675 this->filename_editbox.text.Assign(file->title);
676 this->SetWidgetDirty(WID_SL_SAVE_OSK_TITLE);
678 } else if (!_load_check_data.HasErrors()) {
679 this->selected = file;
680 if (this->fop == SLO_LOAD) {
681 if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) {
682 this->OnClick(pt, WID_SL_LOAD_BUTTON, 1);
683 } else {
684 assert(this->abstract_filetype == FT_HEIGHTMAP);
685 _file_to_saveload.SetMode(file->type);
686 _file_to_saveload.SetName(name);
687 _file_to_saveload.SetTitle(file->title);
689 delete this;
690 ShowHeightmapLoad();
694 break;
697 case WID_SL_CONTENT_DOWNLOAD:
698 if (!_network_available) {
699 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
700 } else {
701 assert(this->fop == SLO_LOAD);
702 switch (this->abstract_filetype) {
703 default: NOT_REACHED();
704 case FT_SCENARIO: ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_SCENARIO); break;
705 case FT_HEIGHTMAP: ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_HEIGHTMAP); break;
708 break;
710 case WID_SL_DELETE_SELECTION: // Delete
711 break;
713 case WID_SL_SAVE_GAME: // Save game
714 /* Note, this is also called via the OSK; and we need to lower the button. */
715 this->HandleButtonClick(WID_SL_SAVE_GAME);
716 break;
720 void OnMouseLoop() override
722 const Point pt{ _cursor.pos.x - this->left, _cursor.pos.y - this->top };
723 const int widget = GetWidgetFromPos(this, pt.x, pt.y);
725 if (widget == WID_SL_DRIVES_DIRECTORIES_LIST) {
726 int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WD_FRAMERECT_TOP);
727 if (y == INT_MAX) return;
729 /* Get the corresponding non-filtered out item from the list */
730 int i = 0;
731 while (i <= y) {
732 if (!this->fios_items_shown[i]) y++;
733 i++;
735 const FiosItem *file = this->fios_items.Get(y);
737 if (file != this->highlighted) {
738 this->highlighted = file;
739 this->SetWidgetDirty(WID_SL_DRIVES_DIRECTORIES_LIST);
741 } else if (this->highlighted != nullptr) {
742 this->highlighted = nullptr;
743 this->SetWidgetDirty(WID_SL_DRIVES_DIRECTORIES_LIST);
747 EventState OnKeyPress(WChar key, uint16 keycode) override
749 if (keycode == WKC_ESC) {
750 delete this;
751 return ES_HANDLED;
754 return ES_NOT_HANDLED;
757 void OnTimeout() override
759 /* Widgets WID_SL_DELETE_SELECTION and WID_SL_SAVE_GAME only exist when saving to a file. */
760 if (this->fop != SLO_SAVE) return;
762 if (this->IsWidgetLowered(WID_SL_DELETE_SELECTION)) { // Delete button clicked
763 if (!FiosDelete(this->filename_editbox.text.buf)) {
764 ShowErrorMessage(STR_ERROR_UNABLE_TO_DELETE_FILE, INVALID_STRING_ID, WL_ERROR);
765 } else {
766 this->InvalidateData(SLIWD_RESCAN_FILES);
767 /* Reset file name to current date on successful delete */
768 if (this->abstract_filetype == FT_SAVEGAME) GenerateFileName();
770 } else if (this->IsWidgetLowered(WID_SL_SAVE_GAME)) { // Save button clicked
771 if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) {
772 FiosMakeSavegameName(_file_to_saveload.name, this->filename_editbox.text.buf, lastof(_file_to_saveload.name));
773 if (FioCheckFileExists(_file_to_saveload.name, Subdirectory::SAVE_DIR)) {
774 ShowQuery(STR_SAVELOAD_OVERWRITE_TITLE, STR_SAVELOAD_OVERWRITE_WARNING, this, SaveLoadWindow::SaveGameConfirmationCallback);
775 } else {
776 _switch_mode = SM_SAVE_GAME;
778 } else {
779 FiosMakeHeightmapName(_file_to_saveload.name, this->filename_editbox.text.buf, lastof(_file_to_saveload.name));
780 if (FioCheckFileExists(_file_to_saveload.name, Subdirectory::SAVE_DIR)) {
781 ShowQuery(STR_SAVELOAD_OVERWRITE_TITLE, STR_SAVELOAD_OVERWRITE_WARNING, this, SaveLoadWindow::SaveHeightmapConfirmationCallback);
782 } else {
783 _switch_mode = SM_SAVE_HEIGHTMAP;
787 /* In the editor set up the vehicle engines correctly (date might have changed) */
788 if (_game_mode == GM_EDITOR) StartupEngines();
792 void OnResize() override
794 this->vscroll->SetCapacityFromWidget(this, WID_SL_DRIVES_DIRECTORIES_LIST);
798 * Some data on this window has become invalid.
799 * @param data Information about the changed data.
800 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
802 void OnInvalidateData(int data = 0, bool gui_scope = true) override
804 switch (data) {
805 case SLIWD_RESCAN_FILES:
806 /* Rescan files */
807 this->selected = nullptr;
808 _load_check_data.Clear();
809 if (!gui_scope) break;
811 _fios_path_changed = true;
812 this->fios_items.BuildFileList(this->abstract_filetype, this->fop);
813 this->vscroll->SetCount((uint)this->fios_items.Length());
814 this->selected = nullptr;
815 _load_check_data.Clear();
817 /* We reset the files filtered */
818 this->OnInvalidateData(SLIWD_FILTER_CHANGES);
820 FALLTHROUGH;
822 case SLIWD_SELECTION_CHANGES:
823 /* Selection changes */
824 if (!gui_scope) break;
826 if (this->fop != SLO_LOAD) break;
828 switch (this->abstract_filetype) {
829 case FT_HEIGHTMAP:
830 this->SetWidgetDisabledState(WID_SL_LOAD_BUTTON, this->selected == nullptr || _load_check_data.HasErrors());
831 break;
833 case FT_SAVEGAME:
834 case FT_SCENARIO: {
835 bool disabled = this->selected == nullptr || _load_check_data.HasErrors();
836 if (!_settings_client.gui.UserIsAllowedToChangeNewGRFs()) {
837 disabled |= _load_check_data.HasNewGrfs() && _load_check_data.grf_compatibility == GLC_NOT_FOUND;
839 this->SetWidgetDisabledState(WID_SL_LOAD_BUTTON, disabled);
840 this->SetWidgetDisabledState(WID_SL_NEWGRF_INFO, !_load_check_data.HasNewGrfs());
841 this->SetWidgetDisabledState(WID_SL_MISSING_NEWGRFS,
842 !_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility == GLC_ALL_GOOD);
843 break;
846 default:
847 NOT_REACHED();
849 break;
851 case SLIWD_FILTER_CHANGES:
852 /* Filter changes */
853 this->fios_items_shown.resize(this->fios_items.Length());
854 uint items_shown_count = 0; ///< The number of items shown in the list
855 /* We pass through every fios item */
856 for (uint i = 0; i < this->fios_items.Length(); i++) {
857 if (this->string_filter.IsEmpty()) {
858 /* We don't filter anything out if the filter editbox is empty */
859 this->fios_items_shown[i] = true;
860 items_shown_count++;
861 } else {
862 this->string_filter.ResetState();
863 this->string_filter.AddLine(this->fios_items[i].title);
864 /* We set the vector to show this fios element as filtered depending on the result of the filter */
865 this->fios_items_shown[i] = this->string_filter.GetState();
866 if (this->fios_items_shown[i]) items_shown_count++;
868 if (&(this->fios_items[i]) == this->selected && this->fios_items_shown[i] == false) {
869 /* The selected element has been filtered out */
870 this->selected = nullptr;
871 this->OnInvalidateData(SLIWD_SELECTION_CHANGES);
875 this->vscroll->SetCount(items_shown_count);
876 break;
880 void OnEditboxChanged(int wid) override
882 if (wid == WID_SL_FILTER) {
883 this->string_filter.SetFilterTerm(this->filter_editbox.text.buf);
884 this->InvalidateData(SLIWD_FILTER_CHANGES);
889 /** Load game/scenario */
890 static WindowDesc _load_dialog_desc(
891 WDP_CENTER, "load_game", 500, 294,
892 WC_SAVELOAD, WC_NONE,
894 _nested_load_dialog_widgets, lengthof(_nested_load_dialog_widgets)
897 /** Load heightmap */
898 static WindowDesc _load_heightmap_dialog_desc(
899 WDP_CENTER, "load_heightmap", 257, 320,
900 WC_SAVELOAD, WC_NONE,
902 _nested_load_heightmap_dialog_widgets, lengthof(_nested_load_heightmap_dialog_widgets)
905 /** Save game/scenario */
906 static WindowDesc _save_dialog_desc(
907 WDP_CENTER, "save_game", 500, 294,
908 WC_SAVELOAD, WC_NONE,
910 _nested_save_dialog_widgets, lengthof(_nested_save_dialog_widgets)
914 * Launch save/load dialog in the given mode.
915 * @param abstract_filetype Kind of file to handle.
916 * @param fop File operation to perform (load or save).
918 void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop)
920 DeleteWindowById(WC_SAVELOAD, 0);
922 WindowDesc *sld;
923 if (fop == SLO_SAVE) {
924 sld = &_save_dialog_desc;
925 } else {
926 /* Dialogue for loading a file. */
927 sld = (abstract_filetype == FT_HEIGHTMAP) ? &_load_heightmap_dialog_desc : &_load_dialog_desc;
930 _file_to_saveload.abstract_ftype = abstract_filetype;
932 new SaveLoadWindow(sld, abstract_filetype, fop);