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  */
7 
8 /** @file settings_gui.cpp GUI for settings. */
9 
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "error.h"
13 #include "settings_gui.h"
14 #include "textbuf_gui.h"
15 #include "command_func.h"
16 #include "network/network.h"
17 #include "town.h"
18 #include "settings_internal.h"
19 #include "strings_func.h"
20 #include "window_func.h"
21 #include "string_func.h"
22 #include "widgets/dropdown_type.h"
23 #include "widgets/dropdown_func.h"
24 #include "widgets/slider_func.h"
25 #include "highscore.h"
26 #include "base_media_base.h"
27 #include "company_base.h"
28 #include "company_func.h"
29 #include "viewport_func.h"
30 #include "core/geometry_func.hpp"
31 #include "ai/ai.hpp"
32 #include "blitter/factory.hpp"
33 #include "language.h"
34 #include "textfile_gui.h"
35 #include "stringfilter_type.h"
36 #include "querystring_gui.h"
37 #include "fontcache.h"
38 #include "zoom_func.h"
39 #include "rev.h"
40 #include "video/video_driver.hpp"
41 #include "music/music_driver.hpp"
42 
43 #include <vector>
44 #include <iterator>
45 
46 #include "safeguards.h"
47 #include "video/video_driver.hpp"
48 
49 
50 static const StringID _autosave_dropdown[] = {
51 	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
52 	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
53 	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
54 	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
55 	STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
56 	INVALID_STRING_ID,
57 };
58 
59 static const StringID _gui_zoom_dropdown[] = {
60 	STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_AUTO,
61 	STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL,
62 	STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM,
63 	STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM,
64 	INVALID_STRING_ID,
65 };
66 
67 static const StringID _font_zoom_dropdown[] = {
68 	STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_AUTO,
69 	STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_NORMAL,
70 	STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_2X_ZOOM,
71 	STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_4X_ZOOM,
72 	INVALID_STRING_ID,
73 };
74 
75 static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
76 
77 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd);
78 
79 /**
80  * Get index of the current screen resolution.
81  * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
82  */
GetCurrentResolutionIndex()83 static uint GetCurrentResolutionIndex()
84 {
85 	auto it = std::find(_resolutions.begin(), _resolutions.end(), Dimension(_screen.width, _screen.height));
86 	return std::distance(_resolutions.begin(), it);
87 }
88 
89 static void ShowCustCurrency();
90 
91 template <class T>
BuildSetDropDownList(int * selected_index,bool allow_selection)92 static DropDownList BuildSetDropDownList(int *selected_index, bool allow_selection)
93 {
94 	int n = T::GetNumSets();
95 	*selected_index = T::GetIndexOfUsedSet();
96 
97 	DropDownList list;
98 	for (int i = 0; i < n; i++) {
99 		list.emplace_back(new DropDownListCharStringItem(T::GetSet(i)->name, i, !allow_selection && (*selected_index != i)));
100 	}
101 
102 	return list;
103 }
104 
BuildMusicSetDropDownList(int * selected_index)105 DropDownList BuildMusicSetDropDownList(int *selected_index)
106 {
107 	return BuildSetDropDownList<BaseMusic>(selected_index, true);
108 }
109 
110 /** Window for displaying the textfile of a BaseSet. */
111 template <class TBaseSet>
112 struct BaseSetTextfileWindow : public TextfileWindow {
113 	const TBaseSet* baseset; ///< View the textfile of this BaseSet.
114 	StringID content_type;   ///< STR_CONTENT_TYPE_xxx for title.
115 
BaseSetTextfileWindowBaseSetTextfileWindow116 	BaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
117 	{
118 		const char *textfile = this->baseset->GetTextfile(file_type);
119 		this->LoadTextfile(textfile, BASESET_DIR);
120 	}
121 
SetStringParametersBaseSetTextfileWindow122 	void SetStringParameters(int widget) const override
123 	{
124 		if (widget == WID_TF_CAPTION) {
125 			SetDParam(0, content_type);
126 			SetDParamStr(1, this->baseset->name);
127 		}
128 	}
129 };
130 
131 /**
132  * Open the BaseSet version of the textfile window.
133  * @param file_type The type of textfile to display.
134  * @param baseset The BaseSet to use.
135  * @param content_type STR_CONTENT_TYPE_xxx for title.
136  */
137 template <class TBaseSet>
ShowBaseSetTextfileWindow(TextfileType file_type,const TBaseSet * baseset,StringID content_type)138 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type)
139 {
140 	CloseWindowById(WC_TEXTFILE, file_type);
141 	new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
142 }
143 
144 std::set<int> _refresh_rates = { 30, 60, 75, 90, 100, 120, 144, 240 };
145 
146 /**
147  * Add the refresh rate from the config and the refresh rates from all the monitors to
148  * our list of refresh rates shown in the GUI.
149  */
AddCustomRefreshRates()150 static void AddCustomRefreshRates()
151 {
152 	/* Add the refresh rate as selected in the config. */
153 	_refresh_rates.insert(_settings_client.gui.refresh_rate);
154 
155 	/* Add all the refresh rates of all monitors connected to the machine.  */
156 	std::vector<int> monitorRates = VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
157 	std::copy(monitorRates.begin(), monitorRates.end(), std::inserter(_refresh_rates, _refresh_rates.end()));
158 }
159 
160 struct GameOptionsWindow : Window {
161 	GameSettings *opt;
162 	bool reload;
163 
GameOptionsWindowGameOptionsWindow164 	GameOptionsWindow(WindowDesc *desc) : Window(desc)
165 	{
166 		this->opt = &GetGameSettings();
167 		this->reload = false;
168 
169 		AddCustomRefreshRates();
170 
171 		this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS);
172 		this->OnInvalidateData(0);
173 	}
174 
CloseGameOptionsWindow175 	void Close() override
176 	{
177 		CloseWindowById(WC_CUSTOM_CURRENCY, 0);
178 		CloseWindowByClass(WC_TEXTFILE);
179 		if (this->reload) _switch_mode = SM_MENU;
180 		this->Window::Close();
181 	}
182 
183 	/**
184 	 * Build the dropdown list for a specific widget.
185 	 * @param widget         Widget to build list for
186 	 * @param selected_index Currently selected item
187 	 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
188 	 */
BuildDropDownListGameOptionsWindow189 	DropDownList BuildDropDownList(int widget, int *selected_index) const
190 	{
191 		DropDownList list;
192 		switch (widget) {
193 			case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
194 				*selected_index = this->opt->locale.currency;
195 				StringID *items = BuildCurrencyDropdown();
196 				uint64 disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
197 
198 				/* Add non-custom currencies; sorted naturally */
199 				for (uint i = 0; i < CURRENCY_END; items++, i++) {
200 					if (i == CURRENCY_CUSTOM) continue;
201 					list.emplace_back(new DropDownListStringItem(*items, i, HasBit(disabled, i)));
202 				}
203 				std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
204 
205 				/* Append custom currency at the end */
206 				list.emplace_back(new DropDownListItem(-1, false)); // separator line
207 				list.emplace_back(new DropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
208 				break;
209 			}
210 
211 			case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
212 				*selected_index = _settings_client.gui.autosave;
213 				const StringID *items = _autosave_dropdown;
214 				for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
215 					list.emplace_back(new DropDownListStringItem(*items, i, false));
216 				}
217 				break;
218 			}
219 
220 			case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
221 				for (uint i = 0; i < _languages.size(); i++) {
222 					bool hide_language = IsReleasedVersion() && !_languages[i].IsReasonablyFinished();
223 					if (hide_language) continue;
224 					bool hide_percentage = IsReleasedVersion() || _languages[i].missing < _settings_client.gui.missing_strings_threshold;
225 					auto item = new DropDownListParamStringItem(hide_percentage ? STR_JUST_RAW_STRING : STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE, i, false);
226 					if (&_languages[i] == _current_language) {
227 						*selected_index = i;
228 						item->SetParamStr(0, _languages[i].own_name);
229 					} else {
230 						/* Especially with sprite-fonts, not all localized
231 						 * names can be rendered. So instead, we use the
232 						 * international names for anything but the current
233 						 * selected language. This avoids showing a few ????
234 						 * entries in the dropdown list. */
235 						item->SetParamStr(0, _languages[i].name);
236 					}
237 					item->SetParam(1, (LANGUAGE_TOTAL_STRINGS - _languages[i].missing) * 100 / LANGUAGE_TOTAL_STRINGS);
238 					list.emplace_back(item);
239 				}
240 				std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
241 				break;
242 			}
243 
244 			case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
245 				if (_resolutions.empty()) break;
246 
247 				*selected_index = GetCurrentResolutionIndex();
248 				for (uint i = 0; i < _resolutions.size(); i++) {
249 					auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_RESOLUTION_ITEM, i, false);
250 					item->SetParam(0, _resolutions[i].width);
251 					item->SetParam(1, _resolutions[i].height);
252 					list.emplace_back(item);
253 				}
254 				break;
255 
256 			case WID_GO_REFRESH_RATE_DROPDOWN: // Setup refresh rate dropdown
257 				for (auto it = _refresh_rates.begin(); it != _refresh_rates.end(); it++) {
258 					auto i = std::distance(_refresh_rates.begin(), it);
259 					if (*it == _settings_client.gui.refresh_rate) *selected_index = i;
260 					auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, i, false);
261 					item->SetParam(0, *it);
262 					list.emplace_back(item);
263 				}
264 				break;
265 
266 			case WID_GO_GUI_ZOOM_DROPDOWN: {
267 				*selected_index = _gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom + 1 : 0;
268 				const StringID *items = _gui_zoom_dropdown;
269 				for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
270 					list.emplace_back(new DropDownListStringItem(*items, i, i != 0 && _settings_client.gui.zoom_min > ZOOM_LVL_OUT_4X - i + 1));
271 				}
272 				break;
273 			}
274 
275 			case WID_GO_FONT_ZOOM_DROPDOWN: {
276 				*selected_index = _font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom + 1 : 0;
277 				const StringID *items = _font_zoom_dropdown;
278 				for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
279 					list.emplace_back(new DropDownListStringItem(*items, i, false));
280 				}
281 				break;
282 			}
283 
284 			case WID_GO_BASE_GRF_DROPDOWN:
285 				list = BuildSetDropDownList<BaseGraphics>(selected_index, (_game_mode == GM_MENU));
286 				break;
287 
288 			case WID_GO_BASE_SFX_DROPDOWN:
289 				list = BuildSetDropDownList<BaseSounds>(selected_index, (_game_mode == GM_MENU));
290 				break;
291 
292 			case WID_GO_BASE_MUSIC_DROPDOWN:
293 				list = BuildMusicSetDropDownList(selected_index);
294 				break;
295 		}
296 
297 		return list;
298 	}
299 
SetStringParametersGameOptionsWindow300 	void SetStringParameters(int widget) const override
301 	{
302 		switch (widget) {
303 			case WID_GO_CURRENCY_DROPDOWN:     SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
304 			case WID_GO_AUTOSAVE_DROPDOWN:     SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
305 			case WID_GO_LANG_DROPDOWN:         SetDParamStr(0, _current_language->own_name); break;
306 			case WID_GO_GUI_ZOOM_DROPDOWN:     SetDParam(0, _gui_zoom_dropdown[_gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom_cfg + 1 : 0]); break;
307 			case WID_GO_FONT_ZOOM_DROPDOWN:    SetDParam(0, _font_zoom_dropdown[_font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom_cfg + 1 : 0]); break;
308 			case WID_GO_BASE_GRF_DROPDOWN:     SetDParamStr(0, BaseGraphics::GetUsedSet()->name); break;
309 			case WID_GO_BASE_GRF_STATUS:       SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
310 			case WID_GO_BASE_SFX_DROPDOWN:     SetDParamStr(0, BaseSounds::GetUsedSet()->name); break;
311 			case WID_GO_BASE_MUSIC_DROPDOWN:   SetDParamStr(0, BaseMusic::GetUsedSet()->name); break;
312 			case WID_GO_BASE_MUSIC_STATUS:     SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
313 			case WID_GO_REFRESH_RATE_DROPDOWN: SetDParam(0, _settings_client.gui.refresh_rate); break;
314 			case WID_GO_RESOLUTION_DROPDOWN: {
315 				auto current_resolution = GetCurrentResolutionIndex();
316 
317 				if (current_resolution == _resolutions.size()) {
318 					SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER);
319 				} else {
320 					SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM);
321 					SetDParam(1, _resolutions[current_resolution].width);
322 					SetDParam(2, _resolutions[current_resolution].height);
323 				}
324 				break;
325 			}
326 		}
327 	}
328 
DrawWidgetGameOptionsWindow329 	void DrawWidget(const Rect &r, int widget) const override
330 	{
331 		switch (widget) {
332 			case WID_GO_BASE_GRF_DESCRIPTION:
333 				SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
334 				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
335 				break;
336 
337 			case WID_GO_BASE_SFX_DESCRIPTION:
338 				SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
339 				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
340 				break;
341 
342 			case WID_GO_BASE_MUSIC_DESCRIPTION:
343 				SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
344 				DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
345 				break;
346 
347 			case WID_GO_BASE_SFX_VOLUME:
348 				DrawVolumeSliderWidget(r, _settings_client.music.effect_vol);
349 				break;
350 
351 			case WID_GO_BASE_MUSIC_VOLUME:
352 				DrawVolumeSliderWidget(r, _settings_client.music.music_vol);
353 				break;
354 		}
355 	}
356 
UpdateWidgetSizeGameOptionsWindow357 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
358 	{
359 		switch (widget) {
360 			case WID_GO_BASE_GRF_DESCRIPTION:
361 				/* Find the biggest description for the default size. */
362 				for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
363 					SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
364 					size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
365 				}
366 				break;
367 
368 			case WID_GO_BASE_GRF_STATUS:
369 				/* Find the biggest description for the default size. */
370 				for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
371 					uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
372 					if (invalid_files == 0) continue;
373 
374 					SetDParam(0, invalid_files);
375 					*size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
376 				}
377 				break;
378 
379 			case WID_GO_BASE_SFX_DESCRIPTION:
380 				/* Find the biggest description for the default size. */
381 				for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
382 					SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
383 					size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
384 				}
385 				break;
386 
387 			case WID_GO_BASE_MUSIC_DESCRIPTION:
388 				/* Find the biggest description for the default size. */
389 				for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
390 					SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
391 					size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
392 				}
393 				break;
394 
395 			case WID_GO_BASE_MUSIC_STATUS:
396 				/* Find the biggest description for the default size. */
397 				for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
398 					uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
399 					if (invalid_files == 0) continue;
400 
401 					SetDParam(0, invalid_files);
402 					*size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
403 				}
404 				break;
405 
406 			default: {
407 				int selected;
408 				DropDownList list = this->BuildDropDownList(widget, &selected);
409 				if (!list.empty()) {
410 					/* Find the biggest item for the default size. */
411 					for (const auto &ddli : list) {
412 						Dimension string_dim;
413 						int width = ddli->Width();
414 						string_dim.width = width + padding.width;
415 						string_dim.height = ddli->Height(width) + padding.height;
416 						*size = maxdim(*size, string_dim);
417 					}
418 				}
419 			}
420 		}
421 	}
422 
OnClickGameOptionsWindow423 	void OnClick(Point pt, int widget, int click_count) override
424 	{
425 		if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
426 			if (BaseGraphics::GetUsedSet() == nullptr) return;
427 
428 			ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
429 			return;
430 		}
431 		if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
432 			if (BaseSounds::GetUsedSet() == nullptr) return;
433 
434 			ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
435 			return;
436 		}
437 		if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
438 			if (BaseMusic::GetUsedSet() == nullptr) return;
439 
440 			ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
441 			return;
442 		}
443 		switch (widget) {
444 			case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
445 				/* try to toggle full-screen on/off */
446 				if (!ToggleFullScreen(!_fullscreen)) {
447 					ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
448 				}
449 				this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
450 				this->SetWidgetDirty(WID_GO_FULLSCREEN_BUTTON);
451 				break;
452 
453 			case WID_GO_VIDEO_ACCEL_BUTTON:
454 				_video_hw_accel = !_video_hw_accel;
455 				ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART, INVALID_STRING_ID, WL_INFO);
456 				this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
457 				this->SetWidgetDirty(WID_GO_VIDEO_ACCEL_BUTTON);
458 #ifndef __APPLE__
459 				this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
460 				this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
461 #endif
462 				break;
463 
464 			case WID_GO_VIDEO_VSYNC_BUTTON:
465 				if (!_video_hw_accel) break;
466 
467 				_video_vsync = !_video_vsync;
468 				VideoDriver::GetInstance()->ToggleVsync(_video_vsync);
469 
470 				this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
471 				this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
472 				this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
473 				this->SetWidgetDirty(WID_GO_REFRESH_RATE_DROPDOWN);
474 				break;
475 
476 			case WID_GO_BASE_SFX_VOLUME:
477 			case WID_GO_BASE_MUSIC_VOLUME: {
478 				byte &vol = (widget == WID_GO_BASE_MUSIC_VOLUME) ? _settings_client.music.music_vol : _settings_client.music.effect_vol;
479 				if (ClickVolumeSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, vol)) {
480 					if (widget == WID_GO_BASE_MUSIC_VOLUME) MusicDriver::GetInstance()->SetVolume(vol);
481 					this->SetWidgetDirty(widget);
482 					SetWindowClassesDirty(WC_MUSIC_WINDOW);
483 				}
484 
485 				if (click_count > 0) this->mouse_capture_widget = widget;
486 				break;
487 			}
488 
489 			default: {
490 				int selected;
491 				DropDownList list = this->BuildDropDownList(widget, &selected);
492 				if (!list.empty()) {
493 					ShowDropDownList(this, std::move(list), selected, widget);
494 				} else {
495 					if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
496 				}
497 				break;
498 			}
499 		}
500 	}
501 
502 	/**
503 	 * Set the base media set.
504 	 * @param index the index of the media set
505 	 * @tparam T class of media set
506 	 */
507 	template <class T>
SetMediaSetGameOptionsWindow508 	void SetMediaSet(int index)
509 	{
510 		if (_game_mode == GM_MENU) {
511 			auto name = T::GetSet(index)->name;
512 
513 			T::ini_set = name;
514 
515 			T::SetSet(name);
516 			this->reload = true;
517 			this->InvalidateData();
518 		}
519 	}
520 
OnDropdownSelectGameOptionsWindow521 	void OnDropdownSelect(int widget, int index) override
522 	{
523 		switch (widget) {
524 			case WID_GO_CURRENCY_DROPDOWN: // Currency
525 				if (index == CURRENCY_CUSTOM) ShowCustCurrency();
526 				this->opt->locale.currency = index;
527 				ReInitAllWindows(false);
528 				break;
529 
530 			case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
531 				_settings_client.gui.autosave = index;
532 				this->SetDirty();
533 				break;
534 
535 			case WID_GO_LANG_DROPDOWN: // Change interface language
536 				ReadLanguagePack(&_languages[index]);
537 				CloseWindowByClass(WC_QUERY_STRING);
538 				CheckForMissingGlyphs();
539 				ClearAllCachedNames();
540 				UpdateAllVirtCoords();
541 				CheckBlitter();
542 				ReInitAllWindows(false);
543 				break;
544 
545 			case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
546 				if ((uint)index < _resolutions.size() && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
547 					this->SetDirty();
548 				}
549 				break;
550 
551 			case WID_GO_REFRESH_RATE_DROPDOWN: {
552 				_settings_client.gui.refresh_rate = *std::next(_refresh_rates.begin(), index);
553 				if (_settings_client.gui.refresh_rate > 60) {
554 					/* Show warning to the user that this refresh rate might not be suitable on
555 					 * larger maps with many NewGRFs and vehicles. */
556 					ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING, INVALID_STRING_ID, WL_INFO);
557 				}
558 				break;
559 			}
560 
561 			case WID_GO_GUI_ZOOM_DROPDOWN: {
562 				int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
563 				if (new_zoom != _gui_zoom_cfg) {
564 					GfxClearSpriteCache();
565 					_gui_zoom_cfg = new_zoom;
566 					UpdateGUIZoom();
567 					UpdateCursorSize();
568 					UpdateAllVirtCoords();
569 					FixTitleGameZoom();
570 					ReInitAllWindows(true);
571 				}
572 				break;
573 			}
574 
575 			case WID_GO_FONT_ZOOM_DROPDOWN: {
576 				int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
577 				if (new_zoom != _font_zoom_cfg) {
578 					GfxClearSpriteCache();
579 					_font_zoom_cfg = new_zoom;
580 					UpdateGUIZoom();
581 					ClearFontCache();
582 					LoadStringWidthTable();
583 					UpdateAllVirtCoords();
584 					ReInitAllWindows(true);
585 				}
586 				break;
587 			}
588 
589 			case WID_GO_BASE_GRF_DROPDOWN:
590 				this->SetMediaSet<BaseGraphics>(index);
591 				break;
592 
593 			case WID_GO_BASE_SFX_DROPDOWN:
594 				this->SetMediaSet<BaseSounds>(index);
595 				break;
596 
597 			case WID_GO_BASE_MUSIC_DROPDOWN:
598 				ChangeMusicSet(index);
599 				break;
600 		}
601 	}
602 
603 	/**
604 	 * Some data on this window has become invalid.
605 	 * @param data Information about the changed data. @see GameOptionsInvalidationData
606 	 * @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.
607 	 */
OnInvalidateDataGameOptionsWindow608 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
609 	{
610 		if (!gui_scope) return;
611 		this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
612 		this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
613 		this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
614 
615 #ifndef __APPLE__
616 		this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
617 		this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
618 #endif
619 
620 		bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
621 		this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
622 
623 		for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
624 			this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->GetTextfile(tft) == nullptr);
625 			this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->GetTextfile(tft) == nullptr);
626 			this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->GetTextfile(tft) == nullptr);
627 		}
628 
629 		missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
630 		this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
631 	}
632 };
633 
634 static const NWidgetPart _nested_game_options_widgets[] = {
635 	NWidget(NWID_HORIZONTAL),
636 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
637 		NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
638 	EndContainer(),
639 	NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
640 		NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
641 			NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
642 				NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
643 					NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_AUTOSAVE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP), SetFill(1, 0),
644 				EndContainer(),
645 				NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_ZOOM_FRAME, STR_NULL),
646 					NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_GUI_ZOOM_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_TOOLTIP), SetFill(1, 0),
647 				EndContainer(),
648 				NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
649 					NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_CURRENCY_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0),
650 				EndContainer(),
651 			EndContainer(),
652 
653 			NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
654 				NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
655 					NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_LANG_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_LANGUAGE_TOOLTIP), SetFill(1, 0),
656 				EndContainer(),
657 				NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_FONT_ZOOM, STR_NULL),
658 					NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_FONT_ZOOM_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_TOOLTIP), SetFill(1, 0),
659 				EndContainer(),
660 			EndContainer(),
661 		EndContainer(),
662 
663 		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GRAPHICS, STR_NULL), SetPadding(0, 10, 0, 10),
664 			NWidget(NWID_HORIZONTAL),
665 				NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
666 					NWidget(NWID_HORIZONTAL),
667 						NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12),SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
668 						NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
669 						NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(200, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP),
670 					EndContainer(),
671 					NWidget(NWID_HORIZONTAL),
672 						NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE, STR_NULL),
673 						NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
674 						NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_REFRESH_RATE_DROPDOWN), SetMinimalSize(200, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, STR_GAME_OPTIONS_REFRESH_RATE_TOOLTIP),
675 					EndContainer(),
676 					NWidget(NWID_HORIZONTAL),
677 						NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
678 						NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
679 						NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
680 					EndContainer(),
681 					NWidget(NWID_HORIZONTAL),
682 						NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION, STR_NULL),
683 						NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
684 						NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_ACCEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP),
685 					EndContainer(),
686 #ifndef __APPLE__
687 					NWidget(NWID_HORIZONTAL),
688 						NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_VSYNC, STR_NULL),
689 						NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
690 						NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_VSYNC_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP),
691 					EndContainer(),
692 #endif
693 				EndContainer(),
694 			EndContainer(),
695 		EndContainer(),
696 
697 		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
698 			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
699 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
700 				NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
701 			EndContainer(),
702 			NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_GRF_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 6, 0),
703 			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
704 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
705 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
706 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
707 			EndContainer(),
708 		EndContainer(),
709 
710 		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
711 			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
712 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
713 				NWidget(NWID_SPACER), SetMinimalSize(150, 12), SetFill(1, 0),
714 				NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_SFX_VOLUME), SetMinimalSize(67, 12), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
715 			EndContainer(),
716 			NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_SFX_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 6, 0),
717 			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
718 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
719 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
720 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
721 			EndContainer(),
722 		EndContainer(),
723 
724 		NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
725 			NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
726 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
727 				NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
728 				NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_MUSIC_VOLUME), SetMinimalSize(67, 12), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
729 			EndContainer(),
730 			NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_DESCRIPTION), SetMinimalSize(330, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_MUSIC_DESCRIPTION_TOOLTIP), SetFill(1, 0), SetPadding(6, 0, 6, 0),
731 			NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
732 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
733 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
734 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
735 			EndContainer(),
736 		EndContainer(),
737 	EndContainer(),
738 };
739 
740 static WindowDesc _game_options_desc(
741 	WDP_CENTER, "settings_game", 0, 0,
742 	WC_GAME_OPTIONS, WC_NONE,
743 	0,
744 	_nested_game_options_widgets, lengthof(_nested_game_options_widgets)
745 );
746 
747 /** Open the game options window. */
ShowGameOptions()748 void ShowGameOptions()
749 {
750 	CloseWindowByClass(WC_GAME_OPTIONS);
751 	new GameOptionsWindow(&_game_options_desc);
752 }
753 
754 static int SETTING_HEIGHT = 11;    ///< Height of a single setting in the tree view in pixels
755 static const int LEVEL_WIDTH = 15; ///< Indenting width of a sub-page in pixels
756 
757 /**
758  * Flags for #SettingEntry
759  * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
760  */
761 enum SettingEntryFlags {
762 	SEF_LEFT_DEPRESSED  = 0x01, ///< Of a numeric setting entry, the left button is depressed
763 	SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
764 	SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
765 
766 	SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
767 	SEF_FILTERED   = 0x08, ///< Entry is hidden by the string filter
768 };
769 
770 /** How the list of advanced settings is filtered. */
771 enum RestrictionMode {
772 	RM_BASIC,                            ///< Display settings associated to the "basic" list.
773 	RM_ADVANCED,                         ///< Display settings associated to the "advanced" list.
774 	RM_ALL,                              ///< List all settings regardless of the default/newgame/... values.
775 	RM_CHANGED_AGAINST_DEFAULT,          ///< Show only settings which are different compared to default values.
776 	RM_CHANGED_AGAINST_NEW,              ///< Show only settings which are different compared to the user's new game setting values.
777 	RM_END,                              ///< End for iteration.
778 };
779 DECLARE_POSTFIX_INCREMENT(RestrictionMode)
780 
781 /** Filter for settings list. */
782 struct SettingFilter {
783 	StringFilter string;     ///< Filter string.
784 	RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
785 	bool type_hides;         ///< Whether the type hides filtered strings.
786 	RestrictionMode mode;    ///< Filter based on category.
787 	SettingType type;        ///< Filter based on type.
788 };
789 
790 /** Data structure describing a single setting in a tab */
791 struct BaseSettingEntry {
792 	byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
793 	byte level; ///< Nesting level of this setting entry
794 
BaseSettingEntryBaseSettingEntry795 	BaseSettingEntry() : flags(0), level(0) {}
~BaseSettingEntryBaseSettingEntry796 	virtual ~BaseSettingEntry() {}
797 
798 	virtual void Init(byte level = 0);
FoldAllBaseSettingEntry799 	virtual void FoldAll() {}
UnFoldAllBaseSettingEntry800 	virtual void UnFoldAll() {}
801 	virtual void ResetAll() = 0;
802 
803 	/**
804 	 * Set whether this is the last visible entry of the parent node.
805 	 * @param last_field Value to set
806 	 */
SetLastFieldBaseSettingEntry807 	void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
808 
809 	virtual uint Length() const = 0;
GetFoldingStateBaseSettingEntry810 	virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
811 	virtual bool IsVisible(const BaseSettingEntry *item) const;
812 	virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
GetMaxHelpHeightBaseSettingEntry813 	virtual uint GetMaxHelpHeight(int maxw) { return 0; }
814 
815 	/**
816 	 * Check whether an entry is hidden due to filters
817 	 * @return true if hidden.
818 	 */
IsFilteredBaseSettingEntry819 	bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
820 
821 	virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
822 
823 	virtual uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
824 
825 protected:
826 	virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
827 };
828 
829 /** Standard setting */
830 struct SettingEntry : BaseSettingEntry {
831 	const char *name;              ///< Name of the setting
832 	const IntSettingDesc *setting; ///< Setting description of the setting
833 
834 	SettingEntry(const char *name);
835 
836 	virtual void Init(byte level = 0);
837 	virtual void ResetAll();
838 	virtual uint Length() const;
839 	virtual uint GetMaxHelpHeight(int maxw);
840 	virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
841 
842 	void SetButtons(byte new_val);
843 
844 	/**
845 	 * Get the help text of a single setting.
846 	 * @return The requested help text.
847 	 */
GetHelpTextSettingEntry848 	inline StringID GetHelpText() const
849 	{
850 		return this->setting->str_help;
851 	}
852 
853 	void SetValueDParams(uint first_param, int32 value) const;
854 
855 protected:
856 	virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
857 
858 private:
859 	bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
860 };
861 
862 /** Containers for BaseSettingEntry */
863 struct SettingsContainer {
864 	typedef std::vector<BaseSettingEntry*> EntryVector;
865 	EntryVector entries; ///< Settings on this page
866 
867 	template<typename T>
AddSettingsContainer868 	T *Add(T *item)
869 	{
870 		this->entries.push_back(item);
871 		return item;
872 	}
873 
874 	void Init(byte level = 0);
875 	void ResetAll();
876 	void FoldAll();
877 	void UnFoldAll();
878 
879 	uint Length() const;
880 	void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
881 	bool IsVisible(const BaseSettingEntry *item) const;
882 	BaseSettingEntry *FindEntry(uint row, uint *cur_row);
883 	uint GetMaxHelpHeight(int maxw);
884 
885 	bool UpdateFilterState(SettingFilter &filter, bool force_visible);
886 
887 	uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
888 };
889 
890 /** Data structure describing one page of settings in the settings window. */
891 struct SettingsPage : BaseSettingEntry, SettingsContainer {
892 	StringID title;     ///< Title of the sub-page
893 	bool folded;        ///< Sub-page is folded (not visible except for its title)
894 
895 	SettingsPage(StringID title);
896 
897 	virtual void Init(byte level = 0);
898 	virtual void ResetAll();
899 	virtual void FoldAll();
900 	virtual void UnFoldAll();
901 
902 	virtual uint Length() const;
903 	virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
904 	virtual bool IsVisible(const BaseSettingEntry *item) const;
905 	virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
GetMaxHelpHeightSettingsPage906 	virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
907 
908 	virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
909 
910 	virtual uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
911 
912 protected:
913 	virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
914 };
915 
916 /* == BaseSettingEntry methods == */
917 
918 /**
919  * Initialization of a setting entry
920  * @param level      Page nesting level of this entry
921  */
Init(byte level)922 void BaseSettingEntry::Init(byte level)
923 {
924 	this->level = level;
925 }
926 
927 /**
928  * Check whether an entry is visible and not folded or filtered away.
929  * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
930  * @param item Entry to search for.
931  * @return true if entry is visible.
932  */
IsVisible(const BaseSettingEntry * item) const933 bool BaseSettingEntry::IsVisible(const BaseSettingEntry *item) const
934 {
935 	if (this->IsFiltered()) return false;
936 	return this == item;
937 }
938 
939 /**
940  * Find setting entry at row \a row_num
941  * @param row_num Index of entry to return
942  * @param cur_row Current row number
943  * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
944  */
FindEntry(uint row_num,uint * cur_row)945 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
946 {
947 	if (this->IsFiltered()) return nullptr;
948 	if (row_num == *cur_row) return this;
949 	(*cur_row)++;
950 	return nullptr;
951 }
952 
953 /**
954  * Draw a row in the settings panel.
955  *
956  * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
957  * As a result, the drawing routing traverses the tree from top to bottom, counting rows in \a cur_row until it reaches \a first_row.
958  * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
959  *
960  * The \a parent_last parameter ensures that the vertical lines at the left are
961  * only drawn when another entry follows, that it prevents output like
962  * \verbatim
963  *  |-- setting
964  *  |-- (-) - Title
965  *  |    |-- setting
966  *  |    |-- setting
967  * \endverbatim
968  * The left-most vertical line is not wanted. It is prevented by setting the
969  * appropriate bit in the \a parent_last parameter.
970  *
971  * @param settings_ptr Pointer to current values of all settings
972  * @param left         Left-most position in window/panel to start drawing \a first_row
973  * @param right        Right-most x position to draw strings at.
974  * @param y            Upper-most position in window/panel to start drawing \a first_row
975  * @param first_row    First row number to draw
976  * @param max_row      Row-number to stop drawing (the row-number of the row below the last row to draw)
977  * @param selected     Selected entry by the user.
978  * @param cur_row      Current row number (internal variable)
979  * @param parent_last  Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
980  * @return Row number of the next row to draw
981  */
Draw(GameSettings * settings_ptr,int left,int right,int y,uint first_row,uint max_row,BaseSettingEntry * selected,uint cur_row,uint parent_last) const982 uint BaseSettingEntry::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
983 {
984 	if (this->IsFiltered()) return cur_row;
985 	if (cur_row >= max_row) return cur_row;
986 
987 	bool rtl = _current_text_dir == TD_RTL;
988 	int offset = rtl ? -4 : 4;
989 	int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
990 
991 	int x = rtl ? right : left;
992 	if (cur_row >= first_row) {
993 		int colour = _colour_gradient[COLOUR_ORANGE][4];
994 		y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
995 
996 		/* Draw vertical for parent nesting levels */
997 		for (uint lvl = 0; lvl < this->level; lvl++) {
998 			if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
999 			x += level_width;
1000 		}
1001 		/* draw own |- prefix */
1002 		int halfway_y = y + SETTING_HEIGHT / 2;
1003 		int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
1004 		GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
1005 		/* Small horizontal line from the last vertical line */
1006 		GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
1007 		x += level_width;
1008 
1009 		this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
1010 	}
1011 	cur_row++;
1012 
1013 	return cur_row;
1014 }
1015 
1016 /* == SettingEntry methods == */
1017 
1018 /**
1019  * Constructor for a single setting in the 'advanced settings' window
1020  * @param name Name of the setting in the setting table
1021  */
SettingEntry(const char * name)1022 SettingEntry::SettingEntry(const char *name)
1023 {
1024 	this->name = name;
1025 	this->setting = nullptr;
1026 }
1027 
1028 /**
1029  * Initialization of a setting entry
1030  * @param level      Page nesting level of this entry
1031  */
Init(byte level)1032 void SettingEntry::Init(byte level)
1033 {
1034 	BaseSettingEntry::Init(level);
1035 	this->setting = GetSettingFromName(this->name)->AsIntSetting();
1036 }
1037 
1038 /* Sets the given setting entry to its default value */
ResetAll()1039 void SettingEntry::ResetAll()
1040 {
1041 	SetSettingValue(this->setting, this->setting->def);
1042 }
1043 
1044 /**
1045  * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
1046  * @param new_val New value for the button flags
1047  * @see SettingEntryFlags
1048  */
SetButtons(byte new_val)1049 void SettingEntry::SetButtons(byte new_val)
1050 {
1051 	assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
1052 	this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
1053 }
1054 
1055 /** Return number of rows needed to display the (filtered) entry */
Length() const1056 uint SettingEntry::Length() const
1057 {
1058 	return this->IsFiltered() ? 0 : 1;
1059 }
1060 
1061 /**
1062  * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
1063  * @param maxw Maximal width of a line help text.
1064  * @return Biggest height needed to display any help text of this node (and its descendants).
1065  */
GetMaxHelpHeight(int maxw)1066 uint SettingEntry::GetMaxHelpHeight(int maxw)
1067 {
1068 	return GetStringHeight(this->GetHelpText(), maxw);
1069 }
1070 
1071 /**
1072  * Checks whether an entry shall be made visible based on the restriction mode.
1073  * @param mode The current status of the restriction drop down box.
1074  * @return true if the entry shall be visible.
1075  */
IsVisibleByRestrictionMode(RestrictionMode mode) const1076 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode) const
1077 {
1078 	/* There shall not be any restriction, i.e. all settings shall be visible. */
1079 	if (mode == RM_ALL) return true;
1080 
1081 	const IntSettingDesc *sd = this->setting;
1082 
1083 	if (mode == RM_BASIC) return (this->setting->cat & SC_BASIC_LIST) != 0;
1084 	if (mode == RM_ADVANCED) return (this->setting->cat & SC_ADVANCED_LIST) != 0;
1085 
1086 	/* Read the current value. */
1087 	const void *object = ResolveObject(&GetGameSettings(), sd);
1088 	int64 current_value = sd->Read(object);
1089 	int64 filter_value;
1090 
1091 	if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1092 		/* This entry shall only be visible, if the value deviates from its default value. */
1093 
1094 		/* Read the default value. */
1095 		filter_value = sd->def;
1096 	} else {
1097 		assert(mode == RM_CHANGED_AGAINST_NEW);
1098 		/* This entry shall only be visible, if the value deviates from
1099 		 * its value is used when starting a new game. */
1100 
1101 		/* Make sure we're not comparing the new game settings against itself. */
1102 		assert(&GetGameSettings() != &_settings_newgame);
1103 
1104 		/* Read the new game's value. */
1105 		filter_value = sd->Read(ResolveObject(&_settings_newgame, sd));
1106 	}
1107 
1108 	return current_value != filter_value;
1109 }
1110 
1111 /**
1112  * Update the filter state.
1113  * @param filter Filter
1114  * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1115  * @return true if item remains visible
1116  */
UpdateFilterState(SettingFilter & filter,bool force_visible)1117 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1118 {
1119 	CLRBITS(this->flags, SEF_FILTERED);
1120 
1121 	bool visible = true;
1122 
1123 	const IntSettingDesc *sd = this->setting;
1124 	if (!force_visible && !filter.string.IsEmpty()) {
1125 		/* Process the search text filter for this item. */
1126 		filter.string.ResetState();
1127 
1128 		SetDParam(0, STR_EMPTY);
1129 		filter.string.AddLine(sd->str);
1130 		filter.string.AddLine(this->GetHelpText());
1131 
1132 		visible = filter.string.GetState();
1133 	}
1134 
1135 	if (visible) {
1136 		if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1137 			filter.type_hides = true;
1138 			visible = false;
1139 		}
1140 		if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1141 			while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1142 			visible = false;
1143 		}
1144 	}
1145 
1146 	if (!visible) SETBITS(this->flags, SEF_FILTERED);
1147 	return visible;
1148 }
1149 
ResolveObject(const GameSettings * settings_ptr,const IntSettingDesc * sd)1150 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd)
1151 {
1152 	if ((sd->flags & SF_PER_COMPANY) != 0) {
1153 		if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1154 			return &Company::Get(_local_company)->settings;
1155 		}
1156 		return &_settings_client.company;
1157 	}
1158 	return settings_ptr;
1159 }
1160 
1161 /**
1162  * Set the DParams for drawing the value of a setting.
1163  * @param first_param First DParam to use
1164  * @param value Setting value to set params for.
1165  */
SetValueDParams(uint first_param,int32 value) const1166 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1167 {
1168 	if (this->setting->IsBoolSetting()) {
1169 		SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1170 	} else {
1171 		if ((this->setting->flags & SF_GUI_DROPDOWN) != 0) {
1172 			SetDParam(first_param++, this->setting->str_val - this->setting->min + value);
1173 		} else if ((this->setting->flags & SF_GUI_NEGATIVE_IS_SPECIAL) != 0) {
1174 			SetDParam(first_param++, this->setting->str_val + ((value >= 0) ? 1 : 0));
1175 			value = abs(value);
1176 		} else {
1177 			SetDParam(first_param++, this->setting->str_val + ((value == 0 && (this->setting->flags & SF_GUI_0_IS_SPECIAL) != 0) ? 1 : 0));
1178 		}
1179 		SetDParam(first_param++, value);
1180 	}
1181 }
1182 
1183 /**
1184  * Function to draw setting value (button + text + current value)
1185  * @param settings_ptr Pointer to current values of all settings
1186  * @param left         Left-most position in window/panel to start drawing
1187  * @param right        Right-most position in window/panel to draw
1188  * @param y            Upper-most position in window/panel to start drawing
1189  * @param highlight    Highlight entry.
1190  */
DrawSetting(GameSettings * settings_ptr,int left,int right,int y,bool highlight) const1191 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1192 {
1193 	const IntSettingDesc *sd = this->setting;
1194 	int state = this->flags & SEF_BUTTONS_MASK;
1195 
1196 	bool rtl = _current_text_dir == TD_RTL;
1197 	uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1198 	uint text_left  = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + 5);
1199 	uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + 5 : 0);
1200 	uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1201 
1202 	/* We do not allow changes of some items when we are a client in a networkgame */
1203 	bool editable = sd->IsEditable();
1204 
1205 	SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1206 	int32 value = sd->Read(ResolveObject(settings_ptr, sd));
1207 	if (sd->IsBoolSetting()) {
1208 		/* Draw checkbox for boolean-value either on/off */
1209 		DrawBoolButton(buttons_left, button_y, value != 0, editable);
1210 	} else if ((sd->flags & SF_GUI_DROPDOWN) != 0) {
1211 		/* Draw [v] button for settings of an enum-type */
1212 		DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1213 	} else {
1214 		/* Draw [<][>] boxes for settings of an integer-type */
1215 		DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1216 				editable && value != (sd->flags & SF_GUI_0_IS_SPECIAL ? 0 : sd->min), editable && (uint32)value != sd->max);
1217 	}
1218 	this->SetValueDParams(1, value);
1219 	DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sd->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1220 }
1221 
1222 /* == SettingsContainer methods == */
1223 
1224 /**
1225  * Initialization of an entire setting page
1226  * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1227  */
Init(byte level)1228 void SettingsContainer::Init(byte level)
1229 {
1230 	for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1231 		(*it)->Init(level);
1232 	}
1233 }
1234 
1235 /** Resets all settings to their default values */
ResetAll()1236 void SettingsContainer::ResetAll()
1237 {
1238 	for (auto settings_entry : this->entries) {
1239 		settings_entry->ResetAll();
1240 	}
1241 }
1242 
1243 /** Recursively close all folds of sub-pages */
FoldAll()1244 void SettingsContainer::FoldAll()
1245 {
1246 	for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1247 		(*it)->FoldAll();
1248 	}
1249 }
1250 
1251 /** Recursively open all folds of sub-pages */
UnFoldAll()1252 void SettingsContainer::UnFoldAll()
1253 {
1254 	for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1255 		(*it)->UnFoldAll();
1256 	}
1257 }
1258 
1259 /**
1260  * Recursively accumulate the folding state of the tree.
1261  * @param[in,out] all_folded Set to false, if one entry is not folded.
1262  * @param[in,out] all_unfolded Set to false, if one entry is folded.
1263  */
GetFoldingState(bool & all_folded,bool & all_unfolded) const1264 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1265 {
1266 	for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1267 		(*it)->GetFoldingState(all_folded, all_unfolded);
1268 	}
1269 }
1270 
1271 /**
1272  * Update the filter state.
1273  * @param filter Filter
1274  * @param force_visible Whether to force all items visible, no matter what
1275  * @return true if item remains visible
1276  */
UpdateFilterState(SettingFilter & filter,bool force_visible)1277 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1278 {
1279 	bool visible = false;
1280 	bool first_visible = true;
1281 	for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1282 		visible |= (*it)->UpdateFilterState(filter, force_visible);
1283 		(*it)->SetLastField(first_visible);
1284 		if (visible && first_visible) first_visible = false;
1285 	}
1286 	return visible;
1287 }
1288 
1289 
1290 /**
1291  * Check whether an entry is visible and not folded or filtered away.
1292  * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1293  * @param item Entry to search for.
1294  * @return true if entry is visible.
1295  */
IsVisible(const BaseSettingEntry * item) const1296 bool SettingsContainer::IsVisible(const BaseSettingEntry *item) const
1297 {
1298 	for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1299 		if ((*it)->IsVisible(item)) return true;
1300 	}
1301 	return false;
1302 }
1303 
1304 /** Return number of rows needed to display the whole page */
Length() const1305 uint SettingsContainer::Length() const
1306 {
1307 	uint length = 0;
1308 	for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1309 		length += (*it)->Length();
1310 	}
1311 	return length;
1312 }
1313 
1314 /**
1315  * Find the setting entry at row number \a row_num
1316  * @param row_num Index of entry to return
1317  * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1318  * @return The requested setting entry or \c nullptr if it does not exist
1319  */
FindEntry(uint row_num,uint * cur_row)1320 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1321 {
1322 	BaseSettingEntry *pe = nullptr;
1323 	for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1324 		pe = (*it)->FindEntry(row_num, cur_row);
1325 		if (pe != nullptr) {
1326 			break;
1327 		}
1328 	}
1329 	return pe;
1330 }
1331 
1332 /**
1333  * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1334  * @param maxw Maximal width of a line help text.
1335  * @return Biggest height needed to display any help text of this (sub-)tree.
1336  */
GetMaxHelpHeight(int maxw)1337 uint SettingsContainer::GetMaxHelpHeight(int maxw)
1338 {
1339 	uint biggest = 0;
1340 	for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1341 		biggest = std::max(biggest, (*it)->GetMaxHelpHeight(maxw));
1342 	}
1343 	return biggest;
1344 }
1345 
1346 
1347 /**
1348  * Draw a row in the settings panel.
1349  *
1350  * @param settings_ptr Pointer to current values of all settings
1351  * @param left         Left-most position in window/panel to start drawing \a first_row
1352  * @param right        Right-most x position to draw strings at.
1353  * @param y            Upper-most position in window/panel to start drawing \a first_row
1354  * @param first_row    First row number to draw
1355  * @param max_row      Row-number to stop drawing (the row-number of the row below the last row to draw)
1356  * @param selected     Selected entry by the user.
1357  * @param cur_row      Current row number (internal variable)
1358  * @param parent_last  Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
1359  * @return Row number of the next row to draw
1360  */
Draw(GameSettings * settings_ptr,int left,int right,int y,uint first_row,uint max_row,BaseSettingEntry * selected,uint cur_row,uint parent_last) const1361 uint SettingsContainer::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
1362 {
1363 	for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1364 		cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1365 		if (cur_row >= max_row) {
1366 			break;
1367 		}
1368 	}
1369 	return cur_row;
1370 }
1371 
1372 /* == SettingsPage methods == */
1373 
1374 /**
1375  * Constructor for a sub-page in the 'advanced settings' window
1376  * @param title Title of the sub-page
1377  */
SettingsPage(StringID title)1378 SettingsPage::SettingsPage(StringID title)
1379 {
1380 	this->title = title;
1381 	this->folded = true;
1382 }
1383 
1384 /**
1385  * Initialization of an entire setting page
1386  * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1387  */
Init(byte level)1388 void SettingsPage::Init(byte level)
1389 {
1390 	BaseSettingEntry::Init(level);
1391 	SettingsContainer::Init(level + 1);
1392 }
1393 
1394 /** Resets all settings to their default values */
ResetAll()1395 void SettingsPage::ResetAll()
1396 {
1397 	for (auto settings_entry : this->entries) {
1398 		settings_entry->ResetAll();
1399 	}
1400 }
1401 
1402 /** Recursively close all (filtered) folds of sub-pages */
FoldAll()1403 void SettingsPage::FoldAll()
1404 {
1405 	if (this->IsFiltered()) return;
1406 	this->folded = true;
1407 
1408 	SettingsContainer::FoldAll();
1409 }
1410 
1411 /** Recursively open all (filtered) folds of sub-pages */
UnFoldAll()1412 void SettingsPage::UnFoldAll()
1413 {
1414 	if (this->IsFiltered()) return;
1415 	this->folded = false;
1416 
1417 	SettingsContainer::UnFoldAll();
1418 }
1419 
1420 /**
1421  * Recursively accumulate the folding state of the (filtered) tree.
1422  * @param[in,out] all_folded Set to false, if one entry is not folded.
1423  * @param[in,out] all_unfolded Set to false, if one entry is folded.
1424  */
GetFoldingState(bool & all_folded,bool & all_unfolded) const1425 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1426 {
1427 	if (this->IsFiltered()) return;
1428 
1429 	if (this->folded) {
1430 		all_unfolded = false;
1431 	} else {
1432 		all_folded = false;
1433 	}
1434 
1435 	SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1436 }
1437 
1438 /**
1439  * Update the filter state.
1440  * @param filter Filter
1441  * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1442  * @return true if item remains visible
1443  */
UpdateFilterState(SettingFilter & filter,bool force_visible)1444 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1445 {
1446 	if (!force_visible && !filter.string.IsEmpty()) {
1447 		filter.string.ResetState();
1448 		filter.string.AddLine(this->title);
1449 		force_visible = filter.string.GetState();
1450 	}
1451 
1452 	bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1453 	if (visible) {
1454 		CLRBITS(this->flags, SEF_FILTERED);
1455 	} else {
1456 		SETBITS(this->flags, SEF_FILTERED);
1457 	}
1458 	return visible;
1459 }
1460 
1461 /**
1462  * Check whether an entry is visible and not folded or filtered away.
1463  * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1464  * @param item Entry to search for.
1465  * @return true if entry is visible.
1466  */
IsVisible(const BaseSettingEntry * item) const1467 bool SettingsPage::IsVisible(const BaseSettingEntry *item) const
1468 {
1469 	if (this->IsFiltered()) return false;
1470 	if (this == item) return true;
1471 	if (this->folded) return false;
1472 
1473 	return SettingsContainer::IsVisible(item);
1474 }
1475 
1476 /** Return number of rows needed to display the (filtered) entry */
Length() const1477 uint SettingsPage::Length() const
1478 {
1479 	if (this->IsFiltered()) return 0;
1480 	if (this->folded) return 1; // Only displaying the title
1481 
1482 	return 1 + SettingsContainer::Length();
1483 }
1484 
1485 /**
1486  * Find setting entry at row \a row_num
1487  * @param row_num Index of entry to return
1488  * @param cur_row Current row number
1489  * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1490  */
FindEntry(uint row_num,uint * cur_row)1491 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1492 {
1493 	if (this->IsFiltered()) return nullptr;
1494 	if (row_num == *cur_row) return this;
1495 	(*cur_row)++;
1496 	if (this->folded) return nullptr;
1497 
1498 	return SettingsContainer::FindEntry(row_num, cur_row);
1499 }
1500 
1501 /**
1502  * Draw a row in the settings panel.
1503  *
1504  * @param settings_ptr Pointer to current values of all settings
1505  * @param left         Left-most position in window/panel to start drawing \a first_row
1506  * @param right        Right-most x position to draw strings at.
1507  * @param y            Upper-most position in window/panel to start drawing \a first_row
1508  * @param first_row    First row number to draw
1509  * @param max_row      Row-number to stop drawing (the row-number of the row below the last row to draw)
1510  * @param selected     Selected entry by the user.
1511  * @param cur_row      Current row number (internal variable)
1512  * @param parent_last  Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
1513  * @return Row number of the next row to draw
1514  */
Draw(GameSettings * settings_ptr,int left,int right,int y,uint first_row,uint max_row,BaseSettingEntry * selected,uint cur_row,uint parent_last) const1515 uint SettingsPage::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
1516 {
1517 	if (this->IsFiltered()) return cur_row;
1518 	if (cur_row >= max_row) return cur_row;
1519 
1520 	cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1521 
1522 	if (!this->folded) {
1523 		if (this->flags & SEF_LAST_FIELD) {
1524 			assert(this->level < 8 * sizeof(parent_last));
1525 			SetBit(parent_last, this->level); // Add own last-field state
1526 		}
1527 
1528 		cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1529 	}
1530 
1531 	return cur_row;
1532 }
1533 
1534 /**
1535  * Function to draw setting value (button + text + current value)
1536  * @param settings_ptr Pointer to current values of all settings
1537  * @param left         Left-most position in window/panel to start drawing
1538  * @param right        Right-most position in window/panel to draw
1539  * @param y            Upper-most position in window/panel to start drawing
1540  * @param highlight    Highlight entry.
1541  */
DrawSetting(GameSettings * settings_ptr,int left,int right,int y,bool highlight) const1542 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1543 {
1544 	bool rtl = _current_text_dir == TD_RTL;
1545 	DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1546 	DrawString(rtl ? left : left + _circle_size.width + 2, rtl ? right - _circle_size.width - 2 : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1547 }
1548 
1549 /** Construct settings tree */
GetSettingsTree()1550 static SettingsContainer &GetSettingsTree()
1551 {
1552 	static SettingsContainer *main = nullptr;
1553 
1554 	if (main == nullptr)
1555 	{
1556 		/* Build up the dynamic settings-array only once per OpenTTD session */
1557 		main = new SettingsContainer();
1558 
1559 		SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1560 		{
1561 			localisation->Add(new SettingEntry("locale.units_velocity"));
1562 			localisation->Add(new SettingEntry("locale.units_power"));
1563 			localisation->Add(new SettingEntry("locale.units_weight"));
1564 			localisation->Add(new SettingEntry("locale.units_volume"));
1565 			localisation->Add(new SettingEntry("locale.units_force"));
1566 			localisation->Add(new SettingEntry("locale.units_height"));
1567 			localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1568 		}
1569 
1570 		SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1571 		{
1572 			graphics->Add(new SettingEntry("gui.zoom_min"));
1573 			graphics->Add(new SettingEntry("gui.zoom_max"));
1574 			graphics->Add(new SettingEntry("gui.sprite_zoom_min"));
1575 			graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1576 			graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1577 		}
1578 
1579 		SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1580 		{
1581 			sound->Add(new SettingEntry("sound.click_beep"));
1582 			sound->Add(new SettingEntry("sound.confirm"));
1583 			sound->Add(new SettingEntry("sound.news_ticker"));
1584 			sound->Add(new SettingEntry("sound.news_full"));
1585 			sound->Add(new SettingEntry("sound.new_year"));
1586 			sound->Add(new SettingEntry("sound.disaster"));
1587 			sound->Add(new SettingEntry("sound.vehicle"));
1588 			sound->Add(new SettingEntry("sound.ambient"));
1589 		}
1590 
1591 		SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1592 		{
1593 			SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1594 			{
1595 				general->Add(new SettingEntry("gui.osk_activation"));
1596 				general->Add(new SettingEntry("gui.hover_delay_ms"));
1597 				general->Add(new SettingEntry("gui.errmsg_duration"));
1598 				general->Add(new SettingEntry("gui.window_snap_radius"));
1599 				general->Add(new SettingEntry("gui.window_soft_limit"));
1600 				general->Add(new SettingEntry("gui.right_mouse_wnd_close"));
1601 			}
1602 
1603 			SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1604 			{
1605 				viewports->Add(new SettingEntry("gui.auto_scrolling"));
1606 				viewports->Add(new SettingEntry("gui.scroll_mode"));
1607 				viewports->Add(new SettingEntry("gui.smooth_scroll"));
1608 				/* While the horizontal scrollwheel scrolling is written as general code, only
1609 				 *  the cocoa (OSX) driver generates input for it.
1610 				 *  Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1611 				viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1612 				viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1613 #ifdef __APPLE__
1614 				/* We might need to emulate a right mouse button on mac */
1615 				viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1616 #endif
1617 				viewports->Add(new SettingEntry("gui.population_in_label"));
1618 				viewports->Add(new SettingEntry("gui.liveries"));
1619 				viewports->Add(new SettingEntry("construction.train_signal_side"));
1620 				viewports->Add(new SettingEntry("gui.measure_tooltip"));
1621 				viewports->Add(new SettingEntry("gui.loading_indicators"));
1622 				viewports->Add(new SettingEntry("gui.show_track_reservation"));
1623 			}
1624 
1625 			SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1626 			{
1627 				construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1628 				construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1629 				construction->Add(new SettingEntry("gui.quick_goto"));
1630 				construction->Add(new SettingEntry("gui.default_rail_type"));
1631 			}
1632 
1633 			interface->Add(new SettingEntry("gui.fast_forward_speed_limit"));
1634 			interface->Add(new SettingEntry("gui.autosave"));
1635 			interface->Add(new SettingEntry("gui.toolbar_pos"));
1636 			interface->Add(new SettingEntry("gui.statusbar_pos"));
1637 			interface->Add(new SettingEntry("gui.prefer_teamchat"));
1638 			interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1639 			interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1640 			interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1641 			interface->Add(new SettingEntry("gui.expenses_layout"));
1642 			interface->Add(new SettingEntry("gui.show_newgrf_name"));
1643 		}
1644 
1645 		SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1646 		{
1647 			advisors->Add(new SettingEntry("gui.coloured_news_year"));
1648 			advisors->Add(new SettingEntry("news_display.general"));
1649 			advisors->Add(new SettingEntry("news_display.new_vehicles"));
1650 			advisors->Add(new SettingEntry("news_display.accident"));
1651 			advisors->Add(new SettingEntry("news_display.company_info"));
1652 			advisors->Add(new SettingEntry("news_display.acceptance"));
1653 			advisors->Add(new SettingEntry("news_display.arrival_player"));
1654 			advisors->Add(new SettingEntry("news_display.arrival_other"));
1655 			advisors->Add(new SettingEntry("news_display.advice"));
1656 			advisors->Add(new SettingEntry("gui.order_review_system"));
1657 			advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1658 			advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1659 			advisors->Add(new SettingEntry("gui.show_finances"));
1660 			advisors->Add(new SettingEntry("news_display.economy"));
1661 			advisors->Add(new SettingEntry("news_display.subsidies"));
1662 			advisors->Add(new SettingEntry("news_display.open"));
1663 			advisors->Add(new SettingEntry("news_display.close"));
1664 			advisors->Add(new SettingEntry("news_display.production_player"));
1665 			advisors->Add(new SettingEntry("news_display.production_other"));
1666 			advisors->Add(new SettingEntry("news_display.production_nobody"));
1667 		}
1668 
1669 		SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1670 		{
1671 			company->Add(new SettingEntry("gui.semaphore_build_before"));
1672 			company->Add(new SettingEntry("gui.cycle_signal_types"));
1673 			company->Add(new SettingEntry("gui.signal_gui_mode"));
1674 			company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1675 			company->Add(new SettingEntry("gui.auto_remove_signals"));
1676 			company->Add(new SettingEntry("gui.new_nonstop"));
1677 			company->Add(new SettingEntry("gui.stop_location"));
1678 			company->Add(new SettingEntry("gui.starting_colour"));
1679 			company->Add(new SettingEntry("company.engine_renew"));
1680 			company->Add(new SettingEntry("company.engine_renew_months"));
1681 			company->Add(new SettingEntry("company.engine_renew_money"));
1682 			company->Add(new SettingEntry("vehicle.servint_ispercent"));
1683 			company->Add(new SettingEntry("vehicle.servint_trains"));
1684 			company->Add(new SettingEntry("vehicle.servint_roadveh"));
1685 			company->Add(new SettingEntry("vehicle.servint_ships"));
1686 			company->Add(new SettingEntry("vehicle.servint_aircraft"));
1687 		}
1688 
1689 		SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1690 		{
1691 			accounting->Add(new SettingEntry("economy.inflation"));
1692 			accounting->Add(new SettingEntry("difficulty.initial_interest"));
1693 			accounting->Add(new SettingEntry("difficulty.max_loan"));
1694 			accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1695 			accounting->Add(new SettingEntry("difficulty.subsidy_duration"));
1696 			accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1697 			accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1698 			accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1699 			accounting->Add(new SettingEntry("difficulty.construction_cost"));
1700 		}
1701 
1702 		SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1703 		{
1704 			SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1705 			{
1706 				physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1707 				physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1708 				physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1709 				physics->Add(new SettingEntry("vehicle.freight_trains"));
1710 				physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1711 				physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1712 				physics->Add(new SettingEntry("vehicle.smoke_amount"));
1713 				physics->Add(new SettingEntry("vehicle.plane_speed"));
1714 			}
1715 
1716 			SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1717 			{
1718 				routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1719 				routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1720 				routing->Add(new SettingEntry("pf.reverse_at_signals"));
1721 				routing->Add(new SettingEntry("pf.forbid_90_deg"));
1722 				routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1723 				routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1724 			}
1725 
1726 			vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1727 			vehicles->Add(new SettingEntry("order.serviceathelipad"));
1728 		}
1729 
1730 		SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1731 		{
1732 			limitations->Add(new SettingEntry("construction.command_pause_level"));
1733 			limitations->Add(new SettingEntry("construction.autoslope"));
1734 			limitations->Add(new SettingEntry("construction.extra_dynamite"));
1735 			limitations->Add(new SettingEntry("construction.map_height_limit"));
1736 			limitations->Add(new SettingEntry("construction.max_bridge_length"));
1737 			limitations->Add(new SettingEntry("construction.max_bridge_height"));
1738 			limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1739 			limitations->Add(new SettingEntry("station.never_expire_airports"));
1740 			limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1741 			limitations->Add(new SettingEntry("vehicle.max_trains"));
1742 			limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1743 			limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1744 			limitations->Add(new SettingEntry("vehicle.max_ships"));
1745 			limitations->Add(new SettingEntry("vehicle.max_train_length"));
1746 			limitations->Add(new SettingEntry("station.station_spread"));
1747 			limitations->Add(new SettingEntry("station.distant_join_stations"));
1748 			limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1749 			limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1750 			limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1751 		}
1752 
1753 		SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1754 		{
1755 			disasters->Add(new SettingEntry("difficulty.disasters"));
1756 			disasters->Add(new SettingEntry("difficulty.economy"));
1757 			disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1758 			disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1759 		}
1760 
1761 		SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1762 		{
1763 			genworld->Add(new SettingEntry("game_creation.landscape"));
1764 			genworld->Add(new SettingEntry("game_creation.land_generator"));
1765 			genworld->Add(new SettingEntry("difficulty.terrain_type"));
1766 			genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1767 			genworld->Add(new SettingEntry("game_creation.variety"));
1768 			genworld->Add(new SettingEntry("game_creation.snow_coverage"));
1769 			genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1770 			genworld->Add(new SettingEntry("game_creation.desert_coverage"));
1771 			genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1772 			genworld->Add(new SettingEntry("game_creation.tree_placer"));
1773 			genworld->Add(new SettingEntry("vehicle.road_side"));
1774 			genworld->Add(new SettingEntry("economy.larger_towns"));
1775 			genworld->Add(new SettingEntry("economy.initial_city_size"));
1776 			genworld->Add(new SettingEntry("economy.town_layout"));
1777 			genworld->Add(new SettingEntry("difficulty.industry_density"));
1778 			genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1779 			genworld->Add(new SettingEntry("game_creation.ending_year"));
1780 		}
1781 
1782 		SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1783 		{
1784 			SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1785 			{
1786 				authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1787 				authorities->Add(new SettingEntry("economy.bribe"));
1788 				authorities->Add(new SettingEntry("economy.exclusive_rights"));
1789 				authorities->Add(new SettingEntry("economy.fund_roads"));
1790 				authorities->Add(new SettingEntry("economy.fund_buildings"));
1791 				authorities->Add(new SettingEntry("economy.station_noise_level"));
1792 			}
1793 
1794 			SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1795 			{
1796 				towns->Add(new SettingEntry("economy.town_growth_rate"));
1797 				towns->Add(new SettingEntry("economy.allow_town_roads"));
1798 				towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1799 				towns->Add(new SettingEntry("economy.found_town"));
1800 				towns->Add(new SettingEntry("economy.town_cargogen_mode"));
1801 			}
1802 
1803 			SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1804 			{
1805 				industries->Add(new SettingEntry("construction.raw_industry_construction"));
1806 				industries->Add(new SettingEntry("construction.industry_platform"));
1807 				industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1808 				industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1809 				industries->Add(new SettingEntry("economy.type"));
1810 				industries->Add(new SettingEntry("station.serve_neutral_industries"));
1811 			}
1812 
1813 			SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1814 			{
1815 				cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1816 				cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1817 				cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1818 				cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1819 				cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1820 				cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1821 				cdist->Add(new SettingEntry("linkgraph.accuracy"));
1822 				cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1823 				cdist->Add(new SettingEntry("linkgraph.demand_size"));
1824 				cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1825 			}
1826 
1827 			environment->Add(new SettingEntry("station.modified_catchment"));
1828 			environment->Add(new SettingEntry("construction.extra_tree_placement"));
1829 		}
1830 
1831 		SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1832 		{
1833 			SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1834 			{
1835 				npc->Add(new SettingEntry("script.settings_profile"));
1836 				npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1837 				npc->Add(new SettingEntry("script.script_max_memory_megabytes"));
1838 				npc->Add(new SettingEntry("difficulty.competitor_speed"));
1839 				npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1840 				npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1841 				npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1842 				npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1843 				npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1844 			}
1845 
1846 			ai->Add(new SettingEntry("economy.give_money"));
1847 			ai->Add(new SettingEntry("economy.allow_shares"));
1848 			ai->Add(new SettingEntry("economy.min_years_for_shares"));
1849 		}
1850 
1851 		SettingsPage *network = main->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK));
1852 		{
1853 			network->Add(new SettingEntry("network.use_relay_service"));
1854 		}
1855 
1856 		main->Init();
1857 	}
1858 	return *main;
1859 }
1860 
1861 static const StringID _game_settings_restrict_dropdown[] = {
1862 	STR_CONFIG_SETTING_RESTRICT_BASIC,                            // RM_BASIC
1863 	STR_CONFIG_SETTING_RESTRICT_ADVANCED,                         // RM_ADVANCED
1864 	STR_CONFIG_SETTING_RESTRICT_ALL,                              // RM_ALL
1865 	STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT,          // RM_CHANGED_AGAINST_DEFAULT
1866 	STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW,              // RM_CHANGED_AGAINST_NEW
1867 };
1868 static_assert(lengthof(_game_settings_restrict_dropdown) == RM_END);
1869 
1870 /** Warnings about hidden search results. */
1871 enum WarnHiddenResult {
1872 	WHR_NONE,          ///< Nothing was filtering matches away.
1873 	WHR_CATEGORY,      ///< Category setting filtered matches away.
1874 	WHR_TYPE,          ///< Type setting filtered matches away.
1875 	WHR_CATEGORY_TYPE, ///< Both category and type settings filtered matches away.
1876 };
1877 
1878 /**
1879  * Callback function for the reset all settings button
1880  * @param w Window which is calling this callback
1881  * @param confirmed boolean value, true when yes was clicked, false otherwise
1882  */
ResetAllSettingsConfirmationCallback(Window * w,bool confirmed)1883 static void ResetAllSettingsConfirmationCallback(Window *w, bool confirmed)
1884 {
1885 	if (confirmed) {
1886 		GetSettingsTree().ResetAll();
1887 		GetSettingsTree().FoldAll();
1888 		w->InvalidateData();
1889 	}
1890 }
1891 
1892 /** Window to edit settings of the game. */
1893 struct GameSettingsWindow : Window {
1894 	static const int SETTINGTREE_LEFT_OFFSET   = 5; ///< Position of left edge of setting values
1895 	static const int SETTINGTREE_RIGHT_OFFSET  = 5; ///< Position of right edge of setting values
1896 	static const int SETTINGTREE_TOP_OFFSET    = 5; ///< Position of top edge of setting values
1897 	static const int SETTINGTREE_BOTTOM_OFFSET = 5; ///< Position of bottom edge of setting values
1898 
1899 	static GameSettings *settings_ptr; ///< Pointer to the game settings being displayed and modified.
1900 
1901 	SettingEntry *valuewindow_entry;   ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
1902 	SettingEntry *clicked_entry;       ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
1903 	SettingEntry *last_clicked;        ///< If non-nullptr, pointer to the last clicked setting.
1904 	SettingEntry *valuedropdown_entry; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
1905 	bool closing_dropdown;             ///< True, if the dropdown list is currently closing.
1906 
1907 	SettingFilter filter;              ///< Filter for the list.
1908 	QueryString filter_editbox;        ///< Filter editbox;
1909 	bool manually_changed_folding;     ///< Whether the user expanded/collapsed something manually.
1910 	WarnHiddenResult warn_missing;     ///< Whether and how to warn about missing search results.
1911 	int warn_lines;                    ///< Number of lines used for warning about missing search results.
1912 
1913 	Scrollbar *vscroll;
1914 
GameSettingsWindowGameSettingsWindow1915 	GameSettingsWindow(WindowDesc *desc) : Window(desc), filter_editbox(50)
1916 	{
1917 		this->warn_missing = WHR_NONE;
1918 		this->warn_lines = 0;
1919 		this->filter.mode = (RestrictionMode)_settings_client.gui.settings_restriction_mode;
1920 		this->filter.min_cat = RM_ALL;
1921 		this->filter.type = ST_ALL;
1922 		this->filter.type_hides = false;
1923 		this->settings_ptr = &GetGameSettings();
1924 
1925 		_circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1926 		GetSettingsTree().FoldAll(); // Close all sub-pages
1927 
1928 		this->valuewindow_entry = nullptr; // No setting entry for which a entry window is opened
1929 		this->clicked_entry = nullptr; // No numeric setting buttons are depressed
1930 		this->last_clicked = nullptr;
1931 		this->valuedropdown_entry = nullptr;
1932 		this->closing_dropdown = false;
1933 		this->manually_changed_folding = false;
1934 
1935 		this->CreateNestedTree();
1936 		this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1937 		this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS);
1938 
1939 		this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1940 		this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
1941 		this->SetFocusedWidget(WID_GS_FILTER);
1942 
1943 		this->InvalidateData();
1944 	}
1945 
UpdateWidgetSizeGameSettingsWindow1946 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1947 	{
1948 		switch (widget) {
1949 			case WID_GS_OPTIONSPANEL:
1950 				resize->height = SETTING_HEIGHT = std::max({(int)_circle_size.height, SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL}) + 1;
1951 				resize->width  = 1;
1952 
1953 				size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1954 				break;
1955 
1956 			case WID_GS_HELP_TEXT: {
1957 				static const StringID setting_types[] = {
1958 					STR_CONFIG_SETTING_TYPE_CLIENT,
1959 					STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1960 					STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1961 				};
1962 				for (uint i = 0; i < lengthof(setting_types); i++) {
1963 					SetDParam(0, setting_types[i]);
1964 					size->width = std::max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1965 				}
1966 				size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1967 						std::max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1968 				break;
1969 			}
1970 
1971 			case WID_GS_RESTRICT_CATEGORY:
1972 			case WID_GS_RESTRICT_TYPE:
1973 				size->width = std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1974 				break;
1975 
1976 			default:
1977 				break;
1978 		}
1979 	}
1980 
OnPaintGameSettingsWindow1981 	void OnPaint() override
1982 	{
1983 		if (this->closing_dropdown) {
1984 			this->closing_dropdown = false;
1985 			assert(this->valuedropdown_entry != nullptr);
1986 			this->valuedropdown_entry->SetButtons(0);
1987 			this->valuedropdown_entry = nullptr;
1988 		}
1989 
1990 		/* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1991 		const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1992 		StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1993 		int new_warn_lines;
1994 		if (this->warn_missing == WHR_NONE) {
1995 			new_warn_lines = 0;
1996 		} else {
1997 			SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1998 			new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1999 		}
2000 		if (this->warn_lines != new_warn_lines) {
2001 			this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
2002 			this->warn_lines = new_warn_lines;
2003 		}
2004 
2005 		this->DrawWidgets();
2006 
2007 		/* Draw the 'some search results are hidden' notice. */
2008 		if (this->warn_missing != WHR_NONE) {
2009 			const int left = panel->pos_x;
2010 			const int right = left + panel->current_x - 1;
2011 			const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
2012 			SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2013 			if (this->warn_lines == 1) {
2014 				/* If the warning fits at one line, center it. */
2015 				DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
2016 			} else {
2017 				DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
2018 			}
2019 		}
2020 	}
2021 
SetStringParametersGameSettingsWindow2022 	void SetStringParameters(int widget) const override
2023 	{
2024 		switch (widget) {
2025 			case WID_GS_RESTRICT_DROPDOWN:
2026 				SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
2027 				break;
2028 
2029 			case WID_GS_TYPE_DROPDOWN:
2030 				switch (this->filter.type) {
2031 					case ST_GAME:    SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
2032 					case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
2033 					case ST_CLIENT:  SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
2034 					default:         SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
2035 				}
2036 				break;
2037 		}
2038 	}
2039 
BuildDropDownListGameSettingsWindow2040 	DropDownList BuildDropDownList(int widget) const
2041 	{
2042 		DropDownList list;
2043 		switch (widget) {
2044 			case WID_GS_RESTRICT_DROPDOWN:
2045 				for (int mode = 0; mode != RM_END; mode++) {
2046 					/* If we are in adv. settings screen for the new game's settings,
2047 					 * we don't want to allow comparing with new game's settings. */
2048 					bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
2049 
2050 					list.emplace_back(new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled));
2051 				}
2052 				break;
2053 
2054 			case WID_GS_TYPE_DROPDOWN:
2055 				list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false));
2056 				list.emplace_back(new DropDownListStringItem(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME, ST_GAME, false));
2057 				list.emplace_back(new DropDownListStringItem(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME, ST_COMPANY, false));
2058 				list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false));
2059 				break;
2060 		}
2061 		return list;
2062 	}
2063 
DrawWidgetGameSettingsWindow2064 	void DrawWidget(const Rect &r, int widget) const override
2065 	{
2066 		switch (widget) {
2067 			case WID_GS_OPTIONSPANEL: {
2068 				int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
2069 				uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
2070 				int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
2071 						this->vscroll->GetPosition(), last_row, this->last_clicked);
2072 				if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
2073 				break;
2074 			}
2075 
2076 			case WID_GS_HELP_TEXT:
2077 				if (this->last_clicked != nullptr) {
2078 					const IntSettingDesc *sd = this->last_clicked->setting;
2079 
2080 					int y = r.top;
2081 					switch (sd->GetType()) {
2082 						case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
2083 						case ST_CLIENT:  SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
2084 						case ST_GAME:    SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
2085 						default: NOT_REACHED();
2086 					}
2087 					DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
2088 					y += FONT_HEIGHT_NORMAL;
2089 
2090 					this->last_clicked->SetValueDParams(0, sd->def);
2091 					DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
2092 					y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
2093 
2094 					DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
2095 				}
2096 				break;
2097 
2098 			default:
2099 				break;
2100 		}
2101 	}
2102 
2103 	/**
2104 	 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2105 	 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2106 	 */
SetDisplayedHelpTextGameSettingsWindow2107 	void SetDisplayedHelpText(SettingEntry *pe)
2108 	{
2109 		if (this->last_clicked != pe) this->SetDirty();
2110 		this->last_clicked = pe;
2111 	}
2112 
OnClickGameSettingsWindow2113 	void OnClick(Point pt, int widget, int click_count) override
2114 	{
2115 		switch (widget) {
2116 			case WID_GS_EXPAND_ALL:
2117 				this->manually_changed_folding = true;
2118 				GetSettingsTree().UnFoldAll();
2119 				this->InvalidateData();
2120 				break;
2121 
2122 			case WID_GS_COLLAPSE_ALL:
2123 				this->manually_changed_folding = true;
2124 				GetSettingsTree().FoldAll();
2125 				this->InvalidateData();
2126 				break;
2127 
2128 			case WID_GS_RESET_ALL:
2129 				ShowQuery(
2130 					STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_CAPTION,
2131 					STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT,
2132 					this,
2133 					ResetAllSettingsConfirmationCallback
2134 				);
2135 				break;
2136 
2137 			case WID_GS_RESTRICT_DROPDOWN: {
2138 				DropDownList list = this->BuildDropDownList(widget);
2139 				if (!list.empty()) {
2140 					ShowDropDownList(this, std::move(list), this->filter.mode, widget);
2141 				}
2142 				break;
2143 			}
2144 
2145 			case WID_GS_TYPE_DROPDOWN: {
2146 				DropDownList list = this->BuildDropDownList(widget);
2147 				if (!list.empty()) {
2148 					ShowDropDownList(this, std::move(list), this->filter.type, widget);
2149 				}
2150 				break;
2151 			}
2152 		}
2153 
2154 		if (widget != WID_GS_OPTIONSPANEL) return;
2155 
2156 		uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2157 		if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2158 		btn -= this->warn_lines;
2159 
2160 		uint cur_row = 0;
2161 		BaseSettingEntry *clicked_entry = GetSettingsTree().FindEntry(btn, &cur_row);
2162 
2163 		if (clicked_entry == nullptr) return;  // Clicked below the last setting of the page
2164 
2165 		int x = (_current_text_dir == TD_RTL ? this->width - 1 - pt.x : pt.x) - SETTINGTREE_LEFT_OFFSET - (clicked_entry->level + 1) * LEVEL_WIDTH;  // Shift x coordinate
2166 		if (x < 0) return;  // Clicked left of the entry
2167 
2168 		SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2169 		if (clicked_page != nullptr) {
2170 			this->SetDisplayedHelpText(nullptr);
2171 			clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2172 
2173 			this->manually_changed_folding = true;
2174 
2175 			this->InvalidateData();
2176 			return;
2177 		}
2178 
2179 		SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2180 		assert(pe != nullptr);
2181 		const IntSettingDesc *sd = pe->setting;
2182 
2183 		/* return if action is only active in network, or only settable by server */
2184 		if (!sd->IsEditable()) {
2185 			this->SetDisplayedHelpText(pe);
2186 			return;
2187 		}
2188 
2189 		int32 value = sd->Read(ResolveObject(settings_ptr, sd));
2190 
2191 		/* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2192 		if (x < SETTING_BUTTON_WIDTH && (sd->flags & SF_GUI_DROPDOWN)) {
2193 			this->SetDisplayedHelpText(pe);
2194 
2195 			if (this->valuedropdown_entry == pe) {
2196 				/* unclick the dropdown */
2197 				HideDropDownMenu(this);
2198 				this->closing_dropdown = false;
2199 				this->valuedropdown_entry->SetButtons(0);
2200 				this->valuedropdown_entry = nullptr;
2201 			} else {
2202 				if (this->valuedropdown_entry != nullptr) this->valuedropdown_entry->SetButtons(0);
2203 				this->closing_dropdown = false;
2204 
2205 				const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2206 				int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2207 
2208 				Rect wi_rect;
2209 				wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2210 				wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2211 				wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2212 				wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2213 
2214 				/* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2215 				if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2216 					this->valuedropdown_entry = pe;
2217 					this->valuedropdown_entry->SetButtons(SEF_LEFT_DEPRESSED);
2218 
2219 					DropDownList list;
2220 					for (int i = sd->min; i <= (int)sd->max; i++) {
2221 						list.emplace_back(new DropDownListStringItem(sd->str_val + i - sd->min, i, false));
2222 					}
2223 
2224 					ShowDropDownListAt(this, std::move(list), value, -1, wi_rect, COLOUR_ORANGE, true);
2225 				}
2226 			}
2227 			this->SetDirty();
2228 		} else if (x < SETTING_BUTTON_WIDTH) {
2229 			this->SetDisplayedHelpText(pe);
2230 			int32 oldvalue = value;
2231 
2232 			if (sd->IsBoolSetting()) {
2233 				value ^= 1;
2234 			} else {
2235 				/* Add a dynamic step-size to the scroller. In a maximum of
2236 				 * 50-steps you should be able to get from min to max,
2237 				 * unless specified otherwise in the 'interval' variable
2238 				 * of the current setting. */
2239 				uint32 step = (sd->interval == 0) ? ((sd->max - sd->min) / 50) : sd->interval;
2240 				if (step == 0) step = 1;
2241 
2242 				/* don't allow too fast scrolling */
2243 				if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2244 					_left_button_clicked = false;
2245 					return;
2246 				}
2247 
2248 				/* Increase or decrease the value and clamp it to extremes */
2249 				if (x >= SETTING_BUTTON_WIDTH / 2) {
2250 					value += step;
2251 					if (sd->min < 0) {
2252 						assert((int32)sd->max >= 0);
2253 						if (value > (int32)sd->max) value = (int32)sd->max;
2254 					} else {
2255 						if ((uint32)value > sd->max) value = (int32)sd->max;
2256 					}
2257 					if (value < sd->min) value = sd->min; // skip between "disabled" and minimum
2258 				} else {
2259 					value -= step;
2260 					if (value < sd->min) value = (sd->flags & SF_GUI_0_IS_SPECIAL) ? 0 : sd->min;
2261 				}
2262 
2263 				/* Set up scroller timeout for numeric values */
2264 				if (value != oldvalue) {
2265 					if (this->clicked_entry != nullptr) { // Release previous buttons if any
2266 						this->clicked_entry->SetButtons(0);
2267 					}
2268 					this->clicked_entry = pe;
2269 					this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2270 					this->SetTimeout();
2271 					_left_button_clicked = false;
2272 				}
2273 			}
2274 
2275 			if (value != oldvalue) {
2276 				SetSettingValue(sd, value);
2277 				this->SetDirty();
2278 			}
2279 		} else {
2280 			/* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2281 			if (this->last_clicked == pe && !sd->IsBoolSetting() && !(sd->flags & SF_GUI_DROPDOWN)) {
2282 				int64 value64 = value;
2283 				/* Show the correct currency-translated value */
2284 				if (sd->flags & SF_GUI_CURRENCY) value64 *= _currency->rate;
2285 
2286 				this->valuewindow_entry = pe;
2287 				SetDParam(0, value64);
2288 				/* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2289 				ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 15, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2290 			}
2291 			this->SetDisplayedHelpText(pe);
2292 		}
2293 	}
2294 
OnTimeoutGameSettingsWindow2295 	void OnTimeout() override
2296 	{
2297 		if (this->clicked_entry != nullptr) { // On timeout, release any depressed buttons
2298 			this->clicked_entry->SetButtons(0);
2299 			this->clicked_entry = nullptr;
2300 			this->SetDirty();
2301 		}
2302 	}
2303 
OnQueryTextFinishedGameSettingsWindow2304 	void OnQueryTextFinished(char *str) override
2305 	{
2306 		/* The user pressed cancel */
2307 		if (str == nullptr) return;
2308 
2309 		assert(this->valuewindow_entry != nullptr);
2310 		const IntSettingDesc *sd = this->valuewindow_entry->setting;
2311 
2312 		int32 value;
2313 		if (!StrEmpty(str)) {
2314 			long long llvalue = atoll(str);
2315 
2316 			/* Save the correct currency-translated value */
2317 			if (sd->flags & SF_GUI_CURRENCY) llvalue /= _currency->rate;
2318 
2319 			value = (int32)ClampToI32(llvalue);
2320 		} else {
2321 			value = sd->def;
2322 		}
2323 
2324 		SetSettingValue(this->valuewindow_entry->setting, value);
2325 		this->SetDirty();
2326 	}
2327 
OnDropdownSelectGameSettingsWindow2328 	void OnDropdownSelect(int widget, int index) override
2329 	{
2330 		switch (widget) {
2331 			case WID_GS_RESTRICT_DROPDOWN:
2332 				this->filter.mode = (RestrictionMode)index;
2333 				if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2334 						this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2335 
2336 					if (!this->manually_changed_folding) {
2337 						/* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2338 						GetSettingsTree().UpdateFilterState(this->filter, false);
2339 						GetSettingsTree().UnFoldAll();
2340 					}
2341 				} else {
2342 					/* Non-'changes' filter. Save as default. */
2343 					_settings_client.gui.settings_restriction_mode = this->filter.mode;
2344 				}
2345 				this->InvalidateData();
2346 				break;
2347 
2348 			case WID_GS_TYPE_DROPDOWN:
2349 				this->filter.type = (SettingType)index;
2350 				this->InvalidateData();
2351 				break;
2352 
2353 			default:
2354 				if (widget < 0) {
2355 					/* Deal with drop down boxes on the panel. */
2356 					assert(this->valuedropdown_entry != nullptr);
2357 					const IntSettingDesc *sd = this->valuedropdown_entry->setting;
2358 					assert(sd->flags & SF_GUI_DROPDOWN);
2359 
2360 					SetSettingValue(sd, index);
2361 					this->SetDirty();
2362 				}
2363 				break;
2364 		}
2365 	}
2366 
OnDropdownCloseGameSettingsWindow2367 	void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
2368 	{
2369 		if (widget >= 0) {
2370 			/* Normally the default implementation of OnDropdownClose() takes care of
2371 			 * a few things. We want that behaviour here too, but only for
2372 			 * "normal" dropdown boxes. The special dropdown boxes added for every
2373 			 * setting that needs one can't have this call. */
2374 			Window::OnDropdownClose(pt, widget, index, instant_close);
2375 		} else {
2376 			/* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2377 			 * the same dropdown button was clicked again, and then not open the dropdown again.
2378 			 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2379 			 * after OnClick. */
2380 			assert(this->valuedropdown_entry != nullptr);
2381 			this->closing_dropdown = true;
2382 			this->SetDirty();
2383 		}
2384 	}
2385 
OnInvalidateDataGameSettingsWindow2386 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
2387 	{
2388 		if (!gui_scope) return;
2389 
2390 		/* Update which settings are to be visible. */
2391 		RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2392 		this->filter.min_cat = min_level;
2393 		this->filter.type_hides = false;
2394 		GetSettingsTree().UpdateFilterState(this->filter, false);
2395 
2396 		if (this->filter.string.IsEmpty()) {
2397 			this->warn_missing = WHR_NONE;
2398 		} else if (min_level < this->filter.min_cat) {
2399 			this->warn_missing = this->filter.type_hides ? WHR_CATEGORY_TYPE : WHR_CATEGORY;
2400 		} else {
2401 			this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2402 		}
2403 		this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2404 
2405 		if (this->last_clicked != nullptr && !GetSettingsTree().IsVisible(this->last_clicked)) {
2406 			this->SetDisplayedHelpText(nullptr);
2407 		}
2408 
2409 		bool all_folded = true;
2410 		bool all_unfolded = true;
2411 		GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2412 		this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2413 		this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2414 	}
2415 
OnEditboxChangedGameSettingsWindow2416 	void OnEditboxChanged(int wid) override
2417 	{
2418 		if (wid == WID_GS_FILTER) {
2419 			this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2420 			if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2421 				/* User never expanded/collapsed single pages and entered a filter term.
2422 				 * Expand everything, to save weird expand clicks, */
2423 				GetSettingsTree().UnFoldAll();
2424 			}
2425 			this->InvalidateData();
2426 		}
2427 	}
2428 
OnResizeGameSettingsWindow2429 	void OnResize() override
2430 	{
2431 		this->vscroll->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET);
2432 	}
2433 };
2434 
2435 GameSettings *GameSettingsWindow::settings_ptr = nullptr;
2436 
2437 static const NWidgetPart _nested_settings_selection_widgets[] = {
2438 	NWidget(NWID_HORIZONTAL),
2439 		NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2440 		NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2441 		NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2442 	EndContainer(),
2443 	NWidget(WWT_PANEL, COLOUR_MAUVE),
2444 		NWidget(NWID_VERTICAL), SetPIP(0, WD_PAR_VSEP_NORMAL, 0), SetPadding(WD_TEXTPANEL_TOP, 0, WD_TEXTPANEL_BOTTOM, 0),
2445 			NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2446 				NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2447 				NWidget(WWT_DROPDOWN, COLOUR_MAUVE, WID_GS_RESTRICT_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_BLACK_STRING, STR_CONFIG_SETTING_RESTRICT_DROPDOWN_HELPTEXT), SetFill(1, 0), SetResize(1, 0),
2448 			EndContainer(),
2449 			NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2450 				NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2451 				NWidget(WWT_DROPDOWN, COLOUR_MAUVE, WID_GS_TYPE_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_BLACK_STRING, STR_CONFIG_SETTING_TYPE_DROPDOWN_HELPTEXT), SetFill(1, 0), SetResize(1, 0),
2452 			EndContainer(),
2453 		EndContainer(),
2454 		NWidget(NWID_HORIZONTAL), SetPadding(0, 0, WD_TEXTPANEL_BOTTOM, 0),
2455 				SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2456 			NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2457 			NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2458 					SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2459 		EndContainer(),
2460 	EndContainer(),
2461 	NWidget(NWID_HORIZONTAL),
2462 		NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GS_OPTIONSPANEL), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR), EndContainer(),
2463 		NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2464 	EndContainer(),
2465 	NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2466 		NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2467 				SetPadding(WD_FRAMETEXT_TOP, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_BOTTOM, WD_FRAMETEXT_LEFT),
2468 	EndContainer(),
2469 	NWidget(NWID_HORIZONTAL),
2470 		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2471 		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2472 		NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_RESET_ALL), SetDataTip(STR_CONFIG_SETTING_RESET_ALL, STR_NULL),
2473 		NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2474 		EndContainer(),
2475 		NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2476 	EndContainer(),
2477 };
2478 
2479 static WindowDesc _settings_selection_desc(
2480 	WDP_CENTER, "settings", 510, 450,
2481 	WC_GAME_OPTIONS, WC_NONE,
2482 	0,
2483 	_nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2484 );
2485 
2486 /** Open advanced settings window. */
ShowGameSettings()2487 void ShowGameSettings()
2488 {
2489 	CloseWindowByClass(WC_GAME_OPTIONS);
2490 	new GameSettingsWindow(&_settings_selection_desc);
2491 }
2492 
2493 
2494 /**
2495  * Draw [<][>] boxes.
2496  * @param x the x position to draw
2497  * @param y the y position to draw
2498  * @param button_colour the colour of the button
2499  * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2500  * @param clickable_left is the left button clickable?
2501  * @param clickable_right is the right button clickable?
2502  */
DrawArrowButtons(int x,int y,Colours button_colour,byte state,bool clickable_left,bool clickable_right)2503 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2504 {
2505 	int colour = _colour_gradient[button_colour][2];
2506 	Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2507 
2508 	DrawFrameRect(x,             y, x + dim.width - 1,             y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2509 	DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2510 	DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2511 	DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2512 
2513 	/* Grey out the buttons that aren't clickable */
2514 	bool rtl = _current_text_dir == TD_RTL;
2515 	if (rtl ? !clickable_right : !clickable_left) {
2516 		GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2517 	}
2518 	if (rtl ? !clickable_left : !clickable_right) {
2519 		GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2520 	}
2521 }
2522 
2523 /**
2524  * Draw a dropdown button.
2525  * @param x the x position to draw
2526  * @param y the y position to draw
2527  * @param button_colour the colour of the button
2528  * @param state true = lowered
2529  * @param clickable is the button clickable?
2530  */
DrawDropDownButton(int x,int y,Colours button_colour,bool state,bool clickable)2531 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2532 {
2533 	int colour = _colour_gradient[button_colour][2];
2534 
2535 	DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2536 	DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2537 
2538 	if (!clickable) {
2539 		GfxFillRect(x +  1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2540 	}
2541 }
2542 
2543 /**
2544  * Draw a toggle button.
2545  * @param x the x position to draw
2546  * @param y the y position to draw
2547  * @param state true = lowered
2548  * @param clickable is the button clickable?
2549  */
DrawBoolButton(int x,int y,bool state,bool clickable)2550 void DrawBoolButton(int x, int y, bool state, bool clickable)
2551 {
2552 	static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2553 	DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2554 }
2555 
2556 struct CustomCurrencyWindow : Window {
2557 	int query_widget;
2558 
CustomCurrencyWindowCustomCurrencyWindow2559 	CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2560 	{
2561 		this->InitNested();
2562 
2563 		SetButtonState();
2564 	}
2565 
SetButtonStateCustomCurrencyWindow2566 	void SetButtonState()
2567 	{
2568 		this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2569 		this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2570 		this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2571 		this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2572 	}
2573 
SetStringParametersCustomCurrencyWindow2574 	void SetStringParameters(int widget) const override
2575 	{
2576 		switch (widget) {
2577 			case WID_CC_RATE:      SetDParam(0, 1); SetDParam(1, 1);            break;
2578 			case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2579 			case WID_CC_PREFIX:    SetDParamStr(0, _custom_currency.prefix);    break;
2580 			case WID_CC_SUFFIX:    SetDParamStr(0, _custom_currency.suffix);    break;
2581 			case WID_CC_YEAR:
2582 				SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2583 				SetDParam(1, _custom_currency.to_euro);
2584 				break;
2585 
2586 			case WID_CC_PREVIEW:
2587 				SetDParam(0, 10000);
2588 				break;
2589 		}
2590 	}
2591 
UpdateWidgetSizeCustomCurrencyWindow2592 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2593 	{
2594 		switch (widget) {
2595 			/* Set the appropriate width for the edit 'buttons' */
2596 			case WID_CC_SEPARATOR_EDIT:
2597 			case WID_CC_PREFIX_EDIT:
2598 			case WID_CC_SUFFIX_EDIT:
2599 				size->width  = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2600 				break;
2601 
2602 			/* Make sure the window is wide enough for the widest exchange rate */
2603 			case WID_CC_RATE:
2604 				SetDParam(0, 1);
2605 				SetDParam(1, INT32_MAX);
2606 				*size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2607 				break;
2608 		}
2609 	}
2610 
OnClickCustomCurrencyWindow2611 	void OnClick(Point pt, int widget, int click_count) override
2612 	{
2613 		int line = 0;
2614 		int len = 0;
2615 		StringID str = 0;
2616 		CharSetFilter afilter = CS_ALPHANUMERAL;
2617 
2618 		switch (widget) {
2619 			case WID_CC_RATE_DOWN:
2620 				if (_custom_currency.rate > 1) _custom_currency.rate--;
2621 				if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2622 				this->EnableWidget(WID_CC_RATE_UP);
2623 				break;
2624 
2625 			case WID_CC_RATE_UP:
2626 				if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2627 				if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2628 				this->EnableWidget(WID_CC_RATE_DOWN);
2629 				break;
2630 
2631 			case WID_CC_RATE:
2632 				SetDParam(0, _custom_currency.rate);
2633 				str = STR_JUST_INT;
2634 				len = 5;
2635 				line = WID_CC_RATE;
2636 				afilter = CS_NUMERAL;
2637 				break;
2638 
2639 			case WID_CC_SEPARATOR_EDIT:
2640 			case WID_CC_SEPARATOR:
2641 				SetDParamStr(0, _custom_currency.separator);
2642 				str = STR_JUST_RAW_STRING;
2643 				len = 7;
2644 				line = WID_CC_SEPARATOR;
2645 				break;
2646 
2647 			case WID_CC_PREFIX_EDIT:
2648 			case WID_CC_PREFIX:
2649 				SetDParamStr(0, _custom_currency.prefix);
2650 				str = STR_JUST_RAW_STRING;
2651 				len = 15;
2652 				line = WID_CC_PREFIX;
2653 				break;
2654 
2655 			case WID_CC_SUFFIX_EDIT:
2656 			case WID_CC_SUFFIX:
2657 				SetDParamStr(0, _custom_currency.suffix);
2658 				str = STR_JUST_RAW_STRING;
2659 				len = 15;
2660 				line = WID_CC_SUFFIX;
2661 				break;
2662 
2663 			case WID_CC_YEAR_DOWN:
2664 				_custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2665 				if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2666 				this->EnableWidget(WID_CC_YEAR_UP);
2667 				break;
2668 
2669 			case WID_CC_YEAR_UP:
2670 				_custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2671 				if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2672 				this->EnableWidget(WID_CC_YEAR_DOWN);
2673 				break;
2674 
2675 			case WID_CC_YEAR:
2676 				SetDParam(0, _custom_currency.to_euro);
2677 				str = STR_JUST_INT;
2678 				len = 7;
2679 				line = WID_CC_YEAR;
2680 				afilter = CS_NUMERAL;
2681 				break;
2682 		}
2683 
2684 		if (len != 0) {
2685 			this->query_widget = line;
2686 			ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2687 		}
2688 
2689 		this->SetTimeout();
2690 		this->SetDirty();
2691 	}
2692 
OnQueryTextFinishedCustomCurrencyWindow2693 	void OnQueryTextFinished(char *str) override
2694 	{
2695 		if (str == nullptr) return;
2696 
2697 		switch (this->query_widget) {
2698 			case WID_CC_RATE:
2699 				_custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2700 				break;
2701 
2702 			case WID_CC_SEPARATOR: // Thousands separator
2703 				_custom_currency.separator = str;
2704 				break;
2705 
2706 			case WID_CC_PREFIX:
2707 				_custom_currency.prefix = str;
2708 				break;
2709 
2710 			case WID_CC_SUFFIX:
2711 				_custom_currency.suffix = str;
2712 				break;
2713 
2714 			case WID_CC_YEAR: { // Year to switch to euro
2715 				int val = atoi(str);
2716 
2717 				_custom_currency.to_euro = (val < 2000 ? CF_NOEURO : std::min(val, MAX_YEAR));
2718 				break;
2719 			}
2720 		}
2721 		MarkWholeScreenDirty();
2722 		SetButtonState();
2723 	}
2724 
OnTimeoutCustomCurrencyWindow2725 	void OnTimeout() override
2726 	{
2727 		this->SetDirty();
2728 	}
2729 };
2730 
2731 static const NWidgetPart _nested_cust_currency_widgets[] = {
2732 	NWidget(NWID_HORIZONTAL),
2733 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2734 		NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2735 	EndContainer(),
2736 	NWidget(WWT_PANEL, COLOUR_GREY),
2737 		NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(7, 3, 0),
2738 			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2739 				NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2740 				NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2741 				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2742 				NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2743 			EndContainer(),
2744 			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2745 				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2746 				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2747 				NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2748 			EndContainer(),
2749 			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2750 				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2751 				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2752 				NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2753 			EndContainer(),
2754 			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2755 				NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2756 				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2757 				NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2758 			EndContainer(),
2759 			NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2760 				NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2761 				NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2762 				NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2763 				NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2764 			EndContainer(),
2765 		EndContainer(),
2766 		NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2767 								SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2768 	EndContainer(),
2769 };
2770 
2771 static WindowDesc _cust_currency_desc(
2772 	WDP_CENTER, nullptr, 0, 0,
2773 	WC_CUSTOM_CURRENCY, WC_NONE,
2774 	0,
2775 	_nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2776 );
2777 
2778 /** Open custom currency window. */
ShowCustCurrency()2779 static void ShowCustCurrency()
2780 {
2781 	CloseWindowById(WC_CUSTOM_CURRENCY, 0);
2782 	new CustomCurrencyWindow(&_cust_currency_desc);
2783 }
2784