Doc: Clarify comment that SND_05_TRAIN_THROUGH_TUNNEL is only for steam engines ...
[openttd-github.git] / src / settings_gui.cpp
blobd81c4d071f0979d16ff64f903fdf586a66fc09e2
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 "highscore.h"
25 #include "base_media_base.h"
26 #include "company_base.h"
27 #include "company_func.h"
28 #include "viewport_func.h"
29 #include "core/geometry_func.hpp"
30 #include "ai/ai.hpp"
31 #include "blitter/factory.hpp"
32 #include "language.h"
33 #include "textfile_gui.h"
34 #include "stringfilter_type.h"
35 #include "querystring_gui.h"
36 #include "fontcache.h"
37 #include "zoom_func.h"
38 #include "video/video_driver.hpp"
40 #include <vector>
41 #include <iterator>
43 #include "safeguards.h"
44 #include "video/video_driver.hpp"
47 static const StringID _autosave_dropdown[] = {
48 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
49 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
50 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
51 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
52 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
53 INVALID_STRING_ID,
56 static const StringID _gui_zoom_dropdown[] = {
57 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_AUTO,
58 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL,
59 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM,
60 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM,
61 INVALID_STRING_ID,
64 static const StringID _font_zoom_dropdown[] = {
65 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_AUTO,
66 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_NORMAL,
67 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_2X_ZOOM,
68 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_4X_ZOOM,
69 INVALID_STRING_ID,
72 static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
74 static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd);
76 /**
77 * Get index of the current screen resolution.
78 * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
80 static uint GetCurrentResolutionIndex()
82 auto it = std::find(_resolutions.begin(), _resolutions.end(), Dimension(_screen.width, _screen.height));
83 return std::distance(_resolutions.begin(), it);
86 static void ShowCustCurrency();
88 template <class T>
89 static DropDownList BuildSetDropDownList(int *selected_index, bool allow_selection)
91 int n = T::GetNumSets();
92 *selected_index = T::GetIndexOfUsedSet();
94 DropDownList list;
95 for (int i = 0; i < n; i++) {
96 list.emplace_back(new DropDownListCharStringItem(T::GetSet(i)->name, i, !allow_selection && (*selected_index != i)));
99 return list;
102 DropDownList BuildMusicSetDropDownList(int *selected_index)
104 return BuildSetDropDownList<BaseMusic>(selected_index, true);
107 /** Window for displaying the textfile of a BaseSet. */
108 template <class TBaseSet>
109 struct BaseSetTextfileWindow : public TextfileWindow {
110 const TBaseSet* baseset; ///< View the textfile of this BaseSet.
111 StringID content_type; ///< STR_CONTENT_TYPE_xxx for title.
113 BaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
115 const char *textfile = this->baseset->GetTextfile(file_type);
116 this->LoadTextfile(textfile, BASESET_DIR);
119 void SetStringParameters(int widget) const override
121 if (widget == WID_TF_CAPTION) {
122 SetDParam(0, content_type);
123 SetDParamStr(1, this->baseset->name.c_str());
129 * Open the BaseSet version of the textfile window.
130 * @param file_type The type of textfile to display.
131 * @param baseset The BaseSet to use.
132 * @param content_type STR_CONTENT_TYPE_xxx for title.
134 template <class TBaseSet>
135 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type)
137 DeleteWindowById(WC_TEXTFILE, file_type);
138 new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
141 std::set<int> _refresh_rates = { 30, 60, 75, 90, 100, 120, 144, 240 };
144 * Add the refresh rate from the config and the refresh rates from all the monitors to
145 * our list of refresh rates shown in the GUI.
147 static void AddCustomRefreshRates()
149 /* Add the refresh rate as selected in the config. */
150 _refresh_rates.insert(_settings_client.gui.refresh_rate);
152 /* Add all the refresh rates of all monitors connected to the machine. */
153 std::vector<int> monitorRates = VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
154 std::copy(monitorRates.begin(), monitorRates.end(), std::inserter(_refresh_rates, _refresh_rates.end()));
157 struct GameOptionsWindow : Window {
158 GameSettings *opt;
159 bool reload;
161 GameOptionsWindow(WindowDesc *desc) : Window(desc)
163 this->opt = &GetGameSettings();
164 this->reload = false;
166 AddCustomRefreshRates();
168 this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS);
169 this->OnInvalidateData(0);
172 ~GameOptionsWindow()
174 DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
175 DeleteWindowByClass(WC_TEXTFILE);
176 if (this->reload) _switch_mode = SM_MENU;
180 * Build the dropdown list for a specific widget.
181 * @param widget Widget to build list for
182 * @param selected_index Currently selected item
183 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
185 DropDownList BuildDropDownList(int widget, int *selected_index) const
187 DropDownList list;
188 switch (widget) {
189 case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
190 *selected_index = this->opt->locale.currency;
191 StringID *items = BuildCurrencyDropdown();
192 uint64 disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
194 /* Add non-custom currencies; sorted naturally */
195 for (uint i = 0; i < CURRENCY_END; items++, i++) {
196 if (i == CURRENCY_CUSTOM) continue;
197 list.emplace_back(new DropDownListStringItem(*items, i, HasBit(disabled, i)));
199 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
201 /* Append custom currency at the end */
202 list.emplace_back(new DropDownListItem(-1, false)); // separator line
203 list.emplace_back(new DropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
204 break;
207 case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
208 *selected_index = _settings_client.gui.autosave;
209 const StringID *items = _autosave_dropdown;
210 for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
211 list.emplace_back(new DropDownListStringItem(*items, i, false));
213 break;
216 case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
217 for (uint i = 0; i < _languages.size(); i++) {
218 auto item = new DropDownListParamStringItem(STR_JUST_RAW_STRING, i, false);
219 if (&_languages[i] == _current_language) {
220 *selected_index = i;
221 item->SetParamStr(0, _languages[i].own_name);
222 } else {
223 /* Especially with sprite-fonts, not all localized
224 * names can be rendered. So instead, we use the
225 * international names for anything but the current
226 * selected language. This avoids showing a few ????
227 * entries in the dropdown list. */
228 item->SetParamStr(0, _languages[i].name);
230 list.emplace_back(item);
232 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
233 break;
236 case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
237 if (_resolutions.empty()) break;
239 *selected_index = GetCurrentResolutionIndex();
240 for (uint i = 0; i < _resolutions.size(); i++) {
241 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_RESOLUTION_ITEM, i, false);
242 item->SetParam(0, _resolutions[i].width);
243 item->SetParam(1, _resolutions[i].height);
244 list.emplace_back(item);
246 break;
248 case WID_GO_REFRESH_RATE_DROPDOWN: // Setup refresh rate dropdown
249 for (auto it = _refresh_rates.begin(); it != _refresh_rates.end(); it++) {
250 auto i = std::distance(_refresh_rates.begin(), it);
251 if (*it == _settings_client.gui.refresh_rate) *selected_index = i;
252 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, i, false);
253 item->SetParam(0, *it);
254 list.emplace_back(item);
256 break;
258 case WID_GO_GUI_ZOOM_DROPDOWN: {
259 *selected_index = _gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom + 1 : 0;
260 const StringID *items = _gui_zoom_dropdown;
261 for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
262 list.emplace_back(new DropDownListStringItem(*items, i, i != 0 && _settings_client.gui.zoom_min > ZOOM_LVL_OUT_4X - i + 1));
264 break;
267 case WID_GO_FONT_ZOOM_DROPDOWN: {
268 *selected_index = _font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom + 1 : 0;
269 const StringID *items = _font_zoom_dropdown;
270 for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
271 list.emplace_back(new DropDownListStringItem(*items, i, false));
273 break;
276 case WID_GO_BASE_GRF_DROPDOWN:
277 list = BuildSetDropDownList<BaseGraphics>(selected_index, (_game_mode == GM_MENU));
278 break;
280 case WID_GO_BASE_SFX_DROPDOWN:
281 list = BuildSetDropDownList<BaseSounds>(selected_index, (_game_mode == GM_MENU));
282 break;
284 case WID_GO_BASE_MUSIC_DROPDOWN:
285 list = BuildMusicSetDropDownList(selected_index);
286 break;
289 return list;
292 void SetStringParameters(int widget) const override
294 switch (widget) {
295 case WID_GO_CURRENCY_DROPDOWN: SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
296 case WID_GO_AUTOSAVE_DROPDOWN: SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
297 case WID_GO_LANG_DROPDOWN: SetDParamStr(0, _current_language->own_name); break;
298 case WID_GO_GUI_ZOOM_DROPDOWN: SetDParam(0, _gui_zoom_dropdown[_gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom_cfg + 1 : 0]); break;
299 case WID_GO_FONT_ZOOM_DROPDOWN: SetDParam(0, _font_zoom_dropdown[_font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom_cfg + 1 : 0]); break;
300 case WID_GO_BASE_GRF_DROPDOWN: SetDParamStr(0, BaseGraphics::GetUsedSet()->name.c_str()); break;
301 case WID_GO_BASE_GRF_STATUS: SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
302 case WID_GO_BASE_SFX_DROPDOWN: SetDParamStr(0, BaseSounds::GetUsedSet()->name.c_str()); break;
303 case WID_GO_BASE_MUSIC_DROPDOWN: SetDParamStr(0, BaseMusic::GetUsedSet()->name.c_str()); break;
304 case WID_GO_BASE_MUSIC_STATUS: SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
305 case WID_GO_REFRESH_RATE_DROPDOWN: SetDParam(0, _settings_client.gui.refresh_rate); break;
306 case WID_GO_RESOLUTION_DROPDOWN: {
307 auto current_resolution = GetCurrentResolutionIndex();
309 if (current_resolution == _resolutions.size()) {
310 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER);
311 } else {
312 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM);
313 SetDParam(1, _resolutions[current_resolution].width);
314 SetDParam(2, _resolutions[current_resolution].height);
316 break;
321 void DrawWidget(const Rect &r, int widget) const override
323 switch (widget) {
324 case WID_GO_BASE_GRF_DESCRIPTION:
325 SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
326 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
327 break;
329 case WID_GO_BASE_SFX_DESCRIPTION:
330 SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
331 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
332 break;
334 case WID_GO_BASE_MUSIC_DESCRIPTION:
335 SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
336 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
337 break;
341 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
343 switch (widget) {
344 case WID_GO_BASE_GRF_DESCRIPTION:
345 /* Find the biggest description for the default size. */
346 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
347 SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
348 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
350 break;
352 case WID_GO_BASE_GRF_STATUS:
353 /* Find the biggest description for the default size. */
354 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
355 uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
356 if (invalid_files == 0) continue;
358 SetDParam(0, invalid_files);
359 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
361 break;
363 case WID_GO_BASE_SFX_DESCRIPTION:
364 /* Find the biggest description for the default size. */
365 for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
366 SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
367 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
369 break;
371 case WID_GO_BASE_MUSIC_DESCRIPTION:
372 /* Find the biggest description for the default size. */
373 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
374 SetDParamStr(0, BaseMusic::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_STATUS:
380 /* Find the biggest description for the default size. */
381 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
382 uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
383 if (invalid_files == 0) continue;
385 SetDParam(0, invalid_files);
386 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
388 break;
390 default: {
391 int selected;
392 DropDownList list = this->BuildDropDownList(widget, &selected);
393 if (!list.empty()) {
394 /* Find the biggest item for the default size. */
395 for (const auto &ddli : list) {
396 Dimension string_dim;
397 int width = ddli->Width();
398 string_dim.width = width + padding.width;
399 string_dim.height = ddli->Height(width) + padding.height;
400 *size = maxdim(*size, string_dim);
407 void OnClick(Point pt, int widget, int click_count) override
409 if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
410 if (BaseGraphics::GetUsedSet() == nullptr) return;
412 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
413 return;
415 if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
416 if (BaseSounds::GetUsedSet() == nullptr) return;
418 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
419 return;
421 if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
422 if (BaseMusic::GetUsedSet() == nullptr) return;
424 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
425 return;
427 switch (widget) {
428 case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
429 /* try to toggle full-screen on/off */
430 if (!ToggleFullScreen(!_fullscreen)) {
431 ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
433 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
434 this->SetDirty();
435 break;
437 case WID_GO_VIDEO_ACCEL_BUTTON:
438 _video_hw_accel = !_video_hw_accel;
439 ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART, INVALID_STRING_ID, WL_INFO);
440 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
441 this->SetDirty();
442 break;
444 default: {
445 int selected;
446 DropDownList list = this->BuildDropDownList(widget, &selected);
447 if (!list.empty()) {
448 ShowDropDownList(this, std::move(list), selected, widget);
449 } else {
450 if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
452 break;
458 * Set the base media set.
459 * @param index the index of the media set
460 * @tparam T class of media set
462 template <class T>
463 void SetMediaSet(int index)
465 if (_game_mode == GM_MENU) {
466 auto name = T::GetSet(index)->name;
468 T::ini_set = name;
470 T::SetSet(name);
471 this->reload = true;
472 this->InvalidateData();
476 void OnDropdownSelect(int widget, int index) override
478 switch (widget) {
479 case WID_GO_CURRENCY_DROPDOWN: // Currency
480 if (index == CURRENCY_CUSTOM) ShowCustCurrency();
481 this->opt->locale.currency = index;
482 ReInitAllWindows();
483 break;
485 case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
486 _settings_client.gui.autosave = index;
487 this->SetDirty();
488 break;
490 case WID_GO_LANG_DROPDOWN: // Change interface language
491 ReadLanguagePack(&_languages[index]);
492 DeleteWindowByClass(WC_QUERY_STRING);
493 CheckForMissingGlyphs();
494 ClearAllCachedNames();
495 UpdateAllVirtCoords();
496 CheckBlitter();
497 ReInitAllWindows();
498 break;
500 case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
501 if ((uint)index < _resolutions.size() && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
502 this->SetDirty();
504 break;
506 case WID_GO_REFRESH_RATE_DROPDOWN: {
507 _settings_client.gui.refresh_rate = *std::next(_refresh_rates.begin(), index);
508 if (_settings_client.gui.refresh_rate > 60) {
509 /* Show warning to the user that this refresh rate might not be suitable on
510 * larger maps with many NewGRFs and vehicles. */
511 ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING, INVALID_STRING_ID, WL_INFO);
513 break;
516 case WID_GO_GUI_ZOOM_DROPDOWN: {
517 int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
518 if (new_zoom != _gui_zoom_cfg) {
519 GfxClearSpriteCache();
520 _gui_zoom_cfg = new_zoom;
521 UpdateGUIZoom();
522 UpdateCursorSize();
523 UpdateAllVirtCoords();
524 FixTitleGameZoom();
525 ReInitAllWindows();
527 break;
530 case WID_GO_FONT_ZOOM_DROPDOWN: {
531 int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
532 if (new_zoom != _font_zoom_cfg) {
533 GfxClearSpriteCache();
534 _font_zoom_cfg = new_zoom;
535 UpdateGUIZoom();
536 ClearFontCache();
537 LoadStringWidthTable();
538 UpdateAllVirtCoords();
540 break;
543 case WID_GO_BASE_GRF_DROPDOWN:
544 this->SetMediaSet<BaseGraphics>(index);
545 break;
547 case WID_GO_BASE_SFX_DROPDOWN:
548 this->SetMediaSet<BaseSounds>(index);
549 break;
551 case WID_GO_BASE_MUSIC_DROPDOWN:
552 ChangeMusicSet(index);
553 break;
558 * Some data on this window has become invalid.
559 * @param data Information about the changed data. @see GameOptionsInvalidationData
560 * @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.
562 void OnInvalidateData(int data = 0, bool gui_scope = true) override
564 if (!gui_scope) return;
565 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
566 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
568 bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
569 this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
571 for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
572 this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->GetTextfile(tft) == nullptr);
573 this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->GetTextfile(tft) == nullptr);
574 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->GetTextfile(tft) == nullptr);
577 missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
578 this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
582 static const NWidgetPart _nested_game_options_widgets[] = {
583 NWidget(NWID_HORIZONTAL),
584 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
585 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
586 EndContainer(),
587 NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
588 NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
589 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
590 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
591 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),
592 EndContainer(),
593 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_ZOOM_FRAME, STR_NULL),
594 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_GUI_ZOOM_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_TOOLTIP), SetFill(1, 0),
595 EndContainer(),
596 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
597 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),
598 EndContainer(),
599 EndContainer(),
601 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
602 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
603 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),
604 EndContainer(),
605 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_FONT_ZOOM, STR_NULL),
606 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_FONT_ZOOM_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_TOOLTIP), SetFill(1, 0),
607 EndContainer(),
608 EndContainer(),
609 EndContainer(),
611 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GRAPHICS, STR_NULL), SetPadding(0, 10, 0, 10),
612 NWidget(NWID_HORIZONTAL),
613 NWidget(NWID_VERTICAL),
614 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL), SetPadding(0, 0, 2, 0),
615 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE, STR_NULL), SetPadding(0, 0, 2, 0),
616 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL), SetPadding(0, 0, 2, 0),
617 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION, STR_NULL),
618 EndContainer(),
619 NWidget(NWID_VERTICAL),
620 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP), SetFill(1, 0), SetPadding(0, 0, 2, 0),
621 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_REFRESH_RATE_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, STR_GAME_OPTIONS_REFRESH_RATE_TOOLTIP), SetFill(1, 0), SetPadding(0, 0, 2, 0),
622 NWidget(NWID_HORIZONTAL), SetPadding(0, 0, 2, 0),
623 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
624 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
625 EndContainer(),
626 NWidget(NWID_HORIZONTAL),
627 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
628 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_ACCEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP),
629 EndContainer(),
630 EndContainer(),
631 EndContainer(),
632 EndContainer(),
634 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
635 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
636 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
637 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
638 EndContainer(),
639 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),
640 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
641 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),
642 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),
643 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),
644 EndContainer(),
645 EndContainer(),
647 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
648 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
649 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
650 NWidget(NWID_SPACER), SetFill(1, 0),
651 EndContainer(),
652 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),
653 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
654 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),
655 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),
656 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),
657 EndContainer(),
658 EndContainer(),
660 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
661 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
662 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
663 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
664 EndContainer(),
665 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),
666 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
667 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),
668 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),
669 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),
670 EndContainer(),
671 EndContainer(),
672 EndContainer(),
675 static WindowDesc _game_options_desc(
676 WDP_CENTER, "settings_game", 0, 0,
677 WC_GAME_OPTIONS, WC_NONE,
679 _nested_game_options_widgets, lengthof(_nested_game_options_widgets)
682 /** Open the game options window. */
683 void ShowGameOptions()
685 DeleteWindowByClass(WC_GAME_OPTIONS);
686 new GameOptionsWindow(&_game_options_desc);
689 static int SETTING_HEIGHT = 11; ///< Height of a single setting in the tree view in pixels
690 static const int LEVEL_WIDTH = 15; ///< Indenting width of a sub-page in pixels
693 * Flags for #SettingEntry
694 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
696 enum SettingEntryFlags {
697 SEF_LEFT_DEPRESSED = 0x01, ///< Of a numeric setting entry, the left button is depressed
698 SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
699 SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
701 SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
702 SEF_FILTERED = 0x08, ///< Entry is hidden by the string filter
705 /** How the list of advanced settings is filtered. */
706 enum RestrictionMode {
707 RM_BASIC, ///< Display settings associated to the "basic" list.
708 RM_ADVANCED, ///< Display settings associated to the "advanced" list.
709 RM_ALL, ///< List all settings regardless of the default/newgame/... values.
710 RM_CHANGED_AGAINST_DEFAULT, ///< Show only settings which are different compared to default values.
711 RM_CHANGED_AGAINST_NEW, ///< Show only settings which are different compared to the user's new game setting values.
712 RM_END, ///< End for iteration.
714 DECLARE_POSTFIX_INCREMENT(RestrictionMode)
716 /** Filter for settings list. */
717 struct SettingFilter {
718 StringFilter string; ///< Filter string.
719 RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
720 bool type_hides; ///< Whether the type hides filtered strings.
721 RestrictionMode mode; ///< Filter based on category.
722 SettingType type; ///< Filter based on type.
725 /** Data structure describing a single setting in a tab */
726 struct BaseSettingEntry {
727 byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
728 byte level; ///< Nesting level of this setting entry
730 BaseSettingEntry() : flags(0), level(0) {}
731 virtual ~BaseSettingEntry() {}
733 virtual void Init(byte level = 0);
734 virtual void FoldAll() {}
735 virtual void UnFoldAll() {}
738 * Set whether this is the last visible entry of the parent node.
739 * @param last_field Value to set
741 void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
743 virtual uint Length() const = 0;
744 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
745 virtual bool IsVisible(const BaseSettingEntry *item) const;
746 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
747 virtual uint GetMaxHelpHeight(int maxw) { return 0; }
750 * Check whether an entry is hidden due to filters
751 * @return true if hidden.
753 bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
755 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
757 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;
759 protected:
760 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
763 /** Standard setting */
764 struct SettingEntry : BaseSettingEntry {
765 const char *name; ///< Name of the setting
766 const SettingDesc *setting; ///< Setting description of the setting
767 uint index; ///< Index of the setting in the settings table
769 SettingEntry(const char *name);
771 virtual void Init(byte level = 0);
772 virtual uint Length() const;
773 virtual uint GetMaxHelpHeight(int maxw);
774 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
776 void SetButtons(byte new_val);
779 * Get the help text of a single setting.
780 * @return The requested help text.
782 inline StringID GetHelpText() const
784 return this->setting->desc.str_help;
787 void SetValueDParams(uint first_param, int32 value) const;
789 protected:
790 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
792 private:
793 bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
796 /** Containers for BaseSettingEntry */
797 struct SettingsContainer {
798 typedef std::vector<BaseSettingEntry*> EntryVector;
799 EntryVector entries; ///< Settings on this page
801 template<typename T>
802 T *Add(T *item)
804 this->entries.push_back(item);
805 return item;
808 void Init(byte level = 0);
809 void FoldAll();
810 void UnFoldAll();
812 uint Length() const;
813 void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
814 bool IsVisible(const BaseSettingEntry *item) const;
815 BaseSettingEntry *FindEntry(uint row, uint *cur_row);
816 uint GetMaxHelpHeight(int maxw);
818 bool UpdateFilterState(SettingFilter &filter, bool force_visible);
820 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;
823 /** Data structure describing one page of settings in the settings window. */
824 struct SettingsPage : BaseSettingEntry, SettingsContainer {
825 StringID title; ///< Title of the sub-page
826 bool folded; ///< Sub-page is folded (not visible except for its title)
828 SettingsPage(StringID title);
830 virtual void Init(byte level = 0);
831 virtual void FoldAll();
832 virtual void UnFoldAll();
834 virtual uint Length() const;
835 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
836 virtual bool IsVisible(const BaseSettingEntry *item) const;
837 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
838 virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
840 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
842 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;
844 protected:
845 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
848 /* == BaseSettingEntry methods == */
851 * Initialization of a setting entry
852 * @param level Page nesting level of this entry
854 void BaseSettingEntry::Init(byte level)
856 this->level = level;
860 * Check whether an entry is visible and not folded or filtered away.
861 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
862 * @param item Entry to search for.
863 * @return true if entry is visible.
865 bool BaseSettingEntry::IsVisible(const BaseSettingEntry *item) const
867 if (this->IsFiltered()) return false;
868 if (this == item) return true;
869 return false;
873 * Find setting entry at row \a row_num
874 * @param row_num Index of entry to return
875 * @param cur_row Current row number
876 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
878 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
880 if (this->IsFiltered()) return nullptr;
881 if (row_num == *cur_row) return this;
882 (*cur_row)++;
883 return nullptr;
887 * Draw a row in the settings panel.
889 * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
890 * 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.
891 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
893 * The \a parent_last parameter ensures that the vertical lines at the left are
894 * only drawn when another entry follows, that it prevents output like
895 * \verbatim
896 * |-- setting
897 * |-- (-) - Title
898 * | |-- setting
899 * | |-- setting
900 * \endverbatim
901 * The left-most vertical line is not wanted. It is prevented by setting the
902 * appropriate bit in the \a parent_last parameter.
904 * @param settings_ptr Pointer to current values of all settings
905 * @param left Left-most position in window/panel to start drawing \a first_row
906 * @param right Right-most x position to draw strings at.
907 * @param y Upper-most position in window/panel to start drawing \a first_row
908 * @param first_row First row number to draw
909 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
910 * @param selected Selected entry by the user.
911 * @param cur_row Current row number (internal variable)
912 * @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)
913 * @return Row number of the next row to draw
915 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
917 if (this->IsFiltered()) return cur_row;
918 if (cur_row >= max_row) return cur_row;
920 bool rtl = _current_text_dir == TD_RTL;
921 int offset = rtl ? -4 : 4;
922 int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
924 int x = rtl ? right : left;
925 if (cur_row >= first_row) {
926 int colour = _colour_gradient[COLOUR_ORANGE][4];
927 y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
929 /* Draw vertical for parent nesting levels */
930 for (uint lvl = 0; lvl < this->level; lvl++) {
931 if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
932 x += level_width;
934 /* draw own |- prefix */
935 int halfway_y = y + SETTING_HEIGHT / 2;
936 int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
937 GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
938 /* Small horizontal line from the last vertical line */
939 GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
940 x += level_width;
942 this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
944 cur_row++;
946 return cur_row;
949 /* == SettingEntry methods == */
952 * Constructor for a single setting in the 'advanced settings' window
953 * @param name Name of the setting in the setting table
955 SettingEntry::SettingEntry(const char *name)
957 this->name = name;
958 this->setting = nullptr;
959 this->index = 0;
963 * Initialization of a setting entry
964 * @param level Page nesting level of this entry
966 void SettingEntry::Init(byte level)
968 BaseSettingEntry::Init(level);
969 this->setting = GetSettingFromName(this->name, &this->index);
970 assert(this->setting != nullptr);
974 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
975 * @param new_val New value for the button flags
976 * @see SettingEntryFlags
978 void SettingEntry::SetButtons(byte new_val)
980 assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
981 this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
984 /** Return number of rows needed to display the (filtered) entry */
985 uint SettingEntry::Length() const
987 return this->IsFiltered() ? 0 : 1;
991 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
992 * @param maxw Maximal width of a line help text.
993 * @return Biggest height needed to display any help text of this node (and its descendants).
995 uint SettingEntry::GetMaxHelpHeight(int maxw)
997 return GetStringHeight(this->GetHelpText(), maxw);
1001 * Checks whether an entry shall be made visible based on the restriction mode.
1002 * @param mode The current status of the restriction drop down box.
1003 * @return true if the entry shall be visible.
1005 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode) const
1007 /* There shall not be any restriction, i.e. all settings shall be visible. */
1008 if (mode == RM_ALL) return true;
1010 GameSettings *settings_ptr = &GetGameSettings();
1011 const SettingDesc *sd = this->setting;
1013 if (mode == RM_BASIC) return (this->setting->desc.cat & SC_BASIC_LIST) != 0;
1014 if (mode == RM_ADVANCED) return (this->setting->desc.cat & SC_ADVANCED_LIST) != 0;
1016 /* Read the current value. */
1017 const void *var = ResolveVariableAddress(settings_ptr, sd);
1018 int64 current_value = ReadValue(var, sd->save.conv);
1020 int64 filter_value;
1022 if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1023 /* This entry shall only be visible, if the value deviates from its default value. */
1025 /* Read the default value. */
1026 filter_value = ReadValue(&sd->desc.def, sd->save.conv);
1027 } else {
1028 assert(mode == RM_CHANGED_AGAINST_NEW);
1029 /* This entry shall only be visible, if the value deviates from
1030 * its value is used when starting a new game. */
1032 /* Make sure we're not comparing the new game settings against itself. */
1033 assert(settings_ptr != &_settings_newgame);
1035 /* Read the new game's value. */
1036 var = ResolveVariableAddress(&_settings_newgame, sd);
1037 filter_value = ReadValue(var, sd->save.conv);
1040 return current_value != filter_value;
1044 * Update the filter state.
1045 * @param filter Filter
1046 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1047 * @return true if item remains visible
1049 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1051 CLRBITS(this->flags, SEF_FILTERED);
1053 bool visible = true;
1055 const SettingDesc *sd = this->setting;
1056 if (!force_visible && !filter.string.IsEmpty()) {
1057 /* Process the search text filter for this item. */
1058 filter.string.ResetState();
1060 const SettingDescBase *sdb = &sd->desc;
1062 SetDParam(0, STR_EMPTY);
1063 filter.string.AddLine(sdb->str);
1064 filter.string.AddLine(this->GetHelpText());
1066 visible = filter.string.GetState();
1069 if (visible) {
1070 if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1071 filter.type_hides = true;
1072 visible = false;
1074 if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1075 while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1076 visible = false;
1080 if (!visible) SETBITS(this->flags, SEF_FILTERED);
1081 return visible;
1085 static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd)
1087 if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
1088 if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1089 return GetVariableAddress(&Company::Get(_local_company)->settings, &sd->save);
1090 } else {
1091 return GetVariableAddress(&_settings_client.company, &sd->save);
1093 } else {
1094 return GetVariableAddress(settings_ptr, &sd->save);
1099 * Set the DParams for drawing the value of a setting.
1100 * @param first_param First DParam to use
1101 * @param value Setting value to set params for.
1103 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1105 const SettingDescBase *sdb = &this->setting->desc;
1106 if (sdb->cmd == SDT_BOOLX) {
1107 SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1108 } else {
1109 if ((sdb->flags & SGF_MULTISTRING) != 0) {
1110 SetDParam(first_param++, sdb->str_val - sdb->min + value);
1111 } else if ((sdb->flags & SGF_DISPLAY_ABS) != 0) {
1112 SetDParam(first_param++, sdb->str_val + ((value >= 0) ? 1 : 0));
1113 value = abs(value);
1114 } else {
1115 SetDParam(first_param++, sdb->str_val + ((value == 0 && (sdb->flags & SGF_0ISDISABLED) != 0) ? 1 : 0));
1117 SetDParam(first_param++, value);
1122 * Function to draw setting value (button + text + current value)
1123 * @param settings_ptr Pointer to current values of all settings
1124 * @param left Left-most position in window/panel to start drawing
1125 * @param right Right-most position in window/panel to draw
1126 * @param y Upper-most position in window/panel to start drawing
1127 * @param highlight Highlight entry.
1129 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1131 const SettingDesc *sd = this->setting;
1132 const SettingDescBase *sdb = &sd->desc;
1133 const void *var = ResolveVariableAddress(settings_ptr, sd);
1134 int state = this->flags & SEF_BUTTONS_MASK;
1136 bool rtl = _current_text_dir == TD_RTL;
1137 uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1138 uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + 5);
1139 uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + 5 : 0);
1140 uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1142 /* We do not allow changes of some items when we are a client in a networkgame */
1143 bool editable = sd->IsEditable();
1145 SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1146 int32 value = (int32)ReadValue(var, sd->save.conv);
1147 if (sdb->cmd == SDT_BOOLX) {
1148 /* Draw checkbox for boolean-value either on/off */
1149 DrawBoolButton(buttons_left, button_y, value != 0, editable);
1150 } else if ((sdb->flags & SGF_MULTISTRING) != 0) {
1151 /* Draw [v] button for settings of an enum-type */
1152 DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1153 } else {
1154 /* Draw [<][>] boxes for settings of an integer-type */
1155 DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1156 editable && value != (sdb->flags & SGF_0ISDISABLED ? 0 : sdb->min), editable && (uint32)value != sdb->max);
1158 this->SetValueDParams(1, value);
1159 DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sdb->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1162 /* == SettingsContainer methods == */
1165 * Initialization of an entire setting page
1166 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1168 void SettingsContainer::Init(byte level)
1170 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1171 (*it)->Init(level);
1175 /** Recursively close all folds of sub-pages */
1176 void SettingsContainer::FoldAll()
1178 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1179 (*it)->FoldAll();
1183 /** Recursively open all folds of sub-pages */
1184 void SettingsContainer::UnFoldAll()
1186 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1187 (*it)->UnFoldAll();
1192 * Recursively accumulate the folding state of the tree.
1193 * @param[in,out] all_folded Set to false, if one entry is not folded.
1194 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1196 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1198 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1199 (*it)->GetFoldingState(all_folded, all_unfolded);
1204 * Update the filter state.
1205 * @param filter Filter
1206 * @param force_visible Whether to force all items visible, no matter what
1207 * @return true if item remains visible
1209 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1211 bool visible = false;
1212 bool first_visible = true;
1213 for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1214 visible |= (*it)->UpdateFilterState(filter, force_visible);
1215 (*it)->SetLastField(first_visible);
1216 if (visible && first_visible) first_visible = false;
1218 return visible;
1223 * Check whether an entry is visible and not folded or filtered away.
1224 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1225 * @param item Entry to search for.
1226 * @return true if entry is visible.
1228 bool SettingsContainer::IsVisible(const BaseSettingEntry *item) const
1230 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1231 if ((*it)->IsVisible(item)) return true;
1233 return false;
1236 /** Return number of rows needed to display the whole page */
1237 uint SettingsContainer::Length() const
1239 uint length = 0;
1240 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1241 length += (*it)->Length();
1243 return length;
1247 * Find the setting entry at row number \a row_num
1248 * @param row_num Index of entry to return
1249 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1250 * @return The requested setting entry or \c nullptr if it does not exist
1252 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1254 BaseSettingEntry *pe = nullptr;
1255 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1256 pe = (*it)->FindEntry(row_num, cur_row);
1257 if (pe != nullptr) {
1258 break;
1261 return pe;
1265 * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1266 * @param maxw Maximal width of a line help text.
1267 * @return Biggest height needed to display any help text of this (sub-)tree.
1269 uint SettingsContainer::GetMaxHelpHeight(int maxw)
1271 uint biggest = 0;
1272 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1273 biggest = std::max(biggest, (*it)->GetMaxHelpHeight(maxw));
1275 return biggest;
1280 * Draw a row in the settings panel.
1282 * @param settings_ptr Pointer to current values of all settings
1283 * @param left Left-most position in window/panel to start drawing \a first_row
1284 * @param right Right-most x position to draw strings at.
1285 * @param y Upper-most position in window/panel to start drawing \a first_row
1286 * @param first_row First row number to draw
1287 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1288 * @param selected Selected entry by the user.
1289 * @param cur_row Current row number (internal variable)
1290 * @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)
1291 * @return Row number of the next row to draw
1293 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
1295 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1296 cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1297 if (cur_row >= max_row) {
1298 break;
1301 return cur_row;
1304 /* == SettingsPage methods == */
1307 * Constructor for a sub-page in the 'advanced settings' window
1308 * @param title Title of the sub-page
1310 SettingsPage::SettingsPage(StringID title)
1312 this->title = title;
1313 this->folded = true;
1317 * Initialization of an entire setting page
1318 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1320 void SettingsPage::Init(byte level)
1322 BaseSettingEntry::Init(level);
1323 SettingsContainer::Init(level + 1);
1326 /** Recursively close all (filtered) folds of sub-pages */
1327 void SettingsPage::FoldAll()
1329 if (this->IsFiltered()) return;
1330 this->folded = true;
1332 SettingsContainer::FoldAll();
1335 /** Recursively open all (filtered) folds of sub-pages */
1336 void SettingsPage::UnFoldAll()
1338 if (this->IsFiltered()) return;
1339 this->folded = false;
1341 SettingsContainer::UnFoldAll();
1345 * Recursively accumulate the folding state of the (filtered) tree.
1346 * @param[in,out] all_folded Set to false, if one entry is not folded.
1347 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1349 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1351 if (this->IsFiltered()) return;
1353 if (this->folded) {
1354 all_unfolded = false;
1355 } else {
1356 all_folded = false;
1359 SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1363 * Update the filter state.
1364 * @param filter Filter
1365 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1366 * @return true if item remains visible
1368 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1370 if (!force_visible && !filter.string.IsEmpty()) {
1371 filter.string.ResetState();
1372 filter.string.AddLine(this->title);
1373 force_visible = filter.string.GetState();
1376 bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1377 if (visible) {
1378 CLRBITS(this->flags, SEF_FILTERED);
1379 } else {
1380 SETBITS(this->flags, SEF_FILTERED);
1382 return visible;
1386 * Check whether an entry is visible and not folded or filtered away.
1387 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1388 * @param item Entry to search for.
1389 * @return true if entry is visible.
1391 bool SettingsPage::IsVisible(const BaseSettingEntry *item) const
1393 if (this->IsFiltered()) return false;
1394 if (this == item) return true;
1395 if (this->folded) return false;
1397 return SettingsContainer::IsVisible(item);
1400 /** Return number of rows needed to display the (filtered) entry */
1401 uint SettingsPage::Length() const
1403 if (this->IsFiltered()) return 0;
1404 if (this->folded) return 1; // Only displaying the title
1406 return 1 + SettingsContainer::Length();
1410 * Find setting entry at row \a row_num
1411 * @param row_num Index of entry to return
1412 * @param cur_row Current row number
1413 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1415 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1417 if (this->IsFiltered()) return nullptr;
1418 if (row_num == *cur_row) return this;
1419 (*cur_row)++;
1420 if (this->folded) return nullptr;
1422 return SettingsContainer::FindEntry(row_num, cur_row);
1426 * Draw a row in the settings panel.
1428 * @param settings_ptr Pointer to current values of all settings
1429 * @param left Left-most position in window/panel to start drawing \a first_row
1430 * @param right Right-most x position to draw strings at.
1431 * @param y Upper-most position in window/panel to start drawing \a first_row
1432 * @param first_row First row number to draw
1433 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1434 * @param selected Selected entry by the user.
1435 * @param cur_row Current row number (internal variable)
1436 * @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)
1437 * @return Row number of the next row to draw
1439 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
1441 if (this->IsFiltered()) return cur_row;
1442 if (cur_row >= max_row) return cur_row;
1444 cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1446 if (!this->folded) {
1447 if (this->flags & SEF_LAST_FIELD) {
1448 assert(this->level < 8 * sizeof(parent_last));
1449 SetBit(parent_last, this->level); // Add own last-field state
1452 cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1455 return cur_row;
1459 * Function to draw setting value (button + text + current value)
1460 * @param settings_ptr Pointer to current values of all settings
1461 * @param left Left-most position in window/panel to start drawing
1462 * @param right Right-most position in window/panel to draw
1463 * @param y Upper-most position in window/panel to start drawing
1464 * @param highlight Highlight entry.
1466 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1468 bool rtl = _current_text_dir == TD_RTL;
1469 DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1470 DrawString(rtl ? left : left + _circle_size.width + 2, rtl ? right - _circle_size.width - 2 : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1473 /** Construct settings tree */
1474 static SettingsContainer &GetSettingsTree()
1476 static SettingsContainer *main = nullptr;
1478 if (main == nullptr)
1480 /* Build up the dynamic settings-array only once per OpenTTD session */
1481 main = new SettingsContainer();
1483 SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1485 localisation->Add(new SettingEntry("locale.units_velocity"));
1486 localisation->Add(new SettingEntry("locale.units_power"));
1487 localisation->Add(new SettingEntry("locale.units_weight"));
1488 localisation->Add(new SettingEntry("locale.units_volume"));
1489 localisation->Add(new SettingEntry("locale.units_force"));
1490 localisation->Add(new SettingEntry("locale.units_height"));
1491 localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1494 SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1496 graphics->Add(new SettingEntry("gui.zoom_min"));
1497 graphics->Add(new SettingEntry("gui.zoom_max"));
1498 graphics->Add(new SettingEntry("gui.sprite_zoom_min"));
1499 graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1500 graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1503 SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1505 sound->Add(new SettingEntry("sound.click_beep"));
1506 sound->Add(new SettingEntry("sound.confirm"));
1507 sound->Add(new SettingEntry("sound.news_ticker"));
1508 sound->Add(new SettingEntry("sound.news_full"));
1509 sound->Add(new SettingEntry("sound.new_year"));
1510 sound->Add(new SettingEntry("sound.disaster"));
1511 sound->Add(new SettingEntry("sound.vehicle"));
1512 sound->Add(new SettingEntry("sound.ambient"));
1515 SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1517 SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1519 general->Add(new SettingEntry("gui.osk_activation"));
1520 general->Add(new SettingEntry("gui.hover_delay_ms"));
1521 general->Add(new SettingEntry("gui.errmsg_duration"));
1522 general->Add(new SettingEntry("gui.window_snap_radius"));
1523 general->Add(new SettingEntry("gui.window_soft_limit"));
1524 general->Add(new SettingEntry("gui.right_mouse_wnd_close"));
1527 SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1529 viewports->Add(new SettingEntry("gui.auto_scrolling"));
1530 viewports->Add(new SettingEntry("gui.scroll_mode"));
1531 viewports->Add(new SettingEntry("gui.smooth_scroll"));
1532 /* While the horizontal scrollwheel scrolling is written as general code, only
1533 * the cocoa (OSX) driver generates input for it.
1534 * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1535 viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1536 viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1537 #ifdef __APPLE__
1538 /* We might need to emulate a right mouse button on mac */
1539 viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1540 #endif
1541 viewports->Add(new SettingEntry("gui.population_in_label"));
1542 viewports->Add(new SettingEntry("gui.liveries"));
1543 viewports->Add(new SettingEntry("construction.train_signal_side"));
1544 viewports->Add(new SettingEntry("gui.measure_tooltip"));
1545 viewports->Add(new SettingEntry("gui.loading_indicators"));
1546 viewports->Add(new SettingEntry("gui.show_track_reservation"));
1549 SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1551 construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1552 construction->Add(new SettingEntry("gui.enable_signal_gui"));
1553 construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1554 construction->Add(new SettingEntry("gui.quick_goto"));
1555 construction->Add(new SettingEntry("gui.default_rail_type"));
1558 interface->Add(new SettingEntry("gui.fast_forward_speed_limit"));
1559 interface->Add(new SettingEntry("gui.autosave"));
1560 interface->Add(new SettingEntry("gui.toolbar_pos"));
1561 interface->Add(new SettingEntry("gui.statusbar_pos"));
1562 interface->Add(new SettingEntry("gui.prefer_teamchat"));
1563 interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1564 interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1565 interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1566 interface->Add(new SettingEntry("gui.expenses_layout"));
1567 interface->Add(new SettingEntry("gui.show_newgrf_name"));
1570 SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1572 advisors->Add(new SettingEntry("gui.coloured_news_year"));
1573 advisors->Add(new SettingEntry("news_display.general"));
1574 advisors->Add(new SettingEntry("news_display.new_vehicles"));
1575 advisors->Add(new SettingEntry("news_display.accident"));
1576 advisors->Add(new SettingEntry("news_display.company_info"));
1577 advisors->Add(new SettingEntry("news_display.acceptance"));
1578 advisors->Add(new SettingEntry("news_display.arrival_player"));
1579 advisors->Add(new SettingEntry("news_display.arrival_other"));
1580 advisors->Add(new SettingEntry("news_display.advice"));
1581 advisors->Add(new SettingEntry("gui.order_review_system"));
1582 advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1583 advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1584 advisors->Add(new SettingEntry("gui.show_finances"));
1585 advisors->Add(new SettingEntry("news_display.economy"));
1586 advisors->Add(new SettingEntry("news_display.subsidies"));
1587 advisors->Add(new SettingEntry("news_display.open"));
1588 advisors->Add(new SettingEntry("news_display.close"));
1589 advisors->Add(new SettingEntry("news_display.production_player"));
1590 advisors->Add(new SettingEntry("news_display.production_other"));
1591 advisors->Add(new SettingEntry("news_display.production_nobody"));
1594 SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1596 company->Add(new SettingEntry("gui.semaphore_build_before"));
1597 company->Add(new SettingEntry("gui.default_signal_type"));
1598 company->Add(new SettingEntry("gui.cycle_signal_types"));
1599 company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1600 company->Add(new SettingEntry("gui.auto_remove_signals"));
1601 company->Add(new SettingEntry("gui.new_nonstop"));
1602 company->Add(new SettingEntry("gui.stop_location"));
1603 company->Add(new SettingEntry("gui.starting_colour"));
1604 company->Add(new SettingEntry("company.engine_renew"));
1605 company->Add(new SettingEntry("company.engine_renew_months"));
1606 company->Add(new SettingEntry("company.engine_renew_money"));
1607 company->Add(new SettingEntry("vehicle.servint_ispercent"));
1608 company->Add(new SettingEntry("vehicle.servint_trains"));
1609 company->Add(new SettingEntry("vehicle.servint_roadveh"));
1610 company->Add(new SettingEntry("vehicle.servint_ships"));
1611 company->Add(new SettingEntry("vehicle.servint_aircraft"));
1614 SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1616 accounting->Add(new SettingEntry("economy.inflation"));
1617 accounting->Add(new SettingEntry("difficulty.initial_interest"));
1618 accounting->Add(new SettingEntry("difficulty.max_loan"));
1619 accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1620 accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1621 accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1622 accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1623 accounting->Add(new SettingEntry("difficulty.construction_cost"));
1626 SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1628 SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1630 physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1631 physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1632 physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1633 physics->Add(new SettingEntry("vehicle.freight_trains"));
1634 physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1635 physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1636 physics->Add(new SettingEntry("vehicle.smoke_amount"));
1637 physics->Add(new SettingEntry("vehicle.plane_speed"));
1640 SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1642 routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1643 routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1644 routing->Add(new SettingEntry("pf.reverse_at_signals"));
1645 routing->Add(new SettingEntry("pf.forbid_90_deg"));
1646 routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1647 routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1650 vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1651 vehicles->Add(new SettingEntry("order.serviceathelipad"));
1654 SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1656 limitations->Add(new SettingEntry("construction.command_pause_level"));
1657 limitations->Add(new SettingEntry("construction.autoslope"));
1658 limitations->Add(new SettingEntry("construction.extra_dynamite"));
1659 limitations->Add(new SettingEntry("construction.max_heightlevel"));
1660 limitations->Add(new SettingEntry("construction.max_bridge_length"));
1661 limitations->Add(new SettingEntry("construction.max_bridge_height"));
1662 limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1663 limitations->Add(new SettingEntry("station.never_expire_airports"));
1664 limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1665 limitations->Add(new SettingEntry("vehicle.max_trains"));
1666 limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1667 limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1668 limitations->Add(new SettingEntry("vehicle.max_ships"));
1669 limitations->Add(new SettingEntry("vehicle.max_train_length"));
1670 limitations->Add(new SettingEntry("station.station_spread"));
1671 limitations->Add(new SettingEntry("station.distant_join_stations"));
1672 limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1673 limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1674 limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1677 SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1679 disasters->Add(new SettingEntry("difficulty.disasters"));
1680 disasters->Add(new SettingEntry("difficulty.economy"));
1681 disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1682 disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1685 SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1687 genworld->Add(new SettingEntry("game_creation.landscape"));
1688 genworld->Add(new SettingEntry("game_creation.land_generator"));
1689 genworld->Add(new SettingEntry("difficulty.terrain_type"));
1690 genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1691 genworld->Add(new SettingEntry("game_creation.variety"));
1692 genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1693 genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1694 genworld->Add(new SettingEntry("game_creation.tree_placer"));
1695 genworld->Add(new SettingEntry("vehicle.road_side"));
1696 genworld->Add(new SettingEntry("economy.larger_towns"));
1697 genworld->Add(new SettingEntry("economy.initial_city_size"));
1698 genworld->Add(new SettingEntry("economy.town_layout"));
1699 genworld->Add(new SettingEntry("difficulty.industry_density"));
1700 genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1701 genworld->Add(new SettingEntry("game_creation.ending_year"));
1704 SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1706 SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1708 authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1709 authorities->Add(new SettingEntry("economy.bribe"));
1710 authorities->Add(new SettingEntry("economy.exclusive_rights"));
1711 authorities->Add(new SettingEntry("economy.fund_roads"));
1712 authorities->Add(new SettingEntry("economy.fund_buildings"));
1713 authorities->Add(new SettingEntry("economy.station_noise_level"));
1716 SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1718 towns->Add(new SettingEntry("economy.town_growth_rate"));
1719 towns->Add(new SettingEntry("economy.allow_town_roads"));
1720 towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1721 towns->Add(new SettingEntry("economy.found_town"));
1722 towns->Add(new SettingEntry("economy.town_cargogen_mode"));
1725 SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1727 industries->Add(new SettingEntry("construction.raw_industry_construction"));
1728 industries->Add(new SettingEntry("construction.industry_platform"));
1729 industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1730 industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1731 industries->Add(new SettingEntry("economy.type"));
1732 industries->Add(new SettingEntry("station.serve_neutral_industries"));
1735 SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1737 cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1738 cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1739 cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1740 cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1741 cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1742 cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1743 cdist->Add(new SettingEntry("linkgraph.accuracy"));
1744 cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1745 cdist->Add(new SettingEntry("linkgraph.demand_size"));
1746 cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1749 environment->Add(new SettingEntry("station.modified_catchment"));
1750 environment->Add(new SettingEntry("construction.extra_tree_placement"));
1753 SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1755 SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1757 npc->Add(new SettingEntry("script.settings_profile"));
1758 npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1759 npc->Add(new SettingEntry("script.script_max_memory_megabytes"));
1760 npc->Add(new SettingEntry("difficulty.competitor_speed"));
1761 npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1762 npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1763 npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1764 npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1765 npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1768 ai->Add(new SettingEntry("economy.give_money"));
1769 ai->Add(new SettingEntry("economy.allow_shares"));
1770 ai->Add(new SettingEntry("economy.min_years_for_shares"));
1773 main->Init();
1775 return *main;
1778 static const StringID _game_settings_restrict_dropdown[] = {
1779 STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1780 STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1781 STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1782 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1783 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1785 static_assert(lengthof(_game_settings_restrict_dropdown) == RM_END);
1787 /** Warnings about hidden search results. */
1788 enum WarnHiddenResult {
1789 WHR_NONE, ///< Nothing was filtering matches away.
1790 WHR_CATEGORY, ///< Category setting filtered matches away.
1791 WHR_TYPE, ///< Type setting filtered matches away.
1792 WHR_CATEGORY_TYPE, ///< Both category and type settings filtered matches away.
1795 /** Window to edit settings of the game. */
1796 struct GameSettingsWindow : Window {
1797 static const int SETTINGTREE_LEFT_OFFSET = 5; ///< Position of left edge of setting values
1798 static const int SETTINGTREE_RIGHT_OFFSET = 5; ///< Position of right edge of setting values
1799 static const int SETTINGTREE_TOP_OFFSET = 5; ///< Position of top edge of setting values
1800 static const int SETTINGTREE_BOTTOM_OFFSET = 5; ///< Position of bottom edge of setting values
1802 static GameSettings *settings_ptr; ///< Pointer to the game settings being displayed and modified.
1804 SettingEntry *valuewindow_entry; ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
1805 SettingEntry *clicked_entry; ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
1806 SettingEntry *last_clicked; ///< If non-nullptr, pointer to the last clicked setting.
1807 SettingEntry *valuedropdown_entry; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
1808 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
1810 SettingFilter filter; ///< Filter for the list.
1811 QueryString filter_editbox; ///< Filter editbox;
1812 bool manually_changed_folding; ///< Whether the user expanded/collapsed something manually.
1813 WarnHiddenResult warn_missing; ///< Whether and how to warn about missing search results.
1814 int warn_lines; ///< Number of lines used for warning about missing search results.
1816 Scrollbar *vscroll;
1818 GameSettingsWindow(WindowDesc *desc) : Window(desc), filter_editbox(50)
1820 this->warn_missing = WHR_NONE;
1821 this->warn_lines = 0;
1822 this->filter.mode = (RestrictionMode)_settings_client.gui.settings_restriction_mode;
1823 this->filter.min_cat = RM_ALL;
1824 this->filter.type = ST_ALL;
1825 this->filter.type_hides = false;
1826 this->settings_ptr = &GetGameSettings();
1828 _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1829 GetSettingsTree().FoldAll(); // Close all sub-pages
1831 this->valuewindow_entry = nullptr; // No setting entry for which a entry window is opened
1832 this->clicked_entry = nullptr; // No numeric setting buttons are depressed
1833 this->last_clicked = nullptr;
1834 this->valuedropdown_entry = nullptr;
1835 this->closing_dropdown = false;
1836 this->manually_changed_folding = false;
1838 this->CreateNestedTree();
1839 this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1840 this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS);
1842 this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1843 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
1844 this->SetFocusedWidget(WID_GS_FILTER);
1846 this->InvalidateData();
1849 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1851 switch (widget) {
1852 case WID_GS_OPTIONSPANEL:
1853 resize->height = SETTING_HEIGHT = std::max({(int)_circle_size.height, SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL}) + 1;
1854 resize->width = 1;
1856 size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1857 break;
1859 case WID_GS_HELP_TEXT: {
1860 static const StringID setting_types[] = {
1861 STR_CONFIG_SETTING_TYPE_CLIENT,
1862 STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1863 STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1865 for (uint i = 0; i < lengthof(setting_types); i++) {
1866 SetDParam(0, setting_types[i]);
1867 size->width = std::max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1869 size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1870 std::max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1871 break;
1874 case WID_GS_RESTRICT_CATEGORY:
1875 case WID_GS_RESTRICT_TYPE:
1876 size->width = std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1877 break;
1879 default:
1880 break;
1884 void OnPaint() override
1886 if (this->closing_dropdown) {
1887 this->closing_dropdown = false;
1888 assert(this->valuedropdown_entry != nullptr);
1889 this->valuedropdown_entry->SetButtons(0);
1890 this->valuedropdown_entry = nullptr;
1893 /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1894 const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1895 StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1896 int new_warn_lines;
1897 if (this->warn_missing == WHR_NONE) {
1898 new_warn_lines = 0;
1899 } else {
1900 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1901 new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1903 if (this->warn_lines != new_warn_lines) {
1904 this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
1905 this->warn_lines = new_warn_lines;
1908 this->DrawWidgets();
1910 /* Draw the 'some search results are hidden' notice. */
1911 if (this->warn_missing != WHR_NONE) {
1912 const int left = panel->pos_x;
1913 const int right = left + panel->current_x - 1;
1914 const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
1915 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1916 if (this->warn_lines == 1) {
1917 /* If the warning fits at one line, center it. */
1918 DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1919 } else {
1920 DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1925 void SetStringParameters(int widget) const override
1927 switch (widget) {
1928 case WID_GS_RESTRICT_DROPDOWN:
1929 SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
1930 break;
1932 case WID_GS_TYPE_DROPDOWN:
1933 switch (this->filter.type) {
1934 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
1935 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
1936 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
1937 default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
1939 break;
1943 DropDownList BuildDropDownList(int widget) const
1945 DropDownList list;
1946 switch (widget) {
1947 case WID_GS_RESTRICT_DROPDOWN:
1948 for (int mode = 0; mode != RM_END; mode++) {
1949 /* If we are in adv. settings screen for the new game's settings,
1950 * we don't want to allow comparing with new game's settings. */
1951 bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
1953 list.emplace_back(new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled));
1955 break;
1957 case WID_GS_TYPE_DROPDOWN:
1958 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false));
1959 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));
1960 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));
1961 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false));
1962 break;
1964 return list;
1967 void DrawWidget(const Rect &r, int widget) const override
1969 switch (widget) {
1970 case WID_GS_OPTIONSPANEL: {
1971 int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
1972 uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
1973 int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
1974 this->vscroll->GetPosition(), last_row, this->last_clicked);
1975 if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
1976 break;
1979 case WID_GS_HELP_TEXT:
1980 if (this->last_clicked != nullptr) {
1981 const SettingDesc *sd = this->last_clicked->setting;
1983 int y = r.top;
1984 switch (sd->GetType()) {
1985 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
1986 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
1987 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
1988 default: NOT_REACHED();
1990 DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
1991 y += FONT_HEIGHT_NORMAL;
1993 int32 default_value = ReadValue(&sd->desc.def, sd->save.conv);
1994 this->last_clicked->SetValueDParams(0, default_value);
1995 DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
1996 y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
1998 DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
2000 break;
2002 default:
2003 break;
2008 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2009 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2011 void SetDisplayedHelpText(SettingEntry *pe)
2013 if (this->last_clicked != pe) this->SetDirty();
2014 this->last_clicked = pe;
2017 void OnClick(Point pt, int widget, int click_count) override
2019 switch (widget) {
2020 case WID_GS_EXPAND_ALL:
2021 this->manually_changed_folding = true;
2022 GetSettingsTree().UnFoldAll();
2023 this->InvalidateData();
2024 break;
2026 case WID_GS_COLLAPSE_ALL:
2027 this->manually_changed_folding = true;
2028 GetSettingsTree().FoldAll();
2029 this->InvalidateData();
2030 break;
2032 case WID_GS_RESTRICT_DROPDOWN: {
2033 DropDownList list = this->BuildDropDownList(widget);
2034 if (!list.empty()) {
2035 ShowDropDownList(this, std::move(list), this->filter.mode, widget);
2037 break;
2040 case WID_GS_TYPE_DROPDOWN: {
2041 DropDownList list = this->BuildDropDownList(widget);
2042 if (!list.empty()) {
2043 ShowDropDownList(this, std::move(list), this->filter.type, widget);
2045 break;
2049 if (widget != WID_GS_OPTIONSPANEL) return;
2051 uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2052 if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2053 btn -= this->warn_lines;
2055 uint cur_row = 0;
2056 BaseSettingEntry *clicked_entry = GetSettingsTree().FindEntry(btn, &cur_row);
2058 if (clicked_entry == nullptr) return; // Clicked below the last setting of the page
2060 int x = (_current_text_dir == TD_RTL ? this->width - 1 - pt.x : pt.x) - SETTINGTREE_LEFT_OFFSET - (clicked_entry->level + 1) * LEVEL_WIDTH; // Shift x coordinate
2061 if (x < 0) return; // Clicked left of the entry
2063 SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2064 if (clicked_page != nullptr) {
2065 this->SetDisplayedHelpText(nullptr);
2066 clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2068 this->manually_changed_folding = true;
2070 this->InvalidateData();
2071 return;
2074 SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2075 assert(pe != nullptr);
2076 const SettingDesc *sd = pe->setting;
2078 /* return if action is only active in network, or only settable by server */
2079 if (!sd->IsEditable()) {
2080 this->SetDisplayedHelpText(pe);
2081 return;
2084 const void *var = ResolveVariableAddress(settings_ptr, sd);
2085 int32 value = (int32)ReadValue(var, sd->save.conv);
2087 /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2088 if (x < SETTING_BUTTON_WIDTH && (sd->desc.flags & SGF_MULTISTRING)) {
2089 const SettingDescBase *sdb = &sd->desc;
2090 this->SetDisplayedHelpText(pe);
2092 if (this->valuedropdown_entry == pe) {
2093 /* unclick the dropdown */
2094 HideDropDownMenu(this);
2095 this->closing_dropdown = false;
2096 this->valuedropdown_entry->SetButtons(0);
2097 this->valuedropdown_entry = nullptr;
2098 } else {
2099 if (this->valuedropdown_entry != nullptr) this->valuedropdown_entry->SetButtons(0);
2100 this->closing_dropdown = false;
2102 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2103 int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2105 Rect wi_rect;
2106 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2107 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2108 wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2109 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2111 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2112 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2113 this->valuedropdown_entry = pe;
2114 this->valuedropdown_entry->SetButtons(SEF_LEFT_DEPRESSED);
2116 DropDownList list;
2117 for (int i = sdb->min; i <= (int)sdb->max; i++) {
2118 list.emplace_back(new DropDownListStringItem(sdb->str_val + i - sdb->min, i, false));
2121 ShowDropDownListAt(this, std::move(list), value, -1, wi_rect, COLOUR_ORANGE, true);
2124 this->SetDirty();
2125 } else if (x < SETTING_BUTTON_WIDTH) {
2126 this->SetDisplayedHelpText(pe);
2127 const SettingDescBase *sdb = &sd->desc;
2128 int32 oldvalue = value;
2130 switch (sdb->cmd) {
2131 case SDT_BOOLX: value ^= 1; break;
2132 case SDT_ONEOFMANY:
2133 case SDT_NUMX: {
2134 /* Add a dynamic step-size to the scroller. In a maximum of
2135 * 50-steps you should be able to get from min to max,
2136 * unless specified otherwise in the 'interval' variable
2137 * of the current setting. */
2138 uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
2139 if (step == 0) step = 1;
2141 /* don't allow too fast scrolling */
2142 if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2143 _left_button_clicked = false;
2144 return;
2147 /* Increase or decrease the value and clamp it to extremes */
2148 if (x >= SETTING_BUTTON_WIDTH / 2) {
2149 value += step;
2150 if (sdb->min < 0) {
2151 assert((int32)sdb->max >= 0);
2152 if (value > (int32)sdb->max) value = (int32)sdb->max;
2153 } else {
2154 if ((uint32)value > sdb->max) value = (int32)sdb->max;
2156 if (value < sdb->min) value = sdb->min; // skip between "disabled" and minimum
2157 } else {
2158 value -= step;
2159 if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
2162 /* Set up scroller timeout for numeric values */
2163 if (value != oldvalue) {
2164 if (this->clicked_entry != nullptr) { // Release previous buttons if any
2165 this->clicked_entry->SetButtons(0);
2167 this->clicked_entry = pe;
2168 this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2169 this->SetTimeout();
2170 _left_button_clicked = false;
2172 break;
2175 default: NOT_REACHED();
2178 if (value != oldvalue) {
2179 if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2180 SetCompanySetting(pe->index, value);
2181 } else {
2182 SetSettingValue(pe->index, value);
2184 this->SetDirty();
2186 } else {
2187 /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2188 if (this->last_clicked == pe && sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
2189 int64 value64 = value;
2190 /* Show the correct currency-translated value */
2191 if (sd->desc.flags & SGF_CURRENCY) value64 *= _currency->rate;
2193 this->valuewindow_entry = pe;
2194 SetDParam(0, value64);
2195 /* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2196 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 15, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2198 this->SetDisplayedHelpText(pe);
2202 void OnTimeout() override
2204 if (this->clicked_entry != nullptr) { // On timeout, release any depressed buttons
2205 this->clicked_entry->SetButtons(0);
2206 this->clicked_entry = nullptr;
2207 this->SetDirty();
2211 void OnQueryTextFinished(char *str) override
2213 /* The user pressed cancel */
2214 if (str == nullptr) return;
2216 assert(this->valuewindow_entry != nullptr);
2217 const SettingDesc *sd = this->valuewindow_entry->setting;
2219 int32 value;
2220 if (!StrEmpty(str)) {
2221 long long llvalue = atoll(str);
2223 /* Save the correct currency-translated value */
2224 if (sd->desc.flags & SGF_CURRENCY) llvalue /= _currency->rate;
2226 value = (int32)ClampToI32(llvalue);
2227 } else {
2228 value = (int32)(size_t)sd->desc.def;
2231 if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2232 SetCompanySetting(this->valuewindow_entry->index, value);
2233 } else {
2234 SetSettingValue(this->valuewindow_entry->index, value);
2236 this->SetDirty();
2239 void OnDropdownSelect(int widget, int index) override
2241 switch (widget) {
2242 case WID_GS_RESTRICT_DROPDOWN:
2243 this->filter.mode = (RestrictionMode)index;
2244 if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2245 this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2247 if (!this->manually_changed_folding) {
2248 /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2249 GetSettingsTree().UpdateFilterState(this->filter, false);
2250 GetSettingsTree().UnFoldAll();
2252 } else {
2253 /* Non-'changes' filter. Save as default. */
2254 _settings_client.gui.settings_restriction_mode = this->filter.mode;
2256 this->InvalidateData();
2257 break;
2259 case WID_GS_TYPE_DROPDOWN:
2260 this->filter.type = (SettingType)index;
2261 this->InvalidateData();
2262 break;
2264 default:
2265 if (widget < 0) {
2266 /* Deal with drop down boxes on the panel. */
2267 assert(this->valuedropdown_entry != nullptr);
2268 const SettingDesc *sd = this->valuedropdown_entry->setting;
2269 assert(sd->desc.flags & SGF_MULTISTRING);
2271 if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2272 SetCompanySetting(this->valuedropdown_entry->index, index);
2273 } else {
2274 SetSettingValue(this->valuedropdown_entry->index, index);
2277 this->SetDirty();
2279 break;
2283 void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
2285 if (widget >= 0) {
2286 /* Normally the default implementation of OnDropdownClose() takes care of
2287 * a few things. We want that behaviour here too, but only for
2288 * "normal" dropdown boxes. The special dropdown boxes added for every
2289 * setting that needs one can't have this call. */
2290 Window::OnDropdownClose(pt, widget, index, instant_close);
2291 } else {
2292 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2293 * the same dropdown button was clicked again, and then not open the dropdown again.
2294 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2295 * after OnClick. */
2296 assert(this->valuedropdown_entry != nullptr);
2297 this->closing_dropdown = true;
2298 this->SetDirty();
2302 void OnInvalidateData(int data = 0, bool gui_scope = true) override
2304 if (!gui_scope) return;
2306 /* Update which settings are to be visible. */
2307 RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2308 this->filter.min_cat = min_level;
2309 this->filter.type_hides = false;
2310 GetSettingsTree().UpdateFilterState(this->filter, false);
2312 if (this->filter.string.IsEmpty()) {
2313 this->warn_missing = WHR_NONE;
2314 } else if (min_level < this->filter.min_cat) {
2315 this->warn_missing = this->filter.type_hides ? WHR_CATEGORY_TYPE : WHR_CATEGORY;
2316 } else {
2317 this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2319 this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2321 if (this->last_clicked != nullptr && !GetSettingsTree().IsVisible(this->last_clicked)) {
2322 this->SetDisplayedHelpText(nullptr);
2325 bool all_folded = true;
2326 bool all_unfolded = true;
2327 GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2328 this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2329 this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2332 void OnEditboxChanged(int wid) override
2334 if (wid == WID_GS_FILTER) {
2335 this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2336 if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2337 /* User never expanded/collapsed single pages and entered a filter term.
2338 * Expand everything, to save weird expand clicks, */
2339 GetSettingsTree().UnFoldAll();
2341 this->InvalidateData();
2345 void OnResize() override
2347 this->vscroll->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET);
2351 GameSettings *GameSettingsWindow::settings_ptr = nullptr;
2353 static const NWidgetPart _nested_settings_selection_widgets[] = {
2354 NWidget(NWID_HORIZONTAL),
2355 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2356 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2357 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2358 EndContainer(),
2359 NWidget(WWT_PANEL, COLOUR_MAUVE),
2360 NWidget(NWID_VERTICAL), SetPIP(0, WD_PAR_VSEP_NORMAL, 0), SetPadding(WD_TEXTPANEL_TOP, 0, WD_TEXTPANEL_BOTTOM, 0),
2361 NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2362 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2363 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),
2364 EndContainer(),
2365 NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2366 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2367 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),
2368 EndContainer(),
2369 EndContainer(),
2370 NWidget(NWID_HORIZONTAL), SetPadding(0, 0, WD_TEXTPANEL_BOTTOM, 0),
2371 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2372 NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2373 NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2374 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2375 EndContainer(),
2376 EndContainer(),
2377 NWidget(NWID_HORIZONTAL),
2378 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GS_OPTIONSPANEL), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR), EndContainer(),
2379 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2380 EndContainer(),
2381 NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2382 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2383 SetPadding(WD_FRAMETEXT_TOP, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_BOTTOM, WD_FRAMETEXT_LEFT),
2384 EndContainer(),
2385 NWidget(NWID_HORIZONTAL),
2386 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2387 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2388 NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2389 EndContainer(),
2390 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2391 EndContainer(),
2394 static WindowDesc _settings_selection_desc(
2395 WDP_CENTER, "settings", 510, 450,
2396 WC_GAME_OPTIONS, WC_NONE,
2398 _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2401 /** Open advanced settings window. */
2402 void ShowGameSettings()
2404 DeleteWindowByClass(WC_GAME_OPTIONS);
2405 new GameSettingsWindow(&_settings_selection_desc);
2410 * Draw [<][>] boxes.
2411 * @param x the x position to draw
2412 * @param y the y position to draw
2413 * @param button_colour the colour of the button
2414 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2415 * @param clickable_left is the left button clickable?
2416 * @param clickable_right is the right button clickable?
2418 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2420 int colour = _colour_gradient[button_colour][2];
2421 Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2423 DrawFrameRect(x, y, x + dim.width - 1, y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2424 DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2425 DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2426 DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2428 /* Grey out the buttons that aren't clickable */
2429 bool rtl = _current_text_dir == TD_RTL;
2430 if (rtl ? !clickable_right : !clickable_left) {
2431 GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2433 if (rtl ? !clickable_left : !clickable_right) {
2434 GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2439 * Draw a dropdown button.
2440 * @param x the x position to draw
2441 * @param y the y position to draw
2442 * @param button_colour the colour of the button
2443 * @param state true = lowered
2444 * @param clickable is the button clickable?
2446 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2448 int colour = _colour_gradient[button_colour][2];
2450 DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2451 DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2453 if (!clickable) {
2454 GfxFillRect(x + 1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2459 * Draw a toggle button.
2460 * @param x the x position to draw
2461 * @param y the y position to draw
2462 * @param state true = lowered
2463 * @param clickable is the button clickable?
2465 void DrawBoolButton(int x, int y, bool state, bool clickable)
2467 static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2468 DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2471 struct CustomCurrencyWindow : Window {
2472 int query_widget;
2474 CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2476 this->InitNested();
2478 SetButtonState();
2481 void SetButtonState()
2483 this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2484 this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2485 this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2486 this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2489 void SetStringParameters(int widget) const override
2491 switch (widget) {
2492 case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2493 case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2494 case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2495 case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2496 case WID_CC_YEAR:
2497 SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2498 SetDParam(1, _custom_currency.to_euro);
2499 break;
2501 case WID_CC_PREVIEW:
2502 SetDParam(0, 10000);
2503 break;
2507 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2509 switch (widget) {
2510 /* Set the appropriate width for the edit 'buttons' */
2511 case WID_CC_SEPARATOR_EDIT:
2512 case WID_CC_PREFIX_EDIT:
2513 case WID_CC_SUFFIX_EDIT:
2514 size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2515 break;
2517 /* Make sure the window is wide enough for the widest exchange rate */
2518 case WID_CC_RATE:
2519 SetDParam(0, 1);
2520 SetDParam(1, INT32_MAX);
2521 *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2522 break;
2526 void OnClick(Point pt, int widget, int click_count) override
2528 int line = 0;
2529 int len = 0;
2530 StringID str = 0;
2531 CharSetFilter afilter = CS_ALPHANUMERAL;
2533 switch (widget) {
2534 case WID_CC_RATE_DOWN:
2535 if (_custom_currency.rate > 1) _custom_currency.rate--;
2536 if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2537 this->EnableWidget(WID_CC_RATE_UP);
2538 break;
2540 case WID_CC_RATE_UP:
2541 if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2542 if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2543 this->EnableWidget(WID_CC_RATE_DOWN);
2544 break;
2546 case WID_CC_RATE:
2547 SetDParam(0, _custom_currency.rate);
2548 str = STR_JUST_INT;
2549 len = 5;
2550 line = WID_CC_RATE;
2551 afilter = CS_NUMERAL;
2552 break;
2554 case WID_CC_SEPARATOR_EDIT:
2555 case WID_CC_SEPARATOR:
2556 SetDParamStr(0, _custom_currency.separator);
2557 str = STR_JUST_RAW_STRING;
2558 len = 1;
2559 line = WID_CC_SEPARATOR;
2560 break;
2562 case WID_CC_PREFIX_EDIT:
2563 case WID_CC_PREFIX:
2564 SetDParamStr(0, _custom_currency.prefix);
2565 str = STR_JUST_RAW_STRING;
2566 len = 12;
2567 line = WID_CC_PREFIX;
2568 break;
2570 case WID_CC_SUFFIX_EDIT:
2571 case WID_CC_SUFFIX:
2572 SetDParamStr(0, _custom_currency.suffix);
2573 str = STR_JUST_RAW_STRING;
2574 len = 12;
2575 line = WID_CC_SUFFIX;
2576 break;
2578 case WID_CC_YEAR_DOWN:
2579 _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2580 if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2581 this->EnableWidget(WID_CC_YEAR_UP);
2582 break;
2584 case WID_CC_YEAR_UP:
2585 _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2586 if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2587 this->EnableWidget(WID_CC_YEAR_DOWN);
2588 break;
2590 case WID_CC_YEAR:
2591 SetDParam(0, _custom_currency.to_euro);
2592 str = STR_JUST_INT;
2593 len = 7;
2594 line = WID_CC_YEAR;
2595 afilter = CS_NUMERAL;
2596 break;
2599 if (len != 0) {
2600 this->query_widget = line;
2601 ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2604 this->SetTimeout();
2605 this->SetDirty();
2608 void OnQueryTextFinished(char *str) override
2610 if (str == nullptr) return;
2612 switch (this->query_widget) {
2613 case WID_CC_RATE:
2614 _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2615 break;
2617 case WID_CC_SEPARATOR: // Thousands separator
2618 strecpy(_custom_currency.separator, str, lastof(_custom_currency.separator));
2619 break;
2621 case WID_CC_PREFIX:
2622 strecpy(_custom_currency.prefix, str, lastof(_custom_currency.prefix));
2623 break;
2625 case WID_CC_SUFFIX:
2626 strecpy(_custom_currency.suffix, str, lastof(_custom_currency.suffix));
2627 break;
2629 case WID_CC_YEAR: { // Year to switch to euro
2630 int val = atoi(str);
2632 _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : std::min(val, MAX_YEAR));
2633 break;
2636 MarkWholeScreenDirty();
2637 SetButtonState();
2640 void OnTimeout() override
2642 this->SetDirty();
2646 static const NWidgetPart _nested_cust_currency_widgets[] = {
2647 NWidget(NWID_HORIZONTAL),
2648 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2649 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2650 EndContainer(),
2651 NWidget(WWT_PANEL, COLOUR_GREY),
2652 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(7, 3, 0),
2653 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2654 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2655 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2656 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2657 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2658 EndContainer(),
2659 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2660 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2661 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2662 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2663 EndContainer(),
2664 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2665 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2666 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2667 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2668 EndContainer(),
2669 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2670 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2671 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2672 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2673 EndContainer(),
2674 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2675 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2676 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2677 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2678 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2679 EndContainer(),
2680 EndContainer(),
2681 NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2682 SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2683 EndContainer(),
2686 static WindowDesc _cust_currency_desc(
2687 WDP_CENTER, nullptr, 0, 0,
2688 WC_CUSTOM_CURRENCY, WC_NONE,
2690 _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2693 /** Open custom currency window. */
2694 static void ShowCustCurrency()
2696 DeleteWindowById(WC_CUSTOM_CURRENCY, 0);
2697 new CustomCurrencyWindow(&_cust_currency_desc);