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