Fix crash when setting separation mode for vehicles with no orders list.
[openttd-joker.git] / src / fios_gui.cpp
blob89cde48c8215ca4cc908280c86362555d97202bc
1 /* $Id: fios_gui.cpp 26428 2014-03-25 21:47:07Z planetmaker $ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
10 /** @file fios_gui.cpp GUIs for loading/saving games, scenarios, heightmaps, ... */
12 #include "stdafx.h"
13 #include "saveload/saveload.h"
14 #include "error.h"
15 #include "gui.h"
16 #include "gfx_func.h"
17 #include "command_func.h"
18 #include "network/network.h"
19 #include "network/network_content.h"
20 #include "strings_func.h"
21 #include "fileio_func.h"
22 #include "fios.h"
23 #include "window_func.h"
24 #include "tilehighlight_func.h"
25 #include "querystring_gui.h"
26 #include "engine_func.h"
27 #include "landscape_type.h"
28 #include "date_func.h"
29 #include "core/geometry_func.hpp"
30 #include "gamelog.h"
32 #include "widgets/fios_widget.h"
34 #include "table/sprites.h"
35 #include "table/strings.h"
37 #include "safeguards.h"
39 LoadCheckData _load_check_data; ///< Data loaded from save during SL_LOAD_CHECK.
41 static bool _fios_path_changed;
42 static bool _savegame_sort_dirty;
45 /**
46 * Reset read data.
48 void LoadCheckData::Clear()
50 this->checkable = false;
51 this->error = INVALID_STRING_ID;
52 free(this->error_data);
53 this->error_data = nullptr;
55 this->map_size_x = this->map_size_y = 256; // Default for old savegames which do not store mapsize.
56 this->current_date = 0;
57 memset(&this->settings, 0, sizeof(this->settings));
59 const CompanyPropertiesMap::iterator end = this->companies.End();
60 for (CompanyPropertiesMap::iterator it = this->companies.Begin(); it != end; it++) {
61 delete it->second;
63 companies.Clear();
65 GamelogFree(this->gamelog_action, this->gamelog_actions);
66 this->gamelog_action = nullptr;
67 this->gamelog_actions = 0;
69 ClearGRFConfigList(&this->grfconfig);
72 /** Load game/scenario with optional content download */
73 static const NWidgetPart _nested_load_dialog_widgets[] = {
74 NWidget(NWID_HORIZONTAL),
75 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
76 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
77 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
78 EndContainer(),
79 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
80 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
81 NWidget(NWID_VERTICAL),
82 NWidget(NWID_HORIZONTAL),
83 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
84 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),
85 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),
86 EndContainer(),
87 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
88 EndContainer(),
89 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
90 NWidget(NWID_HORIZONTAL),
91 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
92 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
93 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
94 EndContainer(),
95 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SL_CONTENT_DOWNLOAD_SEL),
96 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0),
97 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
98 EndContainer(),
99 EndContainer(),
100 EndContainer(),
101 NWidget(WWT_PANEL, COLOUR_GREY),
102 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
103 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),
104 NWidget(NWID_HORIZONTAL),
105 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
106 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_NEWGRF_INFO), SetDataTip(STR_INTRO_NEWGRF_SETTINGS, STR_NULL), SetFill(1, 0), SetResize(1, 0),
107 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
108 EndContainer(),
109 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
110 EndContainer(),
111 EndContainer(),
112 EndContainer(),
115 /** Load heightmap with content download */
116 static const NWidgetPart _nested_load_heightmap_dialog_widgets[] = {
117 NWidget(NWID_HORIZONTAL),
118 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
119 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
120 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
121 EndContainer(),
122 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
123 NWidget(NWID_VERTICAL),
124 NWidget(NWID_HORIZONTAL),
125 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
126 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),
127 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),
128 EndContainer(),
129 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
130 EndContainer(),
131 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
132 NWidget(NWID_HORIZONTAL),
133 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 1, 2, 2),
134 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
135 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
136 EndContainer(),
137 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
138 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_CONTENT_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
139 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
140 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetResize(1, 0), SetFill(1, 0),
141 SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_HEIGHTMAP_TOOLTIP),
142 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
143 EndContainer(),
144 EndContainer(),
145 EndContainer(),
148 /** Save game/scenario */
149 static const NWidgetPart _nested_save_dialog_widgets[] = {
150 NWidget(NWID_HORIZONTAL),
151 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
152 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
153 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
154 EndContainer(),
155 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
156 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
157 NWidget(NWID_VERTICAL),
158 NWidget(NWID_HORIZONTAL),
159 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
160 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),
161 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),
162 EndContainer(),
163 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
164 EndContainer(),
165 NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
166 NWidget(NWID_HORIZONTAL),
167 NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetPadding(2, 1, 0, 2),
168 SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
169 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
170 EndContainer(),
171 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_SAVE_OSK_TITLE), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
172 SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP),
173 EndContainer(),
174 NWidget(NWID_HORIZONTAL),
175 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
176 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SAVE_GAME), SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
177 EndContainer(),
178 EndContainer(),
179 NWidget(WWT_PANEL, COLOUR_GREY),
180 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SL_DETAILS), SetResize(1, 1), SetFill(1, 1),
181 NWidget(NWID_HORIZONTAL),
182 NWidget(NWID_SPACER), SetResize(1, 0), SetFill(1, 1),
183 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
184 EndContainer(),
185 EndContainer(),
186 EndContainer(),
189 /** Text colours of #DetailedFileType fios entries in the window. */
190 static const TextColour _fios_colours[] = {
191 TC_LIGHT_BROWN, // DFT_OLD_GAME_FILE
192 TC_ORANGE, // DFT_GAME_FILE
193 TC_YELLOW, // DFT_HEIGHTMAP_BMP
194 TC_ORANGE, // DFT_HEIGHTMAP_PNG
195 TC_LIGHT_BLUE, // DFT_FIOS_DRIVE
196 TC_DARK_GREEN, // DFT_FIOS_PARENT
197 TC_DARK_GREEN, // DFT_FIOS_DIR
198 TC_ORANGE, // DFT_FIOS_DIRECT
203 * Sort the collected list save games prior to displaying it in the save/load gui.
204 * @param [inout] file_list List of save game files found in the directory.
206 static void SortSaveGameList(FileList &file_list)
208 uint sort_start = 0;
209 uint sort_end = 0;
211 /* Directories are always above the files (FIOS_TYPE_DIR)
212 * Drives (A:\ (windows only) are always under the files (FIOS_TYPE_DRIVE)
213 * Only sort savegames/scenarios, not directories
215 for (const FiosItem *item = file_list.Begin(); item != file_list.End(); item++) {
216 switch (item->type) {
217 case FIOS_TYPE_DIR: sort_start++; break;
218 case FIOS_TYPE_PARENT: sort_start++; break;
219 case FIOS_TYPE_DRIVE: sort_end++; break;
220 default: break;
224 uint s_amount = file_list.Length() - sort_start - sort_end;
225 QSortT(file_list.Get(sort_start), s_amount, CompareFiosItems);
228 struct SaveLoadWindow : public Window {
229 private:
230 QueryString filename_editbox; ///< Filename editbox.
231 AbstractFileType abstract_filetype; /// Type of file to select.
232 SaveLoadOperation fop; ///< File operation to perform.
233 FileList fios_items; ///< Save game list.
234 FiosItem o_dir;
235 const FiosItem *selected; ///< Selected game in #fios_items, or \c nullptr.
236 Scrollbar *vscroll;
237 public:
239 /** Generate a default save filename. */
240 void GenerateFileName()
242 GenerateDefaultSaveName(this->filename_editbox.text.buf, &this->filename_editbox.text.buf[this->filename_editbox.text.max_bytes - 1]);
243 this->filename_editbox.text.UpdateSize();
246 SaveLoadWindow(WindowDesc *desc, AbstractFileType abstract_filetype, SaveLoadOperation fop)
247 : Window(desc), filename_editbox(64), abstract_filetype(abstract_filetype), fop(fop)
249 assert(this->fop == SLO_SAVE || this->fop == SLO_LOAD);
251 /* For saving, construct an initial file name. */
252 if (this->fop == SLO_SAVE) {
253 switch (this->abstract_filetype) {
254 case FT_SAVEGAME:
255 this->GenerateFileName();
256 break;
258 case FT_SCENARIO:
259 case FT_HEIGHTMAP:
260 this->filename_editbox.text.Assign("UNNAMED");
261 break;
263 default:
264 NOT_REACHED();
267 this->querystrings[WID_SL_SAVE_OSK_TITLE] = &this->filename_editbox;
268 this->filename_editbox.ok_button = WID_SL_SAVE_GAME;
270 this->CreateNestedTree(true);
271 if (this->fop == SLO_LOAD && this->abstract_filetype == FT_SAVEGAME) {
272 this->GetWidget<NWidgetStacked>(WID_SL_CONTENT_DOWNLOAD_SEL)->SetDisplayedPlane(SZSP_HORIZONTAL);
275 /* Select caption string of the window. */
276 StringID caption_string;
277 switch (this->abstract_filetype) {
278 case FT_SAVEGAME:
279 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_CAPTION : STR_SAVELOAD_LOAD_CAPTION;
280 break;
282 case FT_SCENARIO:
283 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_SCENARIO : STR_SAVELOAD_LOAD_SCENARIO;
284 break;
286 case FT_HEIGHTMAP:
287 caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_HEIGHTMAP : STR_SAVELOAD_LOAD_HEIGHTMAP;
288 break;
290 default:
291 NOT_REACHED();
293 this->GetWidget<NWidgetCore>(WID_SL_CAPTION)->widget_data = caption_string;
295 this->vscroll = this->GetScrollbar(WID_SL_SCROLLBAR);
296 this->FinishInitNested(0);
298 this->LowerWidget(WID_SL_DRIVES_DIRECTORIES_LIST);
300 /* pause is only used in single-player, non-editor mode, non-menu mode. It
301 * will be unpaused in the WE_DESTROY event handler. */
302 if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) {
303 DoCommandP(0, PM_PAUSED_SAVELOAD, 1, CMD_PAUSE);
305 SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
307 this->OnInvalidateData(0);
309 ResetObjectToPlace();
311 /* Select the initial directory. */
312 o_dir.type = FIOS_TYPE_DIRECT;
313 switch (this->abstract_filetype) {
314 case FT_SAVEGAME:
315 FioGetDirectory(o_dir.name, lastof(o_dir.name), SAVE_DIR);
316 break;
318 case FT_SCENARIO:
319 FioGetDirectory(o_dir.name, lastof(o_dir.name), SCENARIO_DIR);
320 break;
322 case FT_HEIGHTMAP:
323 FioGetDirectory(o_dir.name, lastof(o_dir.name), HEIGHTMAP_DIR);
324 break;
326 default:
327 strecpy(o_dir.name, _personal_dir, lastof(o_dir.name));
330 /* Focus the edit box by default in the save windows */
331 if (this->fop == SLO_SAVE) this->SetFocusedWidget(WID_SL_SAVE_OSK_TITLE);
334 virtual ~SaveLoadWindow()
336 /* pause is only used in single-player, non-editor mode, non menu mode */
337 if (!_networking && _game_mode != GM_EDITOR && _game_mode != GM_MENU) {
338 DoCommandP(0, PM_PAUSED_SAVELOAD, 0, CMD_PAUSE);
342 virtual void DrawWidget(const Rect &r, int widget) const
344 switch (widget) {
345 case WID_SL_SORT_BYNAME:
346 case WID_SL_SORT_BYDATE:
347 if (((_savegame_sort_order & SORT_BY_NAME) != 0) == (widget == WID_SL_SORT_BYNAME)) {
348 this->DrawSortButtonState(widget, _savegame_sort_order & SORT_DESCENDING ? SBS_DOWN : SBS_UP);
350 break;
352 case WID_SL_BACKGROUND: {
353 static const char *path = nullptr;
354 static StringID str = STR_ERROR_UNABLE_TO_READ_DRIVE;
355 static uint64 tot = 0;
357 if (_fios_path_changed) {
358 str = FiosGetDescText(&path, &tot);
359 _fios_path_changed = false;
362 if (str != STR_ERROR_UNABLE_TO_READ_DRIVE) SetDParam(0, tot);
363 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP, str);
364 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, path, TC_BLACK);
365 break;
368 case WID_SL_DRIVES_DIRECTORIES_LIST: {
369 GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, PC_BLACK);
371 uint y = r.top + WD_FRAMERECT_TOP;
372 for (uint pos = this->vscroll->GetPosition(); pos < this->fios_items.Length(); pos++) {
373 const FiosItem *item = this->fios_items.Get(pos);
375 if (item == this->selected) {
376 GfxFillRect(r.left + 1, y, r.right, y + this->resize.step_height, PC_DARK_BLUE);
378 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, item->title, _fios_colours[GetDetailedFileType(item->type)]);
379 y += this->resize.step_height;
380 if (y >= this->vscroll->GetCapacity() * this->resize.step_height + r.top + WD_FRAMERECT_TOP) break;
382 break;
385 case WID_SL_DETAILS: {
386 GfxFillRect(r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP,
387 r.right - WD_FRAMERECT_RIGHT, r.top + FONT_HEIGHT_NORMAL * 2 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM, PC_GREY);
388 DrawString(r.left, r.right, r.top + FONT_HEIGHT_NORMAL / 2 + WD_FRAMERECT_TOP, STR_SAVELOAD_DETAIL_CAPTION, TC_FROMSTRING, SA_HOR_CENTER);
390 if (this->selected == nullptr) break;
392 uint y = r.top + FONT_HEIGHT_NORMAL * 2 + WD_PAR_VSEP_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
393 uint y_max = r.bottom - FONT_HEIGHT_NORMAL - WD_FRAMERECT_BOTTOM;
395 if (y > y_max) break;
396 if (!_load_check_data.checkable) {
397 /* Old savegame, no information available */
398 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_NOT_AVAILABLE);
399 y += FONT_HEIGHT_NORMAL;
400 } else if (_load_check_data.error != INVALID_STRING_ID) {
401 /* Incompatible / broken savegame */
402 SetDParamStr(0, _load_check_data.error_data);
403 y = DrawStringMultiLine(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT,
404 y, r.bottom - WD_FRAMERECT_BOTTOM, _load_check_data.error, TC_RED);
405 } else {
406 /* Mapsize */
407 SetDParam(0, _load_check_data.map_size_x);
408 SetDParam(1, _load_check_data.map_size_y);
409 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_MAP_SIZE);
410 y += FONT_HEIGHT_NORMAL;
411 if (y > y_max) break;
413 /* Climate */
414 byte landscape = _load_check_data.settings.game_creation.landscape;
415 if (landscape < NUM_LANDSCAPE) {
416 SetDParam(0, STR_CHEAT_SWITCH_CLIMATE_TEMPERATE_LANDSCAPE + landscape);
417 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_LANDSCAPE);
418 y += FONT_HEIGHT_NORMAL;
421 y += WD_PAR_VSEP_NORMAL;
422 if (y > y_max) break;
424 /* Start date (if available) */
425 if (_load_check_data.settings.game_creation.starting_year != 0) {
426 SetDParam(0, ConvertYMDToDate(_load_check_data.settings.game_creation.starting_year, 0, 1));
427 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_START_DATE);
428 y += FONT_HEIGHT_NORMAL;
430 if (y > y_max) break;
432 /* Hide current date for scenarios */
433 if (this->abstract_filetype != FT_SCENARIO) {
434 /* Current date */
435 SetDParam(0, _load_check_data.current_date);
436 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_NETWORK_SERVER_LIST_CURRENT_DATE);
437 y += FONT_HEIGHT_NORMAL;
440 /* Hide the NewGRF stuff when saving. We also hide the button. */
441 if (this->fop == SLO_LOAD && (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO)) {
442 y += WD_PAR_VSEP_NORMAL;
443 if (y > y_max) break;
445 /* NewGrf compatibility */
446 SetDParam(0, _load_check_data.grfconfig == nullptr ? STR_NEWGRF_LIST_NONE :
447 STR_NEWGRF_LIST_ALL_FOUND + _load_check_data.grf_compatibility);
448 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_GRFSTATUS);
449 y += FONT_HEIGHT_NORMAL;
451 if (y > y_max) break;
453 /* Hide the company stuff for scenarios */
454 if (this->abstract_filetype != FT_SCENARIO) {
455 y += FONT_HEIGHT_NORMAL;
456 if (y > y_max) break;
458 /* Companies / AIs */
459 CompanyPropertiesMap::const_iterator end = _load_check_data.companies.End();
460 for (CompanyPropertiesMap::const_iterator it = _load_check_data.companies.Begin(); it != end; it++) {
461 SetDParam(0, it->first + 1);
462 const CompanyProperties &c = *it->second;
463 if (c.name != nullptr) {
464 SetDParam(1, STR_JUST_RAW_STRING);
465 SetDParamStr(2, c.name);
466 } else {
467 SetDParam(1, c.name_1);
468 SetDParam(2, c.name_2);
470 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SAVELOAD_DETAIL_COMPANY_INDEX);
471 y += FONT_HEIGHT_NORMAL;
472 if (y > y_max) break;
476 break;
481 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
483 switch (widget) {
484 case WID_SL_BACKGROUND:
485 size->height = 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
486 break;
488 case WID_SL_DRIVES_DIRECTORIES_LIST:
489 resize->height = FONT_HEIGHT_NORMAL;
490 size->height = resize->height * 10 + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
491 break;
492 case WID_SL_SORT_BYNAME:
493 case WID_SL_SORT_BYDATE: {
494 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
495 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
496 d.height += padding.height;
497 *size = maxdim(*size, d);
498 break;
503 virtual void OnPaint()
505 if (_savegame_sort_dirty) {
506 _savegame_sort_dirty = false;
507 SortSaveGameList(this->fios_items);
510 this->vscroll->SetCount(this->fios_items.Length());
511 this->DrawWidgets();
514 virtual void OnClick(Point pt, int widget, int click_count)
516 switch (widget) {
517 case WID_SL_SORT_BYNAME: // Sort save names by name
518 _savegame_sort_order = (_savegame_sort_order == SORT_BY_NAME) ?
519 SORT_BY_NAME | SORT_DESCENDING : SORT_BY_NAME;
520 _savegame_sort_dirty = true;
521 this->SetDirty();
522 break;
524 case WID_SL_SORT_BYDATE: // Sort save names by date
525 _savegame_sort_order = (_savegame_sort_order == SORT_BY_DATE) ?
526 SORT_BY_DATE | SORT_DESCENDING : SORT_BY_DATE;
527 _savegame_sort_dirty = true;
528 this->SetDirty();
529 break;
531 case WID_SL_HOME_BUTTON: // OpenTTD 'button', jumps to OpenTTD directory
532 FiosBrowseTo(&o_dir);
533 this->InvalidateData();
534 break;
536 case WID_SL_LOAD_BUTTON:
537 if (this->selected != nullptr && !_load_check_data.HasErrors()) {
538 const char *name = FiosBrowseTo(this->selected);
539 _file_to_saveload.SetMode(this->selected->type);
540 _file_to_saveload.SetName(name);
541 _file_to_saveload.SetTitle(this->selected->title);
543 if (this->abstract_filetype == FT_HEIGHTMAP) {
544 delete this;
545 ShowHeightmapLoad();
547 } else if (!_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility != GLC_NOT_FOUND || _settings_client.gui.UserIsAllowedToChangeNewGRFs()) {
548 _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD_GAME;
549 ClearErrorMessages();
550 delete this;
553 break;
555 case WID_SL_NEWGRF_INFO:
556 if (_load_check_data.HasNewGrfs()) {
557 ShowNewGRFSettings(false, false, false, &_load_check_data.grfconfig);
559 break;
561 case WID_SL_MISSING_NEWGRFS:
562 if (!_network_available) {
563 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
564 } else if (_load_check_data.HasNewGrfs()) {
565 #if defined(ENABLE_NETWORK)
566 ShowMissingContentWindow(_load_check_data.grfconfig);
567 #endif
569 break;
571 case WID_SL_DRIVES_DIRECTORIES_LIST: { // Click the listbox
572 int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WD_FRAMERECT_TOP);
573 if (y == INT_MAX) return;
575 const FiosItem *file = this->fios_items.Get(y);
577 const char *name = FiosBrowseTo(file);
578 if (name != nullptr) {
579 if (click_count == 1) {
580 if (this->selected != file) {
581 this->selected = file;
582 _load_check_data.Clear();
584 if (GetDetailedFileType(file->type) == DFT_GAME_FILE) {
585 /* Other detailed file types cannot be checked before. */
586 SaveOrLoad(name, SLO_CHECK, DFT_GAME_FILE, NO_DIRECTORY, false);
589 this->InvalidateData(1);
591 if (this->fop == SLO_SAVE) {
592 /* Copy clicked name to editbox */
593 this->filename_editbox.text.Assign(file->title);
594 this->SetWidgetDirty(WID_SL_SAVE_OSK_TITLE);
596 } else if (!_load_check_data.HasErrors()) {
597 this->selected = file;
598 if (this->fop == SLO_LOAD) {
599 if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) {
600 this->OnClick(pt, WID_SL_LOAD_BUTTON, 1);
601 } else {
602 assert(this->abstract_filetype == FT_HEIGHTMAP);
603 _file_to_saveload.SetMode(file->type);
604 _file_to_saveload.SetName(name);
605 _file_to_saveload.SetTitle(file->title);
607 delete this;
608 ShowHeightmapLoad();
612 } else {
613 /* Changed directory, need refresh. */
614 this->InvalidateData();
616 break;
619 case WID_SL_CONTENT_DOWNLOAD:
620 if (!_network_available) {
621 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
622 } else {
623 #if defined(ENABLE_NETWORK)
624 assert(this->fop == SLO_LOAD);
625 switch (this->abstract_filetype) {
626 default: NOT_REACHED();
627 case FT_SCENARIO: ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_SCENARIO); break;
628 case FT_HEIGHTMAP: ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_HEIGHTMAP); break;
630 #endif
632 break;
634 case WID_SL_DELETE_SELECTION: // Delete
635 break;
637 case WID_SL_SAVE_GAME: // Save game
638 /* Note, this is also called via the OSK; and we need to lower the button. */
639 this->HandleButtonClick(WID_SL_SAVE_GAME);
640 break;
644 virtual EventState OnKeyPress(WChar key, uint16 keycode)
646 if (keycode == WKC_ESC) {
647 delete this;
648 return ES_HANDLED;
651 return ES_NOT_HANDLED;
654 virtual void OnTimeout()
656 /* Widgets WID_SL_DELETE_SELECTION and WID_SL_SAVE_GAME only exist when saving to a file. */
657 if (this->fop != SLO_SAVE) return;
659 if (this->IsWidgetLowered(WID_SL_DELETE_SELECTION)) { // Delete button clicked
660 if (!FiosDelete(this->filename_editbox.text.buf)) {
661 ShowErrorMessage(STR_ERROR_UNABLE_TO_DELETE_FILE, INVALID_STRING_ID, WL_ERROR);
662 } else {
663 this->InvalidateData();
664 /* Reset file name to current date on successful delete */
665 if (this->abstract_filetype == FT_SAVEGAME) GenerateFileName();
667 } else if (this->IsWidgetLowered(WID_SL_SAVE_GAME)) { // Save button clicked
668 if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) {
669 _switch_mode = SM_SAVE_GAME;
670 FiosMakeSavegameName(_file_to_saveload.name, this->filename_editbox.text.buf, lastof(_file_to_saveload.name));
671 } else {
672 _switch_mode = SM_SAVE_HEIGHTMAP;
673 FiosMakeHeightmapName(_file_to_saveload.name, this->filename_editbox.text.buf, lastof(_file_to_saveload.name));
676 /* In the editor set up the vehicle engines correctly (date might have changed) */
677 if (_game_mode == GM_EDITOR) StartupEngines();
681 virtual void OnResize()
683 this->vscroll->SetCapacityFromWidget(this, WID_SL_DRIVES_DIRECTORIES_LIST);
687 * Some data on this window has become invalid.
688 * @param data Information about the changed data.
689 * @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.
691 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
693 switch (data) {
694 case 0:
695 /* Rescan files */
696 this->selected = nullptr;
697 _load_check_data.Clear();
698 if (!gui_scope) break;
700 _fios_path_changed = true;
701 this->fios_items.BuildFileList(this->abstract_filetype, this->fop);
702 this->vscroll->SetCount(this->fios_items.Length());
703 this->selected = nullptr;
704 _load_check_data.Clear();
705 FALLTHROUGH;
707 case 1:
708 /* Selection changes */
709 if (!gui_scope) break;
711 if (this->fop != SLO_LOAD) break;
713 switch (this->abstract_filetype) {
714 case FT_HEIGHTMAP:
715 this->SetWidgetDisabledState(WID_SL_LOAD_BUTTON, this->selected == nullptr || _load_check_data.HasErrors());
716 break;
718 case FT_SAVEGAME:
719 case FT_SCENARIO: {
720 bool disabled = this->selected == nullptr || _load_check_data.HasErrors();
721 if (!_settings_client.gui.UserIsAllowedToChangeNewGRFs()) {
722 disabled |= _load_check_data.HasNewGrfs() && _load_check_data.grf_compatibility == GLC_NOT_FOUND;
724 this->SetWidgetDisabledState(WID_SL_LOAD_BUTTON, disabled);
725 this->SetWidgetDisabledState(WID_SL_NEWGRF_INFO, !_load_check_data.HasNewGrfs());
726 this->SetWidgetDisabledState(WID_SL_MISSING_NEWGRFS,
727 !_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility == GLC_ALL_GOOD);
728 break;
731 default:
732 NOT_REACHED();
734 break;
739 /** Load game/scenario */
740 static WindowDesc _load_dialog_desc(
741 WDP_CENTER, "load_game", 500, 294,
742 WC_SAVELOAD, WC_NONE,
744 _nested_load_dialog_widgets, lengthof(_nested_load_dialog_widgets)
747 /** Load heightmap */
748 static WindowDesc _load_heightmap_dialog_desc(
749 WDP_CENTER, "load_heightmap", 257, 320,
750 WC_SAVELOAD, WC_NONE,
752 _nested_load_heightmap_dialog_widgets, lengthof(_nested_load_heightmap_dialog_widgets)
755 /** Save game/scenario */
756 static WindowDesc _save_dialog_desc(
757 WDP_CENTER, "save_game", 500, 294,
758 WC_SAVELOAD, WC_NONE,
760 _nested_save_dialog_widgets, lengthof(_nested_save_dialog_widgets)
764 * Launch save/load dialog in the given mode.
765 * @param abstract_filetype Kind of file to handle.
766 * @param fop File operation to perform (load or save).
768 void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop)
770 DeleteWindowById(WC_SAVELOAD, 0);
772 WindowDesc *sld;
773 if (fop == SLO_SAVE) {
774 sld = &_save_dialog_desc;
775 } else {
776 /* Dialogue for loading a file. */
777 sld = (abstract_filetype == FT_HEIGHTMAP) ? &_load_heightmap_dialog_desc : &_load_dialog_desc;
780 _file_to_saveload.abstract_ftype = abstract_filetype;
782 new SaveLoadWindow(sld, abstract_filetype, fop);