Change: Reword Finance window's Net Profit to Profit
[openttd-github.git] / src / settings_gui.cpp
blobb5e31f7f4f2a42aae585ea3f6654654d8a218c86
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file settings_gui.cpp GUI for settings. */
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 #include "gui.h"
44 #include <vector>
45 #include <iterator>
47 #include "safeguards.h"
48 #include "video/video_driver.hpp"
51 static const StringID _autosave_dropdown[] = {
52 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
53 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
54 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
55 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
56 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
57 INVALID_STRING_ID,
60 static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
62 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd);
64 /**
65 * Get index of the current screen resolution.
66 * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
68 static uint GetCurrentResolutionIndex()
70 auto it = std::find(_resolutions.begin(), _resolutions.end(), Dimension(_screen.width, _screen.height));
71 return std::distance(_resolutions.begin(), it);
74 static void ShowCustCurrency();
76 template <class T>
77 static DropDownList BuildSetDropDownList(int *selected_index, bool allow_selection)
79 int n = T::GetNumSets();
80 *selected_index = T::GetIndexOfUsedSet();
82 DropDownList list;
83 for (int i = 0; i < n; i++) {
84 list.emplace_back(new DropDownListCharStringItem(T::GetSet(i)->name, i, !allow_selection && (*selected_index != i)));
87 return list;
90 DropDownList BuildMusicSetDropDownList(int *selected_index)
92 return BuildSetDropDownList<BaseMusic>(selected_index, true);
95 /** Window for displaying the textfile of a BaseSet. */
96 template <class TBaseSet>
97 struct BaseSetTextfileWindow : public TextfileWindow {
98 const TBaseSet* baseset; ///< View the textfile of this BaseSet.
99 StringID content_type; ///< STR_CONTENT_TYPE_xxx for title.
101 BaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
103 const char *textfile = this->baseset->GetTextfile(file_type);
104 this->LoadTextfile(textfile, BASESET_DIR);
107 void SetStringParameters(int widget) const override
109 if (widget == WID_TF_CAPTION) {
110 SetDParam(0, content_type);
111 SetDParamStr(1, this->baseset->name);
117 * Open the BaseSet version of the textfile window.
118 * @param file_type The type of textfile to display.
119 * @param baseset The BaseSet to use.
120 * @param content_type STR_CONTENT_TYPE_xxx for title.
122 template <class TBaseSet>
123 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type)
125 CloseWindowById(WC_TEXTFILE, file_type);
126 new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
129 std::set<int> _refresh_rates = { 30, 60, 75, 90, 100, 120, 144, 240 };
132 * Add the refresh rate from the config and the refresh rates from all the monitors to
133 * our list of refresh rates shown in the GUI.
135 static void AddCustomRefreshRates()
137 /* Add the refresh rate as selected in the config. */
138 _refresh_rates.insert(_settings_client.gui.refresh_rate);
140 /* Add all the refresh rates of all monitors connected to the machine. */
141 std::vector<int> monitorRates = VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
142 std::copy(monitorRates.begin(), monitorRates.end(), std::inserter(_refresh_rates, _refresh_rates.end()));
145 static const std::map<int, StringID> _scale_labels = {
146 { 100, STR_GAME_OPTIONS_GUI_SCALE_1X },
147 { 125, STR_NULL },
148 { 150, STR_NULL },
149 { 175, STR_NULL },
150 { 200, STR_GAME_OPTIONS_GUI_SCALE_2X },
151 { 225, STR_NULL },
152 { 250, STR_NULL },
153 { 275, STR_NULL },
154 { 300, STR_GAME_OPTIONS_GUI_SCALE_3X },
155 { 325, STR_NULL },
156 { 350, STR_NULL },
157 { 375, STR_NULL },
158 { 400, STR_GAME_OPTIONS_GUI_SCALE_4X },
159 { 425, STR_NULL },
160 { 450, STR_NULL },
161 { 475, STR_NULL },
162 { 500, STR_GAME_OPTIONS_GUI_SCALE_5X },
165 struct GameOptionsWindow : Window {
166 GameSettings *opt;
167 bool reload;
168 int gui_scale;
170 GameOptionsWindow(WindowDesc *desc) : Window(desc)
172 this->opt = &GetGameSettings();
173 this->reload = false;
174 this->gui_scale = _gui_scale;
176 AddCustomRefreshRates();
178 this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS);
179 this->OnInvalidateData(0);
182 void Close() override
184 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
185 CloseWindowByClass(WC_TEXTFILE);
186 if (this->reload) _switch_mode = SM_MENU;
187 this->Window::Close();
191 * Build the dropdown list for a specific widget.
192 * @param widget Widget to build list for
193 * @param selected_index Currently selected item
194 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
196 DropDownList BuildDropDownList(int widget, int *selected_index) const
198 DropDownList list;
199 switch (widget) {
200 case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
201 *selected_index = this->opt->locale.currency;
202 StringID *items = BuildCurrencyDropdown();
203 uint64 disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
205 /* Add non-custom currencies; sorted naturally */
206 for (uint i = 0; i < CURRENCY_END; items++, i++) {
207 if (i == CURRENCY_CUSTOM) continue;
208 list.emplace_back(new DropDownListStringItem(*items, i, HasBit(disabled, i)));
210 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
212 /* Append custom currency at the end */
213 list.emplace_back(new DropDownListItem(-1, false)); // separator line
214 list.emplace_back(new DropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
215 break;
218 case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
219 *selected_index = _settings_client.gui.autosave;
220 const StringID *items = _autosave_dropdown;
221 for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
222 list.emplace_back(new DropDownListStringItem(*items, i, false));
224 break;
227 case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
228 for (uint i = 0; i < _languages.size(); i++) {
229 bool hide_language = IsReleasedVersion() && !_languages[i].IsReasonablyFinished();
230 if (hide_language) continue;
231 bool hide_percentage = IsReleasedVersion() || _languages[i].missing < _settings_client.gui.missing_strings_threshold;
232 auto item = new DropDownListParamStringItem(hide_percentage ? STR_JUST_RAW_STRING : STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE, i, false);
233 if (&_languages[i] == _current_language) {
234 *selected_index = i;
235 item->SetParamStr(0, _languages[i].own_name);
236 } else {
237 /* Especially with sprite-fonts, not all localized
238 * names can be rendered. So instead, we use the
239 * international names for anything but the current
240 * selected language. This avoids showing a few ????
241 * entries in the dropdown list. */
242 item->SetParamStr(0, _languages[i].name);
244 item->SetParam(1, (LANGUAGE_TOTAL_STRINGS - _languages[i].missing) * 100 / LANGUAGE_TOTAL_STRINGS);
245 list.emplace_back(item);
247 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
248 break;
251 case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
252 if (_resolutions.empty()) break;
254 *selected_index = GetCurrentResolutionIndex();
255 for (uint i = 0; i < _resolutions.size(); i++) {
256 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_RESOLUTION_ITEM, i, false);
257 item->SetParam(0, _resolutions[i].width);
258 item->SetParam(1, _resolutions[i].height);
259 list.emplace_back(item);
261 break;
263 case WID_GO_REFRESH_RATE_DROPDOWN: // Setup refresh rate dropdown
264 for (auto it = _refresh_rates.begin(); it != _refresh_rates.end(); it++) {
265 auto i = std::distance(_refresh_rates.begin(), it);
266 if (*it == _settings_client.gui.refresh_rate) *selected_index = i;
267 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, i, false);
268 item->SetParam(0, *it);
269 list.emplace_back(item);
271 break;
273 case WID_GO_BASE_GRF_DROPDOWN:
274 list = BuildSetDropDownList<BaseGraphics>(selected_index, (_game_mode == GM_MENU));
275 break;
277 case WID_GO_BASE_SFX_DROPDOWN:
278 list = BuildSetDropDownList<BaseSounds>(selected_index, (_game_mode == GM_MENU));
279 break;
281 case WID_GO_BASE_MUSIC_DROPDOWN:
282 list = BuildMusicSetDropDownList(selected_index);
283 break;
286 return list;
289 void SetStringParameters(int widget) const override
291 switch (widget) {
292 case WID_GO_CURRENCY_DROPDOWN: SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
293 case WID_GO_AUTOSAVE_DROPDOWN: SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
294 case WID_GO_LANG_DROPDOWN: SetDParamStr(0, _current_language->own_name); break;
295 case WID_GO_BASE_GRF_DROPDOWN: SetDParamStr(0, BaseGraphics::GetUsedSet()->name); break;
296 case WID_GO_BASE_GRF_STATUS: SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
297 case WID_GO_BASE_SFX_DROPDOWN: SetDParamStr(0, BaseSounds::GetUsedSet()->name); break;
298 case WID_GO_BASE_MUSIC_DROPDOWN: SetDParamStr(0, BaseMusic::GetUsedSet()->name); break;
299 case WID_GO_BASE_MUSIC_STATUS: SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
300 case WID_GO_VIDEO_DRIVER_INFO: SetDParamStr(0, VideoDriver::GetInstance()->GetInfoString()); break;
301 case WID_GO_REFRESH_RATE_DROPDOWN: SetDParam(0, _settings_client.gui.refresh_rate); break;
302 case WID_GO_RESOLUTION_DROPDOWN: {
303 auto current_resolution = GetCurrentResolutionIndex();
305 if (current_resolution == _resolutions.size()) {
306 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER);
307 } else {
308 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM);
309 SetDParam(1, _resolutions[current_resolution].width);
310 SetDParam(2, _resolutions[current_resolution].height);
312 break;
317 void DrawWidget(const Rect &r, int widget) const override
319 switch (widget) {
320 case WID_GO_BASE_GRF_DESCRIPTION:
321 SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
322 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
323 break;
325 case WID_GO_BASE_SFX_DESCRIPTION:
326 SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
327 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
328 break;
330 case WID_GO_BASE_MUSIC_DESCRIPTION:
331 SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
332 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
333 break;
335 case WID_GO_GUI_SCALE:
336 DrawSliderWidget(r, MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE, this->gui_scale, _scale_labels);
337 break;
339 case WID_GO_BASE_SFX_VOLUME:
340 DrawSliderWidget(r, 0, INT8_MAX, _settings_client.music.effect_vol, {});
341 break;
343 case WID_GO_BASE_MUSIC_VOLUME:
344 DrawSliderWidget(r, 0, INT8_MAX, _settings_client.music.music_vol, {});
345 break;
349 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
351 switch (widget) {
352 case WID_GO_BASE_GRF_DESCRIPTION:
353 /* Find the biggest description for the default size. */
354 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
355 SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
356 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
358 break;
360 case WID_GO_BASE_GRF_STATUS:
361 /* Find the biggest description for the default size. */
362 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
363 uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
364 if (invalid_files == 0) continue;
366 SetDParam(0, invalid_files);
367 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
369 break;
371 case WID_GO_BASE_SFX_DESCRIPTION:
372 /* Find the biggest description for the default size. */
373 for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
374 SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
375 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
377 break;
379 case WID_GO_BASE_MUSIC_DESCRIPTION:
380 /* Find the biggest description for the default size. */
381 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
382 SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
383 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
385 break;
387 case WID_GO_BASE_MUSIC_STATUS:
388 /* Find the biggest description for the default size. */
389 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
390 uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
391 if (invalid_files == 0) continue;
393 SetDParam(0, invalid_files);
394 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
396 break;
398 default: {
399 int selected;
400 DropDownList list = this->BuildDropDownList(widget, &selected);
401 if (!list.empty()) {
402 /* Find the biggest item for the default size. */
403 for (const auto &ddli : list) {
404 Dimension string_dim;
405 int width = ddli->Width();
406 string_dim.width = width + padding.width;
407 string_dim.height = ddli->Height(width) + padding.height;
408 *size = maxdim(*size, string_dim);
415 void OnClick(Point pt, int widget, int click_count) override
417 if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
418 if (BaseGraphics::GetUsedSet() == nullptr) return;
420 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
421 return;
423 if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
424 if (BaseSounds::GetUsedSet() == nullptr) return;
426 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
427 return;
429 if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
430 if (BaseMusic::GetUsedSet() == nullptr) return;
432 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
433 return;
435 switch (widget) {
436 case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
437 /* try to toggle full-screen on/off */
438 if (!ToggleFullScreen(!_fullscreen)) {
439 ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
441 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
442 this->SetWidgetDirty(WID_GO_FULLSCREEN_BUTTON);
443 break;
445 case WID_GO_VIDEO_ACCEL_BUTTON:
446 _video_hw_accel = !_video_hw_accel;
447 ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART, INVALID_STRING_ID, WL_INFO);
448 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
449 this->SetWidgetDirty(WID_GO_VIDEO_ACCEL_BUTTON);
450 #ifndef __APPLE__
451 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
452 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
453 #endif
454 break;
456 case WID_GO_VIDEO_VSYNC_BUTTON:
457 if (!_video_hw_accel) break;
459 _video_vsync = !_video_vsync;
460 VideoDriver::GetInstance()->ToggleVsync(_video_vsync);
462 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
463 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
464 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
465 this->SetWidgetDirty(WID_GO_REFRESH_RATE_DROPDOWN);
466 break;
468 case WID_GO_GUI_SCALE_BEVEL_BUTTON: {
469 _settings_client.gui.scale_bevels = !_settings_client.gui.scale_bevels;
471 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels);
472 this->SetDirty();
474 SetupWidgetDimensions();
475 ReInitAllWindows(true);
476 break;
479 case WID_GO_GUI_SCALE:
480 if (ClickSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE, this->gui_scale)) {
481 if (!_ctrl_pressed) this->gui_scale = ((this->gui_scale + 12) / 25) * 25;
482 this->SetWidgetDirty(widget);
485 if (click_count > 0) this->mouse_capture_widget = widget;
486 break;
488 case WID_GO_GUI_SCALE_AUTO:
490 if (_gui_scale_cfg == -1) {
491 _gui_scale_cfg = _gui_scale;
492 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, false);
493 } else {
494 _gui_scale_cfg = -1;
495 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, true);
496 if (AdjustGUIZoom(false)) ReInitAllWindows(true);
497 this->gui_scale = _gui_scale;
499 this->SetWidgetDirty(widget);
500 break;
503 case WID_GO_BASE_SFX_VOLUME:
504 case WID_GO_BASE_MUSIC_VOLUME: {
505 byte &vol = (widget == WID_GO_BASE_MUSIC_VOLUME) ? _settings_client.music.music_vol : _settings_client.music.effect_vol;
506 if (ClickSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, 0, INT8_MAX, vol)) {
507 if (widget == WID_GO_BASE_MUSIC_VOLUME) MusicDriver::GetInstance()->SetVolume(vol);
508 this->SetWidgetDirty(widget);
509 SetWindowClassesDirty(WC_MUSIC_WINDOW);
512 if (click_count > 0) this->mouse_capture_widget = widget;
513 break;
516 case WID_GO_BASE_MUSIC_JUKEBOX: {
517 ShowMusicWindow();
518 break;
521 default: {
522 int selected;
523 DropDownList list = this->BuildDropDownList(widget, &selected);
524 if (!list.empty()) {
525 ShowDropDownList(this, std::move(list), selected, widget);
526 } else {
527 if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
529 break;
534 void OnMouseLoop() override
536 if (_left_button_down || this->gui_scale == _gui_scale) return;
538 _gui_scale_cfg = this->gui_scale;
540 if (AdjustGUIZoom(false)) {
541 ReInitAllWindows(true);
542 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, false);
543 this->SetDirty();
548 * Set the base media set.
549 * @param index the index of the media set
550 * @tparam T class of media set
552 template <class T>
553 void SetMediaSet(int index)
555 if (_game_mode == GM_MENU) {
556 auto name = T::GetSet(index)->name;
558 T::ini_set = name;
560 T::SetSet(name);
561 this->reload = true;
562 this->InvalidateData();
566 void OnDropdownSelect(int widget, int index) override
568 switch (widget) {
569 case WID_GO_CURRENCY_DROPDOWN: // Currency
570 if (index == CURRENCY_CUSTOM) ShowCustCurrency();
571 this->opt->locale.currency = index;
572 ReInitAllWindows(false);
573 break;
575 case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
576 _settings_client.gui.autosave = index;
577 this->SetDirty();
578 break;
580 case WID_GO_LANG_DROPDOWN: // Change interface language
581 ReadLanguagePack(&_languages[index]);
582 CloseWindowByClass(WC_QUERY_STRING);
583 CheckForMissingGlyphs();
584 ClearAllCachedNames();
585 UpdateAllVirtCoords();
586 CheckBlitter();
587 ReInitAllWindows(false);
588 break;
590 case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
591 if ((uint)index < _resolutions.size() && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
592 this->SetDirty();
594 break;
596 case WID_GO_REFRESH_RATE_DROPDOWN: {
597 _settings_client.gui.refresh_rate = *std::next(_refresh_rates.begin(), index);
598 if (_settings_client.gui.refresh_rate > 60) {
599 /* Show warning to the user that this refresh rate might not be suitable on
600 * larger maps with many NewGRFs and vehicles. */
601 ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING, INVALID_STRING_ID, WL_INFO);
603 break;
606 case WID_GO_BASE_GRF_DROPDOWN:
607 this->SetMediaSet<BaseGraphics>(index);
608 break;
610 case WID_GO_BASE_SFX_DROPDOWN:
611 this->SetMediaSet<BaseSounds>(index);
612 break;
614 case WID_GO_BASE_MUSIC_DROPDOWN:
615 ChangeMusicSet(index);
616 break;
621 * Some data on this window has become invalid.
622 * @param data Information about the changed data. @see GameOptionsInvalidationData
623 * @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.
625 void OnInvalidateData(int data = 0, bool gui_scope = true) override
627 if (!gui_scope) return;
628 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
629 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
630 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
632 #ifndef __APPLE__
633 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
634 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
635 #endif
637 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, _gui_scale_cfg == -1);
638 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels);
640 bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
641 this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
643 for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
644 this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->GetTextfile(tft) == nullptr);
645 this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->GetTextfile(tft) == nullptr);
646 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->GetTextfile(tft) == nullptr);
649 missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
650 this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
654 static const NWidgetPart _nested_game_options_widgets[] = {
655 NWidget(NWID_HORIZONTAL),
656 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
657 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
658 EndContainer(),
659 NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
660 NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
661 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
662 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
663 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),
664 EndContainer(),
665 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
666 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),
667 EndContainer(),
668 EndContainer(),
670 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
671 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
672 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),
673 EndContainer(),
674 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_FRAME, STR_NULL),
675 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
676 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_GUI_SCALE), SetMinimalSize(67, 0), SetMinimalTextLines(1, 12 + WidgetDimensions::unscaled.vsep_normal, FS_SMALL), SetFill(0, 0), SetDataTip(0x0, STR_GAME_OPTIONS_GUI_SCALE_TOOLTIP),
677 NWidget(NWID_HORIZONTAL),
678 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_AUTO, STR_NULL),
679 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
680 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_SCALE_AUTO), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_SCALE_AUTO_TOOLTIP),
681 EndContainer(),
682 NWidget(NWID_HORIZONTAL),
683 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_BEVELS, STR_NULL),
684 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
685 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_SCALE_BEVEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_SCALE_BEVELS_TOOLTIP),
686 EndContainer(),
687 EndContainer(),
688 EndContainer(),
689 EndContainer(),
690 EndContainer(),
692 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GRAPHICS, STR_NULL), SetPadding(0, 10, 0, 10),
693 NWidget(NWID_HORIZONTAL),
694 NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
695 NWidget(NWID_HORIZONTAL),
696 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12),SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
697 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
698 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(200, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP),
699 EndContainer(),
700 NWidget(NWID_HORIZONTAL),
701 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE, STR_NULL),
702 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
703 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),
704 EndContainer(),
705 NWidget(NWID_HORIZONTAL),
706 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
707 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
708 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
709 EndContainer(),
710 NWidget(NWID_HORIZONTAL),
711 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION, STR_NULL),
712 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
713 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_ACCEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP),
714 EndContainer(),
715 #ifndef __APPLE__
716 NWidget(NWID_HORIZONTAL),
717 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_VSYNC, STR_NULL),
718 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
719 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_VSYNC_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP),
720 EndContainer(),
721 #endif
722 NWidget(NWID_HORIZONTAL),
723 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_VIDEO_DRIVER_INFO), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_DRIVER_INFO, STR_NULL),
724 EndContainer(),
725 EndContainer(),
726 EndContainer(),
727 EndContainer(),
729 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
730 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
731 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
732 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
733 EndContainer(),
734 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),
735 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
736 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),
737 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),
738 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),
739 EndContainer(),
740 EndContainer(),
742 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
743 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
744 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
745 NWidget(NWID_SPACER), SetMinimalSize(150, 12), SetFill(1, 0),
746 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),
747 EndContainer(),
748 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),
749 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
750 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),
751 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),
752 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),
753 EndContainer(),
754 EndContainer(),
756 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
757 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
758 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
759 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
760 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),
761 EndContainer(),
762 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
763 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),
764 NWidget(NWID_VERTICAL), SetPIP(0, 0, 0),
765 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_JUKEBOX), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_MUSIC, STR_TOOLBAR_TOOLTIP_SHOW_SOUND_MUSIC_WINDOW), SetPadding(6, 0, 6, 0),
766 EndContainer(),
767 EndContainer(),
768 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
769 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),
770 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),
771 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),
772 EndContainer(),
773 EndContainer(),
774 EndContainer(),
777 static WindowDesc _game_options_desc(
778 WDP_CENTER, "settings_game", 0, 0,
779 WC_GAME_OPTIONS, WC_NONE,
781 _nested_game_options_widgets, lengthof(_nested_game_options_widgets)
784 /** Open the game options window. */
785 void ShowGameOptions()
787 CloseWindowByClass(WC_GAME_OPTIONS);
788 new GameOptionsWindow(&_game_options_desc);
791 static int SETTING_HEIGHT = 11; ///< Height of a single setting in the tree view in pixels
794 * Flags for #SettingEntry
795 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
797 enum SettingEntryFlags {
798 SEF_LEFT_DEPRESSED = 0x01, ///< Of a numeric setting entry, the left button is depressed
799 SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
800 SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
802 SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
803 SEF_FILTERED = 0x08, ///< Entry is hidden by the string filter
806 /** How the list of advanced settings is filtered. */
807 enum RestrictionMode {
808 RM_BASIC, ///< Display settings associated to the "basic" list.
809 RM_ADVANCED, ///< Display settings associated to the "advanced" list.
810 RM_ALL, ///< List all settings regardless of the default/newgame/... values.
811 RM_CHANGED_AGAINST_DEFAULT, ///< Show only settings which are different compared to default values.
812 RM_CHANGED_AGAINST_NEW, ///< Show only settings which are different compared to the user's new game setting values.
813 RM_END, ///< End for iteration.
815 DECLARE_POSTFIX_INCREMENT(RestrictionMode)
817 /** Filter for settings list. */
818 struct SettingFilter {
819 StringFilter string; ///< Filter string.
820 RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
821 bool type_hides; ///< Whether the type hides filtered strings.
822 RestrictionMode mode; ///< Filter based on category.
823 SettingType type; ///< Filter based on type.
826 /** Data structure describing a single setting in a tab */
827 struct BaseSettingEntry {
828 byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
829 byte level; ///< Nesting level of this setting entry
831 BaseSettingEntry() : flags(0), level(0) {}
832 virtual ~BaseSettingEntry() {}
834 virtual void Init(byte level = 0);
835 virtual void FoldAll() {}
836 virtual void UnFoldAll() {}
837 virtual void ResetAll() = 0;
840 * Set whether this is the last visible entry of the parent node.
841 * @param last_field Value to set
843 void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
845 virtual uint Length() const = 0;
846 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
847 virtual bool IsVisible(const BaseSettingEntry *item) const;
848 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
849 virtual uint GetMaxHelpHeight(int maxw) { return 0; }
852 * Check whether an entry is hidden due to filters
853 * @return true if hidden.
855 bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
857 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
859 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;
861 protected:
862 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
865 /** Standard setting */
866 struct SettingEntry : BaseSettingEntry {
867 const char *name; ///< Name of the setting
868 const IntSettingDesc *setting; ///< Setting description of the setting
870 SettingEntry(const char *name);
872 virtual void Init(byte level = 0);
873 virtual void ResetAll();
874 virtual uint Length() const;
875 virtual uint GetMaxHelpHeight(int maxw);
876 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
878 void SetButtons(byte new_val);
881 * Get the help text of a single setting.
882 * @return The requested help text.
884 inline StringID GetHelpText() const
886 return this->setting->str_help;
889 void SetValueDParams(uint first_param, int32 value) const;
891 protected:
892 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
894 private:
895 bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
898 /** Containers for BaseSettingEntry */
899 struct SettingsContainer {
900 typedef std::vector<BaseSettingEntry*> EntryVector;
901 EntryVector entries; ///< Settings on this page
903 template<typename T>
904 T *Add(T *item)
906 this->entries.push_back(item);
907 return item;
910 void Init(byte level = 0);
911 void ResetAll();
912 void FoldAll();
913 void UnFoldAll();
915 uint Length() const;
916 void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
917 bool IsVisible(const BaseSettingEntry *item) const;
918 BaseSettingEntry *FindEntry(uint row, uint *cur_row);
919 uint GetMaxHelpHeight(int maxw);
921 bool UpdateFilterState(SettingFilter &filter, bool force_visible);
923 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;
926 /** Data structure describing one page of settings in the settings window. */
927 struct SettingsPage : BaseSettingEntry, SettingsContainer {
928 StringID title; ///< Title of the sub-page
929 bool folded; ///< Sub-page is folded (not visible except for its title)
931 SettingsPage(StringID title);
933 virtual void Init(byte level = 0);
934 virtual void ResetAll();
935 virtual void FoldAll();
936 virtual void UnFoldAll();
938 virtual uint Length() const;
939 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
940 virtual bool IsVisible(const BaseSettingEntry *item) const;
941 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
942 virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
944 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
946 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;
948 protected:
949 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
952 /* == BaseSettingEntry methods == */
955 * Initialization of a setting entry
956 * @param level Page nesting level of this entry
958 void BaseSettingEntry::Init(byte level)
960 this->level = level;
964 * Check whether an entry is visible and not folded or filtered away.
965 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
966 * @param item Entry to search for.
967 * @return true if entry is visible.
969 bool BaseSettingEntry::IsVisible(const BaseSettingEntry *item) const
971 if (this->IsFiltered()) return false;
972 return this == item;
976 * Find setting entry at row \a row_num
977 * @param row_num Index of entry to return
978 * @param cur_row Current row number
979 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
981 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
983 if (this->IsFiltered()) return nullptr;
984 if (row_num == *cur_row) return this;
985 (*cur_row)++;
986 return nullptr;
990 * Draw a row in the settings panel.
992 * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
993 * 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.
994 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
996 * The \a parent_last parameter ensures that the vertical lines at the left are
997 * only drawn when another entry follows, that it prevents output like
998 * \verbatim
999 * |-- setting
1000 * |-- (-) - Title
1001 * | |-- setting
1002 * | |-- setting
1003 * \endverbatim
1004 * The left-most vertical line is not wanted. It is prevented by setting the
1005 * appropriate bit in the \a parent_last parameter.
1007 * @param settings_ptr Pointer to current values of all settings
1008 * @param left Left-most position in window/panel to start drawing \a first_row
1009 * @param right Right-most x position to draw strings at.
1010 * @param y Upper-most position in window/panel to start drawing \a first_row
1011 * @param first_row First row number to draw
1012 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1013 * @param selected Selected entry by the user.
1014 * @param cur_row Current row number (internal variable)
1015 * @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)
1016 * @return Row number of the next row to draw
1018 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
1020 if (this->IsFiltered()) return cur_row;
1021 if (cur_row >= max_row) return cur_row;
1023 bool rtl = _current_text_dir == TD_RTL;
1024 int offset = (rtl ? -(int)_circle_size.width : _circle_size.width) / 2;
1025 int level_width = rtl ? -WidgetDimensions::scaled.hsep_indent : WidgetDimensions::scaled.hsep_indent;
1027 int x = rtl ? right : left;
1028 if (cur_row >= first_row) {
1029 int colour = _colour_gradient[COLOUR_ORANGE][4];
1030 y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
1032 /* Draw vertical for parent nesting levels */
1033 for (uint lvl = 0; lvl < this->level; lvl++) {
1034 if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
1035 x += level_width;
1037 /* draw own |- prefix */
1038 int halfway_y = y + SETTING_HEIGHT / 2;
1039 int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
1040 GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
1041 /* Small horizontal line from the last vertical line */
1042 GfxDrawLine(x + offset, halfway_y, x + level_width - WidgetDimensions::scaled.hsep_normal, halfway_y, colour);
1043 x += level_width;
1045 this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
1047 cur_row++;
1049 return cur_row;
1052 /* == SettingEntry methods == */
1055 * Constructor for a single setting in the 'advanced settings' window
1056 * @param name Name of the setting in the setting table
1058 SettingEntry::SettingEntry(const char *name)
1060 this->name = name;
1061 this->setting = nullptr;
1065 * Initialization of a setting entry
1066 * @param level Page nesting level of this entry
1068 void SettingEntry::Init(byte level)
1070 BaseSettingEntry::Init(level);
1071 this->setting = GetSettingFromName(this->name)->AsIntSetting();
1074 /* Sets the given setting entry to its default value */
1075 void SettingEntry::ResetAll()
1077 SetSettingValue(this->setting, this->setting->def);
1081 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
1082 * @param new_val New value for the button flags
1083 * @see SettingEntryFlags
1085 void SettingEntry::SetButtons(byte new_val)
1087 assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
1088 this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
1091 /** Return number of rows needed to display the (filtered) entry */
1092 uint SettingEntry::Length() const
1094 return this->IsFiltered() ? 0 : 1;
1098 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
1099 * @param maxw Maximal width of a line help text.
1100 * @return Biggest height needed to display any help text of this node (and its descendants).
1102 uint SettingEntry::GetMaxHelpHeight(int maxw)
1104 return GetStringHeight(this->GetHelpText(), maxw);
1108 * Checks whether an entry shall be made visible based on the restriction mode.
1109 * @param mode The current status of the restriction drop down box.
1110 * @return true if the entry shall be visible.
1112 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode) const
1114 /* There shall not be any restriction, i.e. all settings shall be visible. */
1115 if (mode == RM_ALL) return true;
1117 const IntSettingDesc *sd = this->setting;
1119 if (mode == RM_BASIC) return (this->setting->cat & SC_BASIC_LIST) != 0;
1120 if (mode == RM_ADVANCED) return (this->setting->cat & SC_ADVANCED_LIST) != 0;
1122 /* Read the current value. */
1123 const void *object = ResolveObject(&GetGameSettings(), sd);
1124 int64 current_value = sd->Read(object);
1125 int64 filter_value;
1127 if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1128 /* This entry shall only be visible, if the value deviates from its default value. */
1130 /* Read the default value. */
1131 filter_value = sd->def;
1132 } else {
1133 assert(mode == RM_CHANGED_AGAINST_NEW);
1134 /* This entry shall only be visible, if the value deviates from
1135 * its value is used when starting a new game. */
1137 /* Make sure we're not comparing the new game settings against itself. */
1138 assert(&GetGameSettings() != &_settings_newgame);
1140 /* Read the new game's value. */
1141 filter_value = sd->Read(ResolveObject(&_settings_newgame, sd));
1144 return current_value != filter_value;
1148 * Update the filter state.
1149 * @param filter Filter
1150 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1151 * @return true if item remains visible
1153 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1155 CLRBITS(this->flags, SEF_FILTERED);
1157 bool visible = true;
1159 const IntSettingDesc *sd = this->setting;
1160 if (!force_visible && !filter.string.IsEmpty()) {
1161 /* Process the search text filter for this item. */
1162 filter.string.ResetState();
1164 SetDParam(0, STR_EMPTY);
1165 filter.string.AddLine(sd->str);
1166 filter.string.AddLine(this->GetHelpText());
1168 visible = filter.string.GetState();
1171 if (visible) {
1172 if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1173 filter.type_hides = true;
1174 visible = false;
1176 if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1177 while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1178 visible = false;
1182 if (!visible) SETBITS(this->flags, SEF_FILTERED);
1183 return visible;
1186 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd)
1188 if ((sd->flags & SF_PER_COMPANY) != 0) {
1189 if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1190 return &Company::Get(_local_company)->settings;
1192 return &_settings_client.company;
1194 return settings_ptr;
1198 * Set the DParams for drawing the value of a setting.
1199 * @param first_param First DParam to use
1200 * @param value Setting value to set params for.
1202 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1204 if (this->setting->IsBoolSetting()) {
1205 SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1206 } else {
1207 if ((this->setting->flags & SF_GUI_DROPDOWN) != 0) {
1208 SetDParam(first_param++, this->setting->str_val - this->setting->min + value);
1209 } else if ((this->setting->flags & SF_GUI_NEGATIVE_IS_SPECIAL) != 0) {
1210 SetDParam(first_param++, this->setting->str_val + ((value >= 0) ? 1 : 0));
1211 value = abs(value);
1212 } else {
1213 SetDParam(first_param++, this->setting->str_val + ((value == 0 && (this->setting->flags & SF_GUI_0_IS_SPECIAL) != 0) ? 1 : 0));
1215 SetDParam(first_param++, value);
1220 * Function to draw setting value (button + text + current value)
1221 * @param settings_ptr Pointer to current values of all settings
1222 * @param left Left-most position in window/panel to start drawing
1223 * @param right Right-most position in window/panel to draw
1224 * @param y Upper-most position in window/panel to start drawing
1225 * @param highlight Highlight entry.
1227 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1229 const IntSettingDesc *sd = this->setting;
1230 int state = this->flags & SEF_BUTTONS_MASK;
1232 bool rtl = _current_text_dir == TD_RTL;
1233 uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1234 uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide);
1235 uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide : 0);
1236 uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1238 /* We do not allow changes of some items when we are a client in a networkgame */
1239 bool editable = sd->IsEditable();
1241 SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1242 int32 value = sd->Read(ResolveObject(settings_ptr, sd));
1243 if (sd->IsBoolSetting()) {
1244 /* Draw checkbox for boolean-value either on/off */
1245 DrawBoolButton(buttons_left, button_y, value != 0, editable);
1246 } else if ((sd->flags & SF_GUI_DROPDOWN) != 0) {
1247 /* Draw [v] button for settings of an enum-type */
1248 DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1249 } else {
1250 /* Draw [<][>] boxes for settings of an integer-type */
1251 DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1252 editable && value != (sd->flags & SF_GUI_0_IS_SPECIAL ? 0 : sd->min), editable && (uint32)value != sd->max);
1254 this->SetValueDParams(1, value);
1255 DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sd->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1258 /* == SettingsContainer methods == */
1261 * Initialization of an entire setting page
1262 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1264 void SettingsContainer::Init(byte level)
1266 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1267 (*it)->Init(level);
1271 /** Resets all settings to their default values */
1272 void SettingsContainer::ResetAll()
1274 for (auto settings_entry : this->entries) {
1275 settings_entry->ResetAll();
1279 /** Recursively close all folds of sub-pages */
1280 void SettingsContainer::FoldAll()
1282 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1283 (*it)->FoldAll();
1287 /** Recursively open all folds of sub-pages */
1288 void SettingsContainer::UnFoldAll()
1290 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1291 (*it)->UnFoldAll();
1296 * Recursively accumulate the folding state of the tree.
1297 * @param[in,out] all_folded Set to false, if one entry is not folded.
1298 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1300 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1302 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1303 (*it)->GetFoldingState(all_folded, all_unfolded);
1308 * Update the filter state.
1309 * @param filter Filter
1310 * @param force_visible Whether to force all items visible, no matter what
1311 * @return true if item remains visible
1313 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1315 bool visible = false;
1316 bool first_visible = true;
1317 for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1318 visible |= (*it)->UpdateFilterState(filter, force_visible);
1319 (*it)->SetLastField(first_visible);
1320 if (visible && first_visible) first_visible = false;
1322 return visible;
1327 * Check whether an entry is visible and not folded or filtered away.
1328 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1329 * @param item Entry to search for.
1330 * @return true if entry is visible.
1332 bool SettingsContainer::IsVisible(const BaseSettingEntry *item) const
1334 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1335 if ((*it)->IsVisible(item)) return true;
1337 return false;
1340 /** Return number of rows needed to display the whole page */
1341 uint SettingsContainer::Length() const
1343 uint length = 0;
1344 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1345 length += (*it)->Length();
1347 return length;
1351 * Find the setting entry at row number \a row_num
1352 * @param row_num Index of entry to return
1353 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1354 * @return The requested setting entry or \c nullptr if it does not exist
1356 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1358 BaseSettingEntry *pe = nullptr;
1359 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1360 pe = (*it)->FindEntry(row_num, cur_row);
1361 if (pe != nullptr) {
1362 break;
1365 return pe;
1369 * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1370 * @param maxw Maximal width of a line help text.
1371 * @return Biggest height needed to display any help text of this (sub-)tree.
1373 uint SettingsContainer::GetMaxHelpHeight(int maxw)
1375 uint biggest = 0;
1376 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1377 biggest = std::max(biggest, (*it)->GetMaxHelpHeight(maxw));
1379 return biggest;
1384 * Draw a row in the settings panel.
1386 * @param settings_ptr Pointer to current values of all settings
1387 * @param left Left-most position in window/panel to start drawing \a first_row
1388 * @param right Right-most x position to draw strings at.
1389 * @param y Upper-most position in window/panel to start drawing \a first_row
1390 * @param first_row First row number to draw
1391 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1392 * @param selected Selected entry by the user.
1393 * @param cur_row Current row number (internal variable)
1394 * @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)
1395 * @return Row number of the next row to draw
1397 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
1399 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1400 cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1401 if (cur_row >= max_row) {
1402 break;
1405 return cur_row;
1408 /* == SettingsPage methods == */
1411 * Constructor for a sub-page in the 'advanced settings' window
1412 * @param title Title of the sub-page
1414 SettingsPage::SettingsPage(StringID title)
1416 this->title = title;
1417 this->folded = true;
1421 * Initialization of an entire setting page
1422 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1424 void SettingsPage::Init(byte level)
1426 BaseSettingEntry::Init(level);
1427 SettingsContainer::Init(level + 1);
1430 /** Resets all settings to their default values */
1431 void SettingsPage::ResetAll()
1433 for (auto settings_entry : this->entries) {
1434 settings_entry->ResetAll();
1438 /** Recursively close all (filtered) folds of sub-pages */
1439 void SettingsPage::FoldAll()
1441 if (this->IsFiltered()) return;
1442 this->folded = true;
1444 SettingsContainer::FoldAll();
1447 /** Recursively open all (filtered) folds of sub-pages */
1448 void SettingsPage::UnFoldAll()
1450 if (this->IsFiltered()) return;
1451 this->folded = false;
1453 SettingsContainer::UnFoldAll();
1457 * Recursively accumulate the folding state of the (filtered) tree.
1458 * @param[in,out] all_folded Set to false, if one entry is not folded.
1459 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1461 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1463 if (this->IsFiltered()) return;
1465 if (this->folded) {
1466 all_unfolded = false;
1467 } else {
1468 all_folded = false;
1471 SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1475 * Update the filter state.
1476 * @param filter Filter
1477 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1478 * @return true if item remains visible
1480 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1482 if (!force_visible && !filter.string.IsEmpty()) {
1483 filter.string.ResetState();
1484 filter.string.AddLine(this->title);
1485 force_visible = filter.string.GetState();
1488 bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1489 if (visible) {
1490 CLRBITS(this->flags, SEF_FILTERED);
1491 } else {
1492 SETBITS(this->flags, SEF_FILTERED);
1494 return visible;
1498 * Check whether an entry is visible and not folded or filtered away.
1499 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1500 * @param item Entry to search for.
1501 * @return true if entry is visible.
1503 bool SettingsPage::IsVisible(const BaseSettingEntry *item) const
1505 if (this->IsFiltered()) return false;
1506 if (this == item) return true;
1507 if (this->folded) return false;
1509 return SettingsContainer::IsVisible(item);
1512 /** Return number of rows needed to display the (filtered) entry */
1513 uint SettingsPage::Length() const
1515 if (this->IsFiltered()) return 0;
1516 if (this->folded) return 1; // Only displaying the title
1518 return 1 + SettingsContainer::Length();
1522 * Find setting entry at row \a row_num
1523 * @param row_num Index of entry to return
1524 * @param cur_row Current row number
1525 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1527 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1529 if (this->IsFiltered()) return nullptr;
1530 if (row_num == *cur_row) return this;
1531 (*cur_row)++;
1532 if (this->folded) return nullptr;
1534 return SettingsContainer::FindEntry(row_num, cur_row);
1538 * Draw a row in the settings panel.
1540 * @param settings_ptr Pointer to current values of all settings
1541 * @param left Left-most position in window/panel to start drawing \a first_row
1542 * @param right Right-most x position to draw strings at.
1543 * @param y Upper-most position in window/panel to start drawing \a first_row
1544 * @param first_row First row number to draw
1545 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1546 * @param selected Selected entry by the user.
1547 * @param cur_row Current row number (internal variable)
1548 * @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)
1549 * @return Row number of the next row to draw
1551 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
1553 if (this->IsFiltered()) return cur_row;
1554 if (cur_row >= max_row) return cur_row;
1556 cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1558 if (!this->folded) {
1559 if (this->flags & SEF_LAST_FIELD) {
1560 assert(this->level < 8 * sizeof(parent_last));
1561 SetBit(parent_last, this->level); // Add own last-field state
1564 cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1567 return cur_row;
1571 * Function to draw setting value (button + text + current value)
1572 * @param settings_ptr Pointer to current values of all settings
1573 * @param left Left-most position in window/panel to start drawing
1574 * @param right Right-most position in window/panel to draw
1575 * @param y Upper-most position in window/panel to start drawing
1576 * @param highlight Highlight entry.
1578 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1580 bool rtl = _current_text_dir == TD_RTL;
1581 DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1582 DrawString(rtl ? left : left + _circle_size.width + WidgetDimensions::scaled.hsep_normal, rtl ? right - _circle_size.width - WidgetDimensions::scaled.hsep_normal : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1585 /** Construct settings tree */
1586 static SettingsContainer &GetSettingsTree()
1588 static SettingsContainer *main = nullptr;
1590 if (main == nullptr)
1592 /* Build up the dynamic settings-array only once per OpenTTD session */
1593 main = new SettingsContainer();
1595 SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1597 localisation->Add(new SettingEntry("locale.units_velocity"));
1598 localisation->Add(new SettingEntry("locale.units_power"));
1599 localisation->Add(new SettingEntry("locale.units_weight"));
1600 localisation->Add(new SettingEntry("locale.units_volume"));
1601 localisation->Add(new SettingEntry("locale.units_force"));
1602 localisation->Add(new SettingEntry("locale.units_height"));
1603 localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1606 SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1608 graphics->Add(new SettingEntry("gui.zoom_min"));
1609 graphics->Add(new SettingEntry("gui.zoom_max"));
1610 graphics->Add(new SettingEntry("gui.sprite_zoom_min"));
1611 graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1612 graphics->Add(new SettingEntry("gui.linkgraph_colours"));
1613 graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1616 SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1618 sound->Add(new SettingEntry("sound.click_beep"));
1619 sound->Add(new SettingEntry("sound.confirm"));
1620 sound->Add(new SettingEntry("sound.news_ticker"));
1621 sound->Add(new SettingEntry("sound.news_full"));
1622 sound->Add(new SettingEntry("sound.new_year"));
1623 sound->Add(new SettingEntry("sound.disaster"));
1624 sound->Add(new SettingEntry("sound.vehicle"));
1625 sound->Add(new SettingEntry("sound.ambient"));
1628 SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1630 SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1632 general->Add(new SettingEntry("gui.osk_activation"));
1633 general->Add(new SettingEntry("gui.hover_delay_ms"));
1634 general->Add(new SettingEntry("gui.errmsg_duration"));
1635 general->Add(new SettingEntry("gui.window_snap_radius"));
1636 general->Add(new SettingEntry("gui.window_soft_limit"));
1637 general->Add(new SettingEntry("gui.right_mouse_wnd_close"));
1640 SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1642 viewports->Add(new SettingEntry("gui.auto_scrolling"));
1643 viewports->Add(new SettingEntry("gui.scroll_mode"));
1644 viewports->Add(new SettingEntry("gui.smooth_scroll"));
1645 /* While the horizontal scrollwheel scrolling is written as general code, only
1646 * the cocoa (OSX) driver generates input for it.
1647 * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1648 viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1649 viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1650 #ifdef __APPLE__
1651 /* We might need to emulate a right mouse button on mac */
1652 viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1653 #endif
1654 viewports->Add(new SettingEntry("gui.population_in_label"));
1655 viewports->Add(new SettingEntry("gui.liveries"));
1656 viewports->Add(new SettingEntry("construction.train_signal_side"));
1657 viewports->Add(new SettingEntry("gui.measure_tooltip"));
1658 viewports->Add(new SettingEntry("gui.loading_indicators"));
1659 viewports->Add(new SettingEntry("gui.show_track_reservation"));
1662 SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1664 construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1665 construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1666 construction->Add(new SettingEntry("gui.quick_goto"));
1667 construction->Add(new SettingEntry("gui.default_rail_type"));
1670 interface->Add(new SettingEntry("gui.fast_forward_speed_limit"));
1671 interface->Add(new SettingEntry("gui.autosave"));
1672 interface->Add(new SettingEntry("gui.toolbar_pos"));
1673 interface->Add(new SettingEntry("gui.statusbar_pos"));
1674 interface->Add(new SettingEntry("gui.prefer_teamchat"));
1675 interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1676 interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1677 interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1678 interface->Add(new SettingEntry("gui.show_newgrf_name"));
1681 SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1683 advisors->Add(new SettingEntry("gui.coloured_news_year"));
1684 advisors->Add(new SettingEntry("news_display.general"));
1685 advisors->Add(new SettingEntry("news_display.new_vehicles"));
1686 advisors->Add(new SettingEntry("news_display.accident"));
1687 advisors->Add(new SettingEntry("news_display.accident_other"));
1688 advisors->Add(new SettingEntry("news_display.company_info"));
1689 advisors->Add(new SettingEntry("news_display.acceptance"));
1690 advisors->Add(new SettingEntry("news_display.arrival_player"));
1691 advisors->Add(new SettingEntry("news_display.arrival_other"));
1692 advisors->Add(new SettingEntry("news_display.advice"));
1693 advisors->Add(new SettingEntry("gui.order_review_system"));
1694 advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1695 advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1696 advisors->Add(new SettingEntry("gui.show_finances"));
1697 advisors->Add(new SettingEntry("news_display.economy"));
1698 advisors->Add(new SettingEntry("news_display.subsidies"));
1699 advisors->Add(new SettingEntry("news_display.open"));
1700 advisors->Add(new SettingEntry("news_display.close"));
1701 advisors->Add(new SettingEntry("news_display.production_player"));
1702 advisors->Add(new SettingEntry("news_display.production_other"));
1703 advisors->Add(new SettingEntry("news_display.production_nobody"));
1706 SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1708 company->Add(new SettingEntry("gui.semaphore_build_before"));
1709 company->Add(new SettingEntry("gui.cycle_signal_types"));
1710 company->Add(new SettingEntry("gui.signal_gui_mode"));
1711 company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1712 company->Add(new SettingEntry("gui.auto_remove_signals"));
1713 company->Add(new SettingEntry("gui.new_nonstop"));
1714 company->Add(new SettingEntry("gui.stop_location"));
1715 company->Add(new SettingEntry("gui.starting_colour"));
1716 company->Add(new SettingEntry("company.engine_renew"));
1717 company->Add(new SettingEntry("company.engine_renew_months"));
1718 company->Add(new SettingEntry("company.engine_renew_money"));
1719 company->Add(new SettingEntry("vehicle.servint_ispercent"));
1720 company->Add(new SettingEntry("vehicle.servint_trains"));
1721 company->Add(new SettingEntry("vehicle.servint_roadveh"));
1722 company->Add(new SettingEntry("vehicle.servint_ships"));
1723 company->Add(new SettingEntry("vehicle.servint_aircraft"));
1726 SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1728 accounting->Add(new SettingEntry("economy.inflation"));
1729 accounting->Add(new SettingEntry("difficulty.initial_interest"));
1730 accounting->Add(new SettingEntry("difficulty.max_loan"));
1731 accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1732 accounting->Add(new SettingEntry("difficulty.subsidy_duration"));
1733 accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1734 accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1735 accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1736 accounting->Add(new SettingEntry("difficulty.construction_cost"));
1739 SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1741 SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1743 physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1744 physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1745 physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1746 physics->Add(new SettingEntry("vehicle.freight_trains"));
1747 physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1748 physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1749 physics->Add(new SettingEntry("vehicle.smoke_amount"));
1750 physics->Add(new SettingEntry("vehicle.plane_speed"));
1753 SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1755 routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1756 routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1757 routing->Add(new SettingEntry("pf.reverse_at_signals"));
1758 routing->Add(new SettingEntry("pf.forbid_90_deg"));
1759 routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1760 routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1763 vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1764 vehicles->Add(new SettingEntry("order.serviceathelipad"));
1767 SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1769 limitations->Add(new SettingEntry("construction.command_pause_level"));
1770 limitations->Add(new SettingEntry("construction.autoslope"));
1771 limitations->Add(new SettingEntry("construction.extra_dynamite"));
1772 limitations->Add(new SettingEntry("construction.map_height_limit"));
1773 limitations->Add(new SettingEntry("construction.max_bridge_length"));
1774 limitations->Add(new SettingEntry("construction.max_bridge_height"));
1775 limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1776 limitations->Add(new SettingEntry("station.never_expire_airports"));
1777 limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1778 limitations->Add(new SettingEntry("vehicle.max_trains"));
1779 limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1780 limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1781 limitations->Add(new SettingEntry("vehicle.max_ships"));
1782 limitations->Add(new SettingEntry("vehicle.max_train_length"));
1783 limitations->Add(new SettingEntry("station.station_spread"));
1784 limitations->Add(new SettingEntry("station.distant_join_stations"));
1785 limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1786 limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1787 limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1790 SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1792 disasters->Add(new SettingEntry("difficulty.disasters"));
1793 disasters->Add(new SettingEntry("difficulty.economy"));
1794 disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1795 disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1798 SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1800 genworld->Add(new SettingEntry("game_creation.landscape"));
1801 genworld->Add(new SettingEntry("game_creation.land_generator"));
1802 genworld->Add(new SettingEntry("difficulty.terrain_type"));
1803 genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1804 genworld->Add(new SettingEntry("game_creation.variety"));
1805 genworld->Add(new SettingEntry("game_creation.snow_coverage"));
1806 genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1807 genworld->Add(new SettingEntry("game_creation.desert_coverage"));
1808 genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1809 genworld->Add(new SettingEntry("game_creation.tree_placer"));
1810 genworld->Add(new SettingEntry("vehicle.road_side"));
1811 genworld->Add(new SettingEntry("economy.larger_towns"));
1812 genworld->Add(new SettingEntry("economy.initial_city_size"));
1813 genworld->Add(new SettingEntry("economy.town_layout"));
1814 genworld->Add(new SettingEntry("difficulty.industry_density"));
1815 genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1816 genworld->Add(new SettingEntry("game_creation.ending_year"));
1819 SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1821 SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1823 authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1824 authorities->Add(new SettingEntry("economy.bribe"));
1825 authorities->Add(new SettingEntry("economy.exclusive_rights"));
1826 authorities->Add(new SettingEntry("economy.fund_roads"));
1827 authorities->Add(new SettingEntry("economy.fund_buildings"));
1828 authorities->Add(new SettingEntry("economy.station_noise_level"));
1831 SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1833 towns->Add(new SettingEntry("economy.town_growth_rate"));
1834 towns->Add(new SettingEntry("economy.allow_town_roads"));
1835 towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1836 towns->Add(new SettingEntry("economy.found_town"));
1837 towns->Add(new SettingEntry("economy.town_cargogen_mode"));
1840 SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1842 industries->Add(new SettingEntry("construction.raw_industry_construction"));
1843 industries->Add(new SettingEntry("construction.industry_platform"));
1844 industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1845 industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1846 industries->Add(new SettingEntry("economy.type"));
1847 industries->Add(new SettingEntry("station.serve_neutral_industries"));
1850 SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1852 cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1853 cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1854 cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1855 cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1856 cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1857 cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1858 cdist->Add(new SettingEntry("linkgraph.accuracy"));
1859 cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1860 cdist->Add(new SettingEntry("linkgraph.demand_size"));
1861 cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1864 environment->Add(new SettingEntry("station.modified_catchment"));
1865 environment->Add(new SettingEntry("construction.extra_tree_placement"));
1868 SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1870 SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1872 npc->Add(new SettingEntry("script.settings_profile"));
1873 npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1874 npc->Add(new SettingEntry("script.script_max_memory_megabytes"));
1875 npc->Add(new SettingEntry("difficulty.competitor_speed"));
1876 npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1877 npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1878 npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1879 npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1880 npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1883 ai->Add(new SettingEntry("economy.give_money"));
1884 ai->Add(new SettingEntry("economy.allow_shares"));
1885 ai->Add(new SettingEntry("economy.min_years_for_shares"));
1888 SettingsPage *network = main->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK));
1890 network->Add(new SettingEntry("network.use_relay_service"));
1893 main->Init();
1895 return *main;
1898 static const StringID _game_settings_restrict_dropdown[] = {
1899 STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1900 STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1901 STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1902 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1903 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1905 static_assert(lengthof(_game_settings_restrict_dropdown) == RM_END);
1907 /** Warnings about hidden search results. */
1908 enum WarnHiddenResult {
1909 WHR_NONE, ///< Nothing was filtering matches away.
1910 WHR_CATEGORY, ///< Category setting filtered matches away.
1911 WHR_TYPE, ///< Type setting filtered matches away.
1912 WHR_CATEGORY_TYPE, ///< Both category and type settings filtered matches away.
1916 * Callback function for the reset all settings button
1917 * @param w Window which is calling this callback
1918 * @param confirmed boolean value, true when yes was clicked, false otherwise
1920 static void ResetAllSettingsConfirmationCallback(Window *w, bool confirmed)
1922 if (confirmed) {
1923 GetSettingsTree().ResetAll();
1924 GetSettingsTree().FoldAll();
1925 w->InvalidateData();
1929 /** Window to edit settings of the game. */
1930 struct GameSettingsWindow : Window {
1931 static GameSettings *settings_ptr; ///< Pointer to the game settings being displayed and modified.
1933 SettingEntry *valuewindow_entry; ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
1934 SettingEntry *clicked_entry; ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
1935 SettingEntry *last_clicked; ///< If non-nullptr, pointer to the last clicked setting.
1936 SettingEntry *valuedropdown_entry; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
1937 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
1939 SettingFilter filter; ///< Filter for the list.
1940 QueryString filter_editbox; ///< Filter editbox;
1941 bool manually_changed_folding; ///< Whether the user expanded/collapsed something manually.
1942 WarnHiddenResult warn_missing; ///< Whether and how to warn about missing search results.
1943 int warn_lines; ///< Number of lines used for warning about missing search results.
1945 Scrollbar *vscroll;
1947 GameSettingsWindow(WindowDesc *desc) : Window(desc), filter_editbox(50)
1949 this->warn_missing = WHR_NONE;
1950 this->warn_lines = 0;
1951 this->filter.mode = (RestrictionMode)_settings_client.gui.settings_restriction_mode;
1952 this->filter.min_cat = RM_ALL;
1953 this->filter.type = ST_ALL;
1954 this->filter.type_hides = false;
1955 this->settings_ptr = &GetGameSettings();
1957 GetSettingsTree().FoldAll(); // Close all sub-pages
1959 this->valuewindow_entry = nullptr; // No setting entry for which a entry window is opened
1960 this->clicked_entry = nullptr; // No numeric setting buttons are depressed
1961 this->last_clicked = nullptr;
1962 this->valuedropdown_entry = nullptr;
1963 this->closing_dropdown = false;
1964 this->manually_changed_folding = false;
1966 this->CreateNestedTree();
1967 this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1968 this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS);
1970 this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1971 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
1972 this->SetFocusedWidget(WID_GS_FILTER);
1974 this->InvalidateData();
1977 void OnInit() override
1979 _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1982 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1984 switch (widget) {
1985 case WID_GS_OPTIONSPANEL:
1986 resize->height = SETTING_HEIGHT = std::max({(int)_circle_size.height, SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL}) + WidgetDimensions::scaled.vsep_normal;
1987 resize->width = 1;
1989 size->height = 5 * resize->height + WidgetDimensions::scaled.framerect.Vertical();
1990 break;
1992 case WID_GS_HELP_TEXT: {
1993 static const StringID setting_types[] = {
1994 STR_CONFIG_SETTING_TYPE_CLIENT,
1995 STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1996 STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1998 for (uint i = 0; i < lengthof(setting_types); i++) {
1999 SetDParam(0, setting_types[i]);
2000 size->width = std::max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
2002 size->height = 2 * FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal +
2003 std::max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
2004 break;
2007 case WID_GS_RESTRICT_CATEGORY:
2008 case WID_GS_RESTRICT_TYPE:
2009 size->width = std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
2010 break;
2012 default:
2013 break;
2017 void OnPaint() override
2019 if (this->closing_dropdown) {
2020 this->closing_dropdown = false;
2021 assert(this->valuedropdown_entry != nullptr);
2022 this->valuedropdown_entry->SetButtons(0);
2023 this->valuedropdown_entry = nullptr;
2026 /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
2027 const Rect panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext);
2028 StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
2029 int new_warn_lines;
2030 if (this->warn_missing == WHR_NONE) {
2031 new_warn_lines = 0;
2032 } else {
2033 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2034 new_warn_lines = GetStringLineCount(warn_str, panel.Width());
2036 if (this->warn_lines != new_warn_lines) {
2037 this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
2038 this->warn_lines = new_warn_lines;
2041 this->DrawWidgets();
2043 /* Draw the 'some search results are hidden' notice. */
2044 if (this->warn_missing != WHR_NONE) {
2045 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2046 DrawStringMultiLine(panel.WithHeight(this->warn_lines * FONT_HEIGHT_NORMAL), warn_str, TC_FROMSTRING, SA_CENTER);
2050 void SetStringParameters(int widget) const override
2052 switch (widget) {
2053 case WID_GS_RESTRICT_DROPDOWN:
2054 SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
2055 break;
2057 case WID_GS_TYPE_DROPDOWN:
2058 switch (this->filter.type) {
2059 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
2060 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
2061 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
2062 default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
2064 break;
2068 DropDownList BuildDropDownList(int widget) const
2070 DropDownList list;
2071 switch (widget) {
2072 case WID_GS_RESTRICT_DROPDOWN:
2073 for (int mode = 0; mode != RM_END; mode++) {
2074 /* If we are in adv. settings screen for the new game's settings,
2075 * we don't want to allow comparing with new game's settings. */
2076 bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
2078 list.emplace_back(new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled));
2080 break;
2082 case WID_GS_TYPE_DROPDOWN:
2083 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false));
2084 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));
2085 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));
2086 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false));
2087 break;
2089 return list;
2092 void DrawWidget(const Rect &r, int widget) const override
2094 switch (widget) {
2095 case WID_GS_OPTIONSPANEL: {
2096 Rect tr = r.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect);
2097 tr.top += this->warn_lines * SETTING_HEIGHT;
2098 uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
2099 int next_row = GetSettingsTree().Draw(settings_ptr, tr.left, tr.right, tr.top,
2100 this->vscroll->GetPosition(), last_row, this->last_clicked);
2101 if (next_row == 0) DrawString(tr, STR_CONFIG_SETTINGS_NONE);
2102 break;
2105 case WID_GS_HELP_TEXT:
2106 if (this->last_clicked != nullptr) {
2107 const IntSettingDesc *sd = this->last_clicked->setting;
2109 Rect tr = r;
2110 switch (sd->GetType()) {
2111 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
2112 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
2113 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
2114 default: NOT_REACHED();
2116 DrawString(tr, STR_CONFIG_SETTING_TYPE);
2117 tr.top += FONT_HEIGHT_NORMAL;
2119 this->last_clicked->SetValueDParams(0, sd->def);
2120 DrawString(tr, STR_CONFIG_SETTING_DEFAULT_VALUE);
2121 tr.top += FONT_HEIGHT_NORMAL + WidgetDimensions::scaled.vsep_normal;
2123 DrawStringMultiLine(tr, this->last_clicked->GetHelpText(), TC_WHITE);
2125 break;
2127 default:
2128 break;
2133 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2134 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2136 void SetDisplayedHelpText(SettingEntry *pe)
2138 if (this->last_clicked != pe) this->SetDirty();
2139 this->last_clicked = pe;
2142 void OnClick(Point pt, int widget, int click_count) override
2144 switch (widget) {
2145 case WID_GS_EXPAND_ALL:
2146 this->manually_changed_folding = true;
2147 GetSettingsTree().UnFoldAll();
2148 this->InvalidateData();
2149 break;
2151 case WID_GS_COLLAPSE_ALL:
2152 this->manually_changed_folding = true;
2153 GetSettingsTree().FoldAll();
2154 this->InvalidateData();
2155 break;
2157 case WID_GS_RESET_ALL:
2158 ShowQuery(
2159 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_CAPTION,
2160 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT,
2161 this,
2162 ResetAllSettingsConfirmationCallback
2164 break;
2166 case WID_GS_RESTRICT_DROPDOWN: {
2167 DropDownList list = this->BuildDropDownList(widget);
2168 if (!list.empty()) {
2169 ShowDropDownList(this, std::move(list), this->filter.mode, widget);
2171 break;
2174 case WID_GS_TYPE_DROPDOWN: {
2175 DropDownList list = this->BuildDropDownList(widget);
2176 if (!list.empty()) {
2177 ShowDropDownList(this, std::move(list), this->filter.type, widget);
2179 break;
2183 if (widget != WID_GS_OPTIONSPANEL) return;
2185 uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.top);
2186 if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2187 btn -= this->warn_lines;
2189 uint cur_row = 0;
2190 BaseSettingEntry *clicked_entry = GetSettingsTree().FindEntry(btn, &cur_row);
2192 if (clicked_entry == nullptr) return; // Clicked below the last setting of the page
2194 int x = (_current_text_dir == TD_RTL ? this->width - 1 - pt.x : pt.x) - WidgetDimensions::scaled.frametext.left - (clicked_entry->level + 1) * WidgetDimensions::scaled.hsep_indent; // Shift x coordinate
2195 if (x < 0) return; // Clicked left of the entry
2197 SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2198 if (clicked_page != nullptr) {
2199 this->SetDisplayedHelpText(nullptr);
2200 clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2202 this->manually_changed_folding = true;
2204 this->InvalidateData();
2205 return;
2208 SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2209 assert(pe != nullptr);
2210 const IntSettingDesc *sd = pe->setting;
2212 /* return if action is only active in network, or only settable by server */
2213 if (!sd->IsEditable()) {
2214 this->SetDisplayedHelpText(pe);
2215 return;
2218 int32 value = sd->Read(ResolveObject(settings_ptr, sd));
2220 /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2221 if (x < SETTING_BUTTON_WIDTH && (sd->flags & SF_GUI_DROPDOWN)) {
2222 this->SetDisplayedHelpText(pe);
2224 if (this->valuedropdown_entry == pe) {
2225 /* unclick the dropdown */
2226 HideDropDownMenu(this);
2227 this->closing_dropdown = false;
2228 this->valuedropdown_entry->SetButtons(0);
2229 this->valuedropdown_entry = nullptr;
2230 } else {
2231 if (this->valuedropdown_entry != nullptr) this->valuedropdown_entry->SetButtons(0);
2232 this->closing_dropdown = false;
2234 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2235 int rel_y = (pt.y - (int)wid->pos_y - WidgetDimensions::scaled.framerect.top) % wid->resize_y;
2237 Rect wi_rect;
2238 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2239 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2240 wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2241 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2243 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2244 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2245 this->valuedropdown_entry = pe;
2246 this->valuedropdown_entry->SetButtons(SEF_LEFT_DEPRESSED);
2248 DropDownList list;
2249 for (int i = sd->min; i <= (int)sd->max; i++) {
2250 list.emplace_back(new DropDownListStringItem(sd->str_val + i - sd->min, i, false));
2253 ShowDropDownListAt(this, std::move(list), value, -1, wi_rect, COLOUR_ORANGE, true);
2256 this->SetDirty();
2257 } else if (x < SETTING_BUTTON_WIDTH) {
2258 this->SetDisplayedHelpText(pe);
2259 int32 oldvalue = value;
2261 if (sd->IsBoolSetting()) {
2262 value ^= 1;
2263 } else {
2264 /* Add a dynamic step-size to the scroller. In a maximum of
2265 * 50-steps you should be able to get from min to max,
2266 * unless specified otherwise in the 'interval' variable
2267 * of the current setting. */
2268 uint32 step = (sd->interval == 0) ? ((sd->max - sd->min) / 50) : sd->interval;
2269 if (step == 0) step = 1;
2271 /* don't allow too fast scrolling */
2272 if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2273 _left_button_clicked = false;
2274 return;
2277 /* Increase or decrease the value and clamp it to extremes */
2278 if (x >= SETTING_BUTTON_WIDTH / 2) {
2279 value += step;
2280 if (sd->min < 0) {
2281 assert((int32)sd->max >= 0);
2282 if (value > (int32)sd->max) value = (int32)sd->max;
2283 } else {
2284 if ((uint32)value > sd->max) value = (int32)sd->max;
2286 if (value < sd->min) value = sd->min; // skip between "disabled" and minimum
2287 } else {
2288 value -= step;
2289 if (value < sd->min) value = (sd->flags & SF_GUI_0_IS_SPECIAL) ? 0 : sd->min;
2292 /* Set up scroller timeout for numeric values */
2293 if (value != oldvalue) {
2294 if (this->clicked_entry != nullptr) { // Release previous buttons if any
2295 this->clicked_entry->SetButtons(0);
2297 this->clicked_entry = pe;
2298 this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2299 this->SetTimeout();
2300 _left_button_clicked = false;
2304 if (value != oldvalue) {
2305 SetSettingValue(sd, value);
2306 this->SetDirty();
2308 } else {
2309 /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2310 if (this->last_clicked == pe && !sd->IsBoolSetting() && !(sd->flags & SF_GUI_DROPDOWN)) {
2311 int64 value64 = value;
2312 /* Show the correct currency-translated value */
2313 if (sd->flags & SF_GUI_CURRENCY) value64 *= _currency->rate;
2315 CharSetFilter charset_filter = CS_NUMERAL; //default, only numeric input allowed
2316 if (sd->min < 0) charset_filter = CS_NUMERAL_SIGNED; // special case, also allow '-' sign for negative input
2318 this->valuewindow_entry = pe;
2319 SetDParam(0, value64);
2320 /* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2321 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 15, this, charset_filter, QSF_ENABLE_DEFAULT);
2323 this->SetDisplayedHelpText(pe);
2327 void OnTimeout() override
2329 if (this->clicked_entry != nullptr) { // On timeout, release any depressed buttons
2330 this->clicked_entry->SetButtons(0);
2331 this->clicked_entry = nullptr;
2332 this->SetDirty();
2336 void OnQueryTextFinished(char *str) override
2338 /* The user pressed cancel */
2339 if (str == nullptr) return;
2341 assert(this->valuewindow_entry != nullptr);
2342 const IntSettingDesc *sd = this->valuewindow_entry->setting;
2344 int32 value;
2345 if (!StrEmpty(str)) {
2346 long long llvalue = atoll(str);
2348 /* Save the correct currency-translated value */
2349 if (sd->flags & SF_GUI_CURRENCY) llvalue /= _currency->rate;
2351 value = (int32)ClampToI32(llvalue);
2352 } else {
2353 value = sd->def;
2356 SetSettingValue(this->valuewindow_entry->setting, value);
2357 this->SetDirty();
2360 void OnDropdownSelect(int widget, int index) override
2362 switch (widget) {
2363 case WID_GS_RESTRICT_DROPDOWN:
2364 this->filter.mode = (RestrictionMode)index;
2365 if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2366 this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2368 if (!this->manually_changed_folding) {
2369 /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2370 GetSettingsTree().UpdateFilterState(this->filter, false);
2371 GetSettingsTree().UnFoldAll();
2373 } else {
2374 /* Non-'changes' filter. Save as default. */
2375 _settings_client.gui.settings_restriction_mode = this->filter.mode;
2377 this->InvalidateData();
2378 break;
2380 case WID_GS_TYPE_DROPDOWN:
2381 this->filter.type = (SettingType)index;
2382 this->InvalidateData();
2383 break;
2385 default:
2386 if (widget < 0) {
2387 /* Deal with drop down boxes on the panel. */
2388 assert(this->valuedropdown_entry != nullptr);
2389 const IntSettingDesc *sd = this->valuedropdown_entry->setting;
2390 assert(sd->flags & SF_GUI_DROPDOWN);
2392 SetSettingValue(sd, index);
2393 this->SetDirty();
2395 break;
2399 void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
2401 if (widget >= 0) {
2402 /* Normally the default implementation of OnDropdownClose() takes care of
2403 * a few things. We want that behaviour here too, but only for
2404 * "normal" dropdown boxes. The special dropdown boxes added for every
2405 * setting that needs one can't have this call. */
2406 Window::OnDropdownClose(pt, widget, index, instant_close);
2407 } else {
2408 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2409 * the same dropdown button was clicked again, and then not open the dropdown again.
2410 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2411 * after OnClick. */
2412 assert(this->valuedropdown_entry != nullptr);
2413 this->closing_dropdown = true;
2414 this->SetDirty();
2418 void OnInvalidateData(int data = 0, bool gui_scope = true) override
2420 if (!gui_scope) return;
2422 /* Update which settings are to be visible. */
2423 RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2424 this->filter.min_cat = min_level;
2425 this->filter.type_hides = false;
2426 GetSettingsTree().UpdateFilterState(this->filter, false);
2428 if (this->filter.string.IsEmpty()) {
2429 this->warn_missing = WHR_NONE;
2430 } else if (min_level < this->filter.min_cat) {
2431 this->warn_missing = this->filter.type_hides ? WHR_CATEGORY_TYPE : WHR_CATEGORY;
2432 } else {
2433 this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2435 this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2437 if (this->last_clicked != nullptr && !GetSettingsTree().IsVisible(this->last_clicked)) {
2438 this->SetDisplayedHelpText(nullptr);
2441 bool all_folded = true;
2442 bool all_unfolded = true;
2443 GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2444 this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2445 this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2448 void OnEditboxChanged(int wid) override
2450 if (wid == WID_GS_FILTER) {
2451 this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2452 if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2453 /* User never expanded/collapsed single pages and entered a filter term.
2454 * Expand everything, to save weird expand clicks, */
2455 GetSettingsTree().UnFoldAll();
2457 this->InvalidateData();
2461 void OnResize() override
2463 this->vscroll->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.Vertical());
2467 GameSettings *GameSettingsWindow::settings_ptr = nullptr;
2469 static const NWidgetPart _nested_settings_selection_widgets[] = {
2470 NWidget(NWID_HORIZONTAL),
2471 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2472 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2473 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2474 EndContainer(),
2475 NWidget(WWT_PANEL, COLOUR_MAUVE),
2476 NWidget(NWID_VERTICAL), SetPIP(WidgetDimensions::unscaled.frametext.top, WidgetDimensions::unscaled.vsep_normal, WidgetDimensions::unscaled.frametext.bottom),
2477 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2478 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2479 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),
2480 EndContainer(),
2481 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2482 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2483 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),
2484 EndContainer(),
2485 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2486 NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2487 NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetMinimalSize(50, 12), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
2488 EndContainer(),
2489 EndContainer(),
2490 EndContainer(),
2491 NWidget(NWID_HORIZONTAL),
2492 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GS_OPTIONSPANEL), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR), EndContainer(),
2493 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2494 EndContainer(),
2495 NWidget(WWT_PANEL, COLOUR_MAUVE),
2496 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2497 SetPadding(WidgetDimensions::unscaled.frametext),
2498 EndContainer(),
2499 NWidget(NWID_HORIZONTAL),
2500 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2501 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2502 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_RESET_ALL), SetDataTip(STR_CONFIG_SETTING_RESET_ALL, STR_NULL),
2503 NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2504 EndContainer(),
2505 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2506 EndContainer(),
2509 static WindowDesc _settings_selection_desc(
2510 WDP_CENTER, "settings", 510, 450,
2511 WC_GAME_OPTIONS, WC_NONE,
2513 _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2516 /** Open advanced settings window. */
2517 void ShowGameSettings()
2519 CloseWindowByClass(WC_GAME_OPTIONS);
2520 new GameSettingsWindow(&_settings_selection_desc);
2525 * Draw [<][>] boxes.
2526 * @param x the x position to draw
2527 * @param y the y position to draw
2528 * @param button_colour the colour of the button
2529 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2530 * @param clickable_left is the left button clickable?
2531 * @param clickable_right is the right button clickable?
2533 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2535 extern void DrawSpriteIgnorePadding(const Rect &r, SpriteID img, bool clicked, StringAlignment align);
2537 int colour = _colour_gradient[button_colour][2];
2538 Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2540 Rect lr = {x, y, x + (int)dim.width - 1, y + (int)dim.height - 1};
2541 Rect rr = {x + (int)dim.width, y, x + (int)dim.width * 2 - 1, y + (int)dim.height - 1};
2543 DrawFrameRect(lr, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2544 DrawFrameRect(rr, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2545 DrawSpriteIgnorePadding(lr, SPR_ARROW_LEFT, (state == 1), SA_CENTER);
2546 DrawSpriteIgnorePadding(rr, SPR_ARROW_RIGHT, (state == 2), SA_CENTER);
2548 /* Grey out the buttons that aren't clickable */
2549 bool rtl = _current_text_dir == TD_RTL;
2550 if (rtl ? !clickable_right : !clickable_left) {
2551 GfxFillRect(lr.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2553 if (rtl ? !clickable_left : !clickable_right) {
2554 GfxFillRect(rr.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2559 * Draw a dropdown button.
2560 * @param x the x position to draw
2561 * @param y the y position to draw
2562 * @param button_colour the colour of the button
2563 * @param state true = lowered
2564 * @param clickable is the button clickable?
2566 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2568 extern void DrawSpriteIgnorePadding(const Rect &r, SpriteID img, bool clicked, StringAlignment align);
2570 int colour = _colour_gradient[button_colour][2];
2572 Rect r = {x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1};
2574 DrawFrameRect(r, button_colour, state ? FR_LOWERED : FR_NONE);
2575 DrawSpriteIgnorePadding(r, SPR_ARROW_DOWN, state, SA_CENTER);
2577 if (!clickable) {
2578 GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2583 * Draw a toggle button.
2584 * @param x the x position to draw
2585 * @param y the y position to draw
2586 * @param state true = lowered
2587 * @param clickable is the button clickable?
2589 void DrawBoolButton(int x, int y, bool state, bool clickable)
2591 static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2593 Rect r = {x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1};
2594 DrawFrameRect(r, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2597 struct CustomCurrencyWindow : Window {
2598 int query_widget;
2600 CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2602 this->InitNested();
2604 SetButtonState();
2607 void SetButtonState()
2609 this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2610 this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2611 this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2612 this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2615 void SetStringParameters(int widget) const override
2617 switch (widget) {
2618 case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2619 case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2620 case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2621 case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2622 case WID_CC_YEAR:
2623 SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2624 SetDParam(1, _custom_currency.to_euro);
2625 break;
2627 case WID_CC_PREVIEW:
2628 SetDParam(0, 10000);
2629 break;
2633 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2635 switch (widget) {
2636 /* Set the appropriate width for the edit 'buttons' */
2637 case WID_CC_SEPARATOR_EDIT:
2638 case WID_CC_PREFIX_EDIT:
2639 case WID_CC_SUFFIX_EDIT:
2640 size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2641 break;
2643 /* Make sure the window is wide enough for the widest exchange rate */
2644 case WID_CC_RATE:
2645 SetDParam(0, 1);
2646 SetDParam(1, INT32_MAX);
2647 *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2648 break;
2652 void OnClick(Point pt, int widget, int click_count) override
2654 int line = 0;
2655 int len = 0;
2656 StringID str = 0;
2657 CharSetFilter afilter = CS_ALPHANUMERAL;
2659 switch (widget) {
2660 case WID_CC_RATE_DOWN:
2661 if (_custom_currency.rate > 1) _custom_currency.rate--;
2662 if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2663 this->EnableWidget(WID_CC_RATE_UP);
2664 break;
2666 case WID_CC_RATE_UP:
2667 if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2668 if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2669 this->EnableWidget(WID_CC_RATE_DOWN);
2670 break;
2672 case WID_CC_RATE:
2673 SetDParam(0, _custom_currency.rate);
2674 str = STR_JUST_INT;
2675 len = 5;
2676 line = WID_CC_RATE;
2677 afilter = CS_NUMERAL;
2678 break;
2680 case WID_CC_SEPARATOR_EDIT:
2681 case WID_CC_SEPARATOR:
2682 SetDParamStr(0, _custom_currency.separator);
2683 str = STR_JUST_RAW_STRING;
2684 len = 7;
2685 line = WID_CC_SEPARATOR;
2686 break;
2688 case WID_CC_PREFIX_EDIT:
2689 case WID_CC_PREFIX:
2690 SetDParamStr(0, _custom_currency.prefix);
2691 str = STR_JUST_RAW_STRING;
2692 len = 15;
2693 line = WID_CC_PREFIX;
2694 break;
2696 case WID_CC_SUFFIX_EDIT:
2697 case WID_CC_SUFFIX:
2698 SetDParamStr(0, _custom_currency.suffix);
2699 str = STR_JUST_RAW_STRING;
2700 len = 15;
2701 line = WID_CC_SUFFIX;
2702 break;
2704 case WID_CC_YEAR_DOWN:
2705 _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2706 if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2707 this->EnableWidget(WID_CC_YEAR_UP);
2708 break;
2710 case WID_CC_YEAR_UP:
2711 _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2712 if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2713 this->EnableWidget(WID_CC_YEAR_DOWN);
2714 break;
2716 case WID_CC_YEAR:
2717 SetDParam(0, _custom_currency.to_euro);
2718 str = STR_JUST_INT;
2719 len = 7;
2720 line = WID_CC_YEAR;
2721 afilter = CS_NUMERAL;
2722 break;
2725 if (len != 0) {
2726 this->query_widget = line;
2727 ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2730 this->SetTimeout();
2731 this->SetDirty();
2734 void OnQueryTextFinished(char *str) override
2736 if (str == nullptr) return;
2738 switch (this->query_widget) {
2739 case WID_CC_RATE:
2740 _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2741 break;
2743 case WID_CC_SEPARATOR: // Thousands separator
2744 _custom_currency.separator = str;
2745 break;
2747 case WID_CC_PREFIX:
2748 _custom_currency.prefix = str;
2749 break;
2751 case WID_CC_SUFFIX:
2752 _custom_currency.suffix = str;
2753 break;
2755 case WID_CC_YEAR: { // Year to switch to euro
2756 int val = atoi(str);
2758 _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : std::min(val, MAX_YEAR));
2759 break;
2762 MarkWholeScreenDirty();
2763 SetButtonState();
2766 void OnTimeout() override
2768 this->SetDirty();
2772 static const NWidgetPart _nested_cust_currency_widgets[] = {
2773 NWidget(NWID_HORIZONTAL),
2774 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2775 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2776 EndContainer(),
2777 NWidget(WWT_PANEL, COLOUR_GREY),
2778 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(7, 3, 0),
2779 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2780 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2781 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2782 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2783 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2784 EndContainer(),
2785 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2786 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2787 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2788 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2789 EndContainer(),
2790 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2791 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2792 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2793 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2794 EndContainer(),
2795 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2796 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2797 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2798 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2799 EndContainer(),
2800 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2801 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2802 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2803 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2804 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2805 EndContainer(),
2806 EndContainer(),
2807 NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2808 SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2809 EndContainer(),
2812 static WindowDesc _cust_currency_desc(
2813 WDP_CENTER, nullptr, 0, 0,
2814 WC_CUSTOM_CURRENCY, WC_NONE,
2816 _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2819 /** Open custom currency window. */
2820 static void ShowCustCurrency()
2822 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
2823 new CustomCurrencyWindow(&_cust_currency_desc);