Fix #8316: Make sort industries by production and transported with a cargo filter...
[openttd-github.git] / src / settings_gui.cpp
blob04deb93ebc8f9b321f3b293dc94c16dc9849ecd3
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file settings_gui.cpp GUI for settings. */
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "error.h"
13 #include "settings_gui.h"
14 #include "textbuf_gui.h"
15 #include "command_func.h"
16 #include "network/network.h"
17 #include "town.h"
18 #include "settings_internal.h"
19 #include "strings_func.h"
20 #include "window_func.h"
21 #include "string_func.h"
22 #include "widgets/dropdown_type.h"
23 #include "widgets/dropdown_func.h"
24 #include "widgets/slider_func.h"
25 #include "highscore.h"
26 #include "base_media_base.h"
27 #include "company_base.h"
28 #include "company_func.h"
29 #include "viewport_func.h"
30 #include "core/geometry_func.hpp"
31 #include "ai/ai.hpp"
32 #include "blitter/factory.hpp"
33 #include "language.h"
34 #include "textfile_gui.h"
35 #include "stringfilter_type.h"
36 #include "querystring_gui.h"
37 #include "fontcache.h"
38 #include "zoom_func.h"
39 #include "rev.h"
40 #include "video/video_driver.hpp"
41 #include "music/music_driver.hpp"
43 #include <vector>
44 #include <iterator>
46 #include "safeguards.h"
47 #include "video/video_driver.hpp"
50 static const StringID _autosave_dropdown[] = {
51 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
52 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
53 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
54 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
55 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
56 INVALID_STRING_ID,
59 static const StringID _gui_zoom_dropdown[] = {
60 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_AUTO,
61 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL,
62 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM,
63 STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM,
64 INVALID_STRING_ID,
67 static const StringID _font_zoom_dropdown[] = {
68 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_AUTO,
69 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_NORMAL,
70 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_2X_ZOOM,
71 STR_GAME_OPTIONS_FONT_ZOOM_DROPDOWN_4X_ZOOM,
72 INVALID_STRING_ID,
75 static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
77 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd);
79 /**
80 * Get index of the current screen resolution.
81 * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
83 static uint GetCurrentResolutionIndex()
85 auto it = std::find(_resolutions.begin(), _resolutions.end(), Dimension(_screen.width, _screen.height));
86 return std::distance(_resolutions.begin(), it);
89 static void ShowCustCurrency();
91 template <class T>
92 static DropDownList BuildSetDropDownList(int *selected_index, bool allow_selection)
94 int n = T::GetNumSets();
95 *selected_index = T::GetIndexOfUsedSet();
97 DropDownList list;
98 for (int i = 0; i < n; i++) {
99 list.emplace_back(new DropDownListCharStringItem(T::GetSet(i)->name, i, !allow_selection && (*selected_index != i)));
102 return list;
105 DropDownList BuildMusicSetDropDownList(int *selected_index)
107 return BuildSetDropDownList<BaseMusic>(selected_index, true);
110 /** Window for displaying the textfile of a BaseSet. */
111 template <class TBaseSet>
112 struct BaseSetTextfileWindow : public TextfileWindow {
113 const TBaseSet* baseset; ///< View the textfile of this BaseSet.
114 StringID content_type; ///< STR_CONTENT_TYPE_xxx for title.
116 BaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
118 const char *textfile = this->baseset->GetTextfile(file_type);
119 this->LoadTextfile(textfile, BASESET_DIR);
122 void SetStringParameters(int widget) const override
124 if (widget == WID_TF_CAPTION) {
125 SetDParam(0, content_type);
126 SetDParamStr(1, this->baseset->name);
132 * Open the BaseSet version of the textfile window.
133 * @param file_type The type of textfile to display.
134 * @param baseset The BaseSet to use.
135 * @param content_type STR_CONTENT_TYPE_xxx for title.
137 template <class TBaseSet>
138 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type)
140 CloseWindowById(WC_TEXTFILE, file_type);
141 new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
144 std::set<int> _refresh_rates = { 30, 60, 75, 90, 100, 120, 144, 240 };
147 * Add the refresh rate from the config and the refresh rates from all the monitors to
148 * our list of refresh rates shown in the GUI.
150 static void AddCustomRefreshRates()
152 /* Add the refresh rate as selected in the config. */
153 _refresh_rates.insert(_settings_client.gui.refresh_rate);
155 /* Add all the refresh rates of all monitors connected to the machine. */
156 std::vector<int> monitorRates = VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
157 std::copy(monitorRates.begin(), monitorRates.end(), std::inserter(_refresh_rates, _refresh_rates.end()));
160 struct GameOptionsWindow : Window {
161 GameSettings *opt;
162 bool reload;
164 GameOptionsWindow(WindowDesc *desc) : Window(desc)
166 this->opt = &GetGameSettings();
167 this->reload = false;
169 AddCustomRefreshRates();
171 this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS);
172 this->OnInvalidateData(0);
175 void Close() override
177 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
178 CloseWindowByClass(WC_TEXTFILE);
179 if (this->reload) _switch_mode = SM_MENU;
180 this->Window::Close();
184 * Build the dropdown list for a specific widget.
185 * @param widget Widget to build list for
186 * @param selected_index Currently selected item
187 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
189 DropDownList BuildDropDownList(int widget, int *selected_index) const
191 DropDownList list;
192 switch (widget) {
193 case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
194 *selected_index = this->opt->locale.currency;
195 StringID *items = BuildCurrencyDropdown();
196 uint64 disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
198 /* Add non-custom currencies; sorted naturally */
199 for (uint i = 0; i < CURRENCY_END; items++, i++) {
200 if (i == CURRENCY_CUSTOM) continue;
201 list.emplace_back(new DropDownListStringItem(*items, i, HasBit(disabled, i)));
203 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
205 /* Append custom currency at the end */
206 list.emplace_back(new DropDownListItem(-1, false)); // separator line
207 list.emplace_back(new DropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
208 break;
211 case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
212 *selected_index = _settings_client.gui.autosave;
213 const StringID *items = _autosave_dropdown;
214 for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
215 list.emplace_back(new DropDownListStringItem(*items, i, false));
217 break;
220 case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
221 for (uint i = 0; i < _languages.size(); i++) {
222 bool hide_language = IsReleasedVersion() && !_languages[i].IsReasonablyFinished();
223 if (hide_language) continue;
224 bool hide_percentage = IsReleasedVersion() || _languages[i].missing < _settings_client.gui.missing_strings_threshold;
225 auto item = new DropDownListParamStringItem(hide_percentage ? STR_JUST_RAW_STRING : STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE, i, false);
226 if (&_languages[i] == _current_language) {
227 *selected_index = i;
228 item->SetParamStr(0, _languages[i].own_name);
229 } else {
230 /* Especially with sprite-fonts, not all localized
231 * names can be rendered. So instead, we use the
232 * international names for anything but the current
233 * selected language. This avoids showing a few ????
234 * entries in the dropdown list. */
235 item->SetParamStr(0, _languages[i].name);
237 item->SetParam(1, (LANGUAGE_TOTAL_STRINGS - _languages[i].missing) * 100 / LANGUAGE_TOTAL_STRINGS);
238 list.emplace_back(item);
240 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
241 break;
244 case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
245 if (_resolutions.empty()) break;
247 *selected_index = GetCurrentResolutionIndex();
248 for (uint i = 0; i < _resolutions.size(); i++) {
249 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_RESOLUTION_ITEM, i, false);
250 item->SetParam(0, _resolutions[i].width);
251 item->SetParam(1, _resolutions[i].height);
252 list.emplace_back(item);
254 break;
256 case WID_GO_REFRESH_RATE_DROPDOWN: // Setup refresh rate dropdown
257 for (auto it = _refresh_rates.begin(); it != _refresh_rates.end(); it++) {
258 auto i = std::distance(_refresh_rates.begin(), it);
259 if (*it == _settings_client.gui.refresh_rate) *selected_index = i;
260 auto item = new DropDownListParamStringItem(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, i, false);
261 item->SetParam(0, *it);
262 list.emplace_back(item);
264 break;
266 case WID_GO_GUI_ZOOM_DROPDOWN: {
267 *selected_index = _gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom + 1 : 0;
268 const StringID *items = _gui_zoom_dropdown;
269 for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
270 list.emplace_back(new DropDownListStringItem(*items, i, i != 0 && _settings_client.gui.zoom_min > ZOOM_LVL_OUT_4X - i + 1));
272 break;
275 case WID_GO_FONT_ZOOM_DROPDOWN: {
276 *selected_index = _font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom + 1 : 0;
277 const StringID *items = _font_zoom_dropdown;
278 for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
279 list.emplace_back(new DropDownListStringItem(*items, i, false));
281 break;
284 case WID_GO_BASE_GRF_DROPDOWN:
285 list = BuildSetDropDownList<BaseGraphics>(selected_index, (_game_mode == GM_MENU));
286 break;
288 case WID_GO_BASE_SFX_DROPDOWN:
289 list = BuildSetDropDownList<BaseSounds>(selected_index, (_game_mode == GM_MENU));
290 break;
292 case WID_GO_BASE_MUSIC_DROPDOWN:
293 list = BuildMusicSetDropDownList(selected_index);
294 break;
297 return list;
300 void SetStringParameters(int widget) const override
302 switch (widget) {
303 case WID_GO_CURRENCY_DROPDOWN: SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
304 case WID_GO_AUTOSAVE_DROPDOWN: SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
305 case WID_GO_LANG_DROPDOWN: SetDParamStr(0, _current_language->own_name); break;
306 case WID_GO_GUI_ZOOM_DROPDOWN: SetDParam(0, _gui_zoom_dropdown[_gui_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _gui_zoom_cfg + 1 : 0]); break;
307 case WID_GO_FONT_ZOOM_DROPDOWN: SetDParam(0, _font_zoom_dropdown[_font_zoom_cfg != ZOOM_LVL_CFG_AUTO ? ZOOM_LVL_OUT_4X - _font_zoom_cfg + 1 : 0]); break;
308 case WID_GO_BASE_GRF_DROPDOWN: SetDParamStr(0, BaseGraphics::GetUsedSet()->name); break;
309 case WID_GO_BASE_GRF_STATUS: SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
310 case WID_GO_BASE_SFX_DROPDOWN: SetDParamStr(0, BaseSounds::GetUsedSet()->name); break;
311 case WID_GO_BASE_MUSIC_DROPDOWN: SetDParamStr(0, BaseMusic::GetUsedSet()->name); break;
312 case WID_GO_BASE_MUSIC_STATUS: SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
313 case WID_GO_REFRESH_RATE_DROPDOWN: SetDParam(0, _settings_client.gui.refresh_rate); break;
314 case WID_GO_RESOLUTION_DROPDOWN: {
315 auto current_resolution = GetCurrentResolutionIndex();
317 if (current_resolution == _resolutions.size()) {
318 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER);
319 } else {
320 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM);
321 SetDParam(1, _resolutions[current_resolution].width);
322 SetDParam(2, _resolutions[current_resolution].height);
324 break;
329 void DrawWidget(const Rect &r, int widget) const override
331 switch (widget) {
332 case WID_GO_BASE_GRF_DESCRIPTION:
333 SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
334 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
335 break;
337 case WID_GO_BASE_SFX_DESCRIPTION:
338 SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
339 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
340 break;
342 case WID_GO_BASE_MUSIC_DESCRIPTION:
343 SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
344 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
345 break;
347 case WID_GO_BASE_SFX_VOLUME:
348 DrawVolumeSliderWidget(r, _settings_client.music.effect_vol);
349 break;
351 case WID_GO_BASE_MUSIC_VOLUME:
352 DrawVolumeSliderWidget(r, _settings_client.music.music_vol);
353 break;
357 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
359 switch (widget) {
360 case WID_GO_BASE_GRF_DESCRIPTION:
361 /* Find the biggest description for the default size. */
362 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
363 SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
364 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
366 break;
368 case WID_GO_BASE_GRF_STATUS:
369 /* Find the biggest description for the default size. */
370 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
371 uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
372 if (invalid_files == 0) continue;
374 SetDParam(0, invalid_files);
375 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
377 break;
379 case WID_GO_BASE_SFX_DESCRIPTION:
380 /* Find the biggest description for the default size. */
381 for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
382 SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
383 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
385 break;
387 case WID_GO_BASE_MUSIC_DESCRIPTION:
388 /* Find the biggest description for the default size. */
389 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
390 SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
391 size->height = std::max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
393 break;
395 case WID_GO_BASE_MUSIC_STATUS:
396 /* Find the biggest description for the default size. */
397 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
398 uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
399 if (invalid_files == 0) continue;
401 SetDParam(0, invalid_files);
402 *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
404 break;
406 default: {
407 int selected;
408 DropDownList list = this->BuildDropDownList(widget, &selected);
409 if (!list.empty()) {
410 /* Find the biggest item for the default size. */
411 for (const auto &ddli : list) {
412 Dimension string_dim;
413 int width = ddli->Width();
414 string_dim.width = width + padding.width;
415 string_dim.height = ddli->Height(width) + padding.height;
416 *size = maxdim(*size, string_dim);
423 void OnClick(Point pt, int widget, int click_count) override
425 if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
426 if (BaseGraphics::GetUsedSet() == nullptr) return;
428 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
429 return;
431 if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
432 if (BaseSounds::GetUsedSet() == nullptr) return;
434 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
435 return;
437 if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
438 if (BaseMusic::GetUsedSet() == nullptr) return;
440 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
441 return;
443 switch (widget) {
444 case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
445 /* try to toggle full-screen on/off */
446 if (!ToggleFullScreen(!_fullscreen)) {
447 ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
449 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
450 this->SetWidgetDirty(WID_GO_FULLSCREEN_BUTTON);
451 break;
453 case WID_GO_VIDEO_ACCEL_BUTTON:
454 _video_hw_accel = !_video_hw_accel;
455 ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART, INVALID_STRING_ID, WL_INFO);
456 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
457 this->SetWidgetDirty(WID_GO_VIDEO_ACCEL_BUTTON);
458 #ifndef __APPLE__
459 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
460 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
461 #endif
462 break;
464 case WID_GO_VIDEO_VSYNC_BUTTON:
465 if (!_video_hw_accel) break;
467 _video_vsync = !_video_vsync;
468 VideoDriver::GetInstance()->ToggleVsync(_video_vsync);
470 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
471 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
472 break;
474 case WID_GO_BASE_SFX_VOLUME:
475 case WID_GO_BASE_MUSIC_VOLUME: {
476 byte &vol = (widget == WID_GO_BASE_MUSIC_VOLUME) ? _settings_client.music.music_vol : _settings_client.music.effect_vol;
477 if (ClickVolumeSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, vol)) {
478 if (widget == WID_GO_BASE_MUSIC_VOLUME) MusicDriver::GetInstance()->SetVolume(vol);
479 this->SetWidgetDirty(widget);
480 SetWindowClassesDirty(WC_MUSIC_WINDOW);
483 if (click_count > 0) this->mouse_capture_widget = widget;
484 break;
487 default: {
488 int selected;
489 DropDownList list = this->BuildDropDownList(widget, &selected);
490 if (!list.empty()) {
491 ShowDropDownList(this, std::move(list), selected, widget);
492 } else {
493 if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
495 break;
501 * Set the base media set.
502 * @param index the index of the media set
503 * @tparam T class of media set
505 template <class T>
506 void SetMediaSet(int index)
508 if (_game_mode == GM_MENU) {
509 auto name = T::GetSet(index)->name;
511 T::ini_set = name;
513 T::SetSet(name);
514 this->reload = true;
515 this->InvalidateData();
519 void OnDropdownSelect(int widget, int index) override
521 switch (widget) {
522 case WID_GO_CURRENCY_DROPDOWN: // Currency
523 if (index == CURRENCY_CUSTOM) ShowCustCurrency();
524 this->opt->locale.currency = index;
525 ReInitAllWindows(false);
526 break;
528 case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
529 _settings_client.gui.autosave = index;
530 this->SetDirty();
531 break;
533 case WID_GO_LANG_DROPDOWN: // Change interface language
534 ReadLanguagePack(&_languages[index]);
535 CloseWindowByClass(WC_QUERY_STRING);
536 CheckForMissingGlyphs();
537 ClearAllCachedNames();
538 UpdateAllVirtCoords();
539 CheckBlitter();
540 ReInitAllWindows(false);
541 break;
543 case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
544 if ((uint)index < _resolutions.size() && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
545 this->SetDirty();
547 break;
549 case WID_GO_REFRESH_RATE_DROPDOWN: {
550 _settings_client.gui.refresh_rate = *std::next(_refresh_rates.begin(), index);
551 if (_settings_client.gui.refresh_rate > 60) {
552 /* Show warning to the user that this refresh rate might not be suitable on
553 * larger maps with many NewGRFs and vehicles. */
554 ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING, INVALID_STRING_ID, WL_INFO);
556 break;
559 case WID_GO_GUI_ZOOM_DROPDOWN: {
560 int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
561 if (new_zoom != _gui_zoom_cfg) {
562 GfxClearSpriteCache();
563 _gui_zoom_cfg = new_zoom;
564 UpdateGUIZoom();
565 UpdateCursorSize();
566 UpdateAllVirtCoords();
567 FixTitleGameZoom();
568 ReInitAllWindows(true);
570 break;
573 case WID_GO_FONT_ZOOM_DROPDOWN: {
574 int8 new_zoom = index > 0 ? ZOOM_LVL_OUT_4X - index + 1 : ZOOM_LVL_CFG_AUTO;
575 if (new_zoom != _font_zoom_cfg) {
576 GfxClearSpriteCache();
577 _font_zoom_cfg = new_zoom;
578 UpdateGUIZoom();
579 ClearFontCache();
580 LoadStringWidthTable();
581 UpdateAllVirtCoords();
582 ReInitAllWindows(true);
584 break;
587 case WID_GO_BASE_GRF_DROPDOWN:
588 this->SetMediaSet<BaseGraphics>(index);
589 break;
591 case WID_GO_BASE_SFX_DROPDOWN:
592 this->SetMediaSet<BaseSounds>(index);
593 break;
595 case WID_GO_BASE_MUSIC_DROPDOWN:
596 ChangeMusicSet(index);
597 break;
602 * Some data on this window has become invalid.
603 * @param data Information about the changed data. @see GameOptionsInvalidationData
604 * @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.
606 void OnInvalidateData(int data = 0, bool gui_scope = true) override
608 if (!gui_scope) return;
609 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
610 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
612 #ifndef __APPLE__
613 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
614 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
615 #endif
617 bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
618 this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
620 for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
621 this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->GetTextfile(tft) == nullptr);
622 this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->GetTextfile(tft) == nullptr);
623 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->GetTextfile(tft) == nullptr);
626 missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
627 this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
631 static const NWidgetPart _nested_game_options_widgets[] = {
632 NWidget(NWID_HORIZONTAL),
633 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
634 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
635 EndContainer(),
636 NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
637 NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
638 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
639 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
640 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),
641 EndContainer(),
642 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_ZOOM_FRAME, STR_NULL),
643 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),
644 EndContainer(),
645 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
646 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),
647 EndContainer(),
648 EndContainer(),
650 NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
651 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
652 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),
653 EndContainer(),
654 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_FONT_ZOOM, STR_NULL),
655 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),
656 EndContainer(),
657 EndContainer(),
658 EndContainer(),
660 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GRAPHICS, STR_NULL), SetPadding(0, 10, 0, 10),
661 NWidget(NWID_HORIZONTAL),
662 NWidget(NWID_VERTICAL), SetPIP(0, 2, 0),
663 NWidget(NWID_HORIZONTAL),
664 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12),SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
665 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
666 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(200, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP),
667 EndContainer(),
668 NWidget(NWID_HORIZONTAL),
669 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE, STR_NULL),
670 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
671 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_REFRESH_RATE_DROPDOWN), SetMinimalSize(200, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, STR_GAME_OPTIONS_REFRESH_RATE_TOOLTIP),
672 EndContainer(),
673 NWidget(NWID_HORIZONTAL),
674 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
675 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
676 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
677 EndContainer(),
678 NWidget(NWID_HORIZONTAL),
679 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION, STR_NULL),
680 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
681 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_ACCEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP),
682 EndContainer(),
683 #ifndef __APPLE__
684 NWidget(NWID_HORIZONTAL),
685 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_VIDEO_VSYNC, STR_NULL),
686 NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
687 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_VSYNC_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP),
688 EndContainer(),
689 #endif
690 EndContainer(),
691 EndContainer(),
692 EndContainer(),
694 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
695 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
696 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
697 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
698 EndContainer(),
699 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),
700 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
701 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),
702 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),
703 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),
704 EndContainer(),
705 EndContainer(),
707 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
708 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
709 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
710 NWidget(NWID_SPACER), SetMinimalSize(150, 12), SetFill(1, 0),
711 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_SFX_VOLUME), SetMinimalSize(67, 12), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
712 EndContainer(),
713 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),
714 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
715 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),
716 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),
717 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),
718 EndContainer(),
719 EndContainer(),
721 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
722 NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 7),
723 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
724 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
725 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_MUSIC_VOLUME), SetMinimalSize(67, 12), SetFill(0, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
726 EndContainer(),
727 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),
728 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
729 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),
730 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),
731 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),
732 EndContainer(),
733 EndContainer(),
734 EndContainer(),
737 static WindowDesc _game_options_desc(
738 WDP_CENTER, "settings_game", 0, 0,
739 WC_GAME_OPTIONS, WC_NONE,
741 _nested_game_options_widgets, lengthof(_nested_game_options_widgets)
744 /** Open the game options window. */
745 void ShowGameOptions()
747 CloseWindowByClass(WC_GAME_OPTIONS);
748 new GameOptionsWindow(&_game_options_desc);
751 static int SETTING_HEIGHT = 11; ///< Height of a single setting in the tree view in pixels
752 static const int LEVEL_WIDTH = 15; ///< Indenting width of a sub-page in pixels
755 * Flags for #SettingEntry
756 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
758 enum SettingEntryFlags {
759 SEF_LEFT_DEPRESSED = 0x01, ///< Of a numeric setting entry, the left button is depressed
760 SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
761 SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
763 SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
764 SEF_FILTERED = 0x08, ///< Entry is hidden by the string filter
767 /** How the list of advanced settings is filtered. */
768 enum RestrictionMode {
769 RM_BASIC, ///< Display settings associated to the "basic" list.
770 RM_ADVANCED, ///< Display settings associated to the "advanced" list.
771 RM_ALL, ///< List all settings regardless of the default/newgame/... values.
772 RM_CHANGED_AGAINST_DEFAULT, ///< Show only settings which are different compared to default values.
773 RM_CHANGED_AGAINST_NEW, ///< Show only settings which are different compared to the user's new game setting values.
774 RM_END, ///< End for iteration.
776 DECLARE_POSTFIX_INCREMENT(RestrictionMode)
778 /** Filter for settings list. */
779 struct SettingFilter {
780 StringFilter string; ///< Filter string.
781 RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
782 bool type_hides; ///< Whether the type hides filtered strings.
783 RestrictionMode mode; ///< Filter based on category.
784 SettingType type; ///< Filter based on type.
787 /** Data structure describing a single setting in a tab */
788 struct BaseSettingEntry {
789 byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
790 byte level; ///< Nesting level of this setting entry
792 BaseSettingEntry() : flags(0), level(0) {}
793 virtual ~BaseSettingEntry() {}
795 virtual void Init(byte level = 0);
796 virtual void FoldAll() {}
797 virtual void UnFoldAll() {}
798 virtual void ResetAll() = 0;
801 * Set whether this is the last visible entry of the parent node.
802 * @param last_field Value to set
804 void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
806 virtual uint Length() const = 0;
807 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
808 virtual bool IsVisible(const BaseSettingEntry *item) const;
809 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
810 virtual uint GetMaxHelpHeight(int maxw) { return 0; }
813 * Check whether an entry is hidden due to filters
814 * @return true if hidden.
816 bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
818 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
820 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;
822 protected:
823 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
826 /** Standard setting */
827 struct SettingEntry : BaseSettingEntry {
828 const char *name; ///< Name of the setting
829 const IntSettingDesc *setting; ///< Setting description of the setting
831 SettingEntry(const char *name);
833 virtual void Init(byte level = 0);
834 virtual void ResetAll();
835 virtual uint Length() const;
836 virtual uint GetMaxHelpHeight(int maxw);
837 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
839 void SetButtons(byte new_val);
842 * Get the help text of a single setting.
843 * @return The requested help text.
845 inline StringID GetHelpText() const
847 return this->setting->str_help;
850 void SetValueDParams(uint first_param, int32 value) const;
852 protected:
853 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
855 private:
856 bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
859 /** Containers for BaseSettingEntry */
860 struct SettingsContainer {
861 typedef std::vector<BaseSettingEntry*> EntryVector;
862 EntryVector entries; ///< Settings on this page
864 template<typename T>
865 T *Add(T *item)
867 this->entries.push_back(item);
868 return item;
871 void Init(byte level = 0);
872 void ResetAll();
873 void FoldAll();
874 void UnFoldAll();
876 uint Length() const;
877 void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
878 bool IsVisible(const BaseSettingEntry *item) const;
879 BaseSettingEntry *FindEntry(uint row, uint *cur_row);
880 uint GetMaxHelpHeight(int maxw);
882 bool UpdateFilterState(SettingFilter &filter, bool force_visible);
884 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;
887 /** Data structure describing one page of settings in the settings window. */
888 struct SettingsPage : BaseSettingEntry, SettingsContainer {
889 StringID title; ///< Title of the sub-page
890 bool folded; ///< Sub-page is folded (not visible except for its title)
892 SettingsPage(StringID title);
894 virtual void Init(byte level = 0);
895 virtual void ResetAll();
896 virtual void FoldAll();
897 virtual void UnFoldAll();
899 virtual uint Length() const;
900 virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
901 virtual bool IsVisible(const BaseSettingEntry *item) const;
902 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
903 virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
905 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
907 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;
909 protected:
910 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
913 /* == BaseSettingEntry methods == */
916 * Initialization of a setting entry
917 * @param level Page nesting level of this entry
919 void BaseSettingEntry::Init(byte level)
921 this->level = level;
925 * Check whether an entry is visible and not folded or filtered away.
926 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
927 * @param item Entry to search for.
928 * @return true if entry is visible.
930 bool BaseSettingEntry::IsVisible(const BaseSettingEntry *item) const
932 if (this->IsFiltered()) return false;
933 return this == item;
937 * Find setting entry at row \a row_num
938 * @param row_num Index of entry to return
939 * @param cur_row Current row number
940 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
942 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
944 if (this->IsFiltered()) return nullptr;
945 if (row_num == *cur_row) return this;
946 (*cur_row)++;
947 return nullptr;
951 * Draw a row in the settings panel.
953 * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
954 * 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.
955 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
957 * The \a parent_last parameter ensures that the vertical lines at the left are
958 * only drawn when another entry follows, that it prevents output like
959 * \verbatim
960 * |-- setting
961 * |-- (-) - Title
962 * | |-- setting
963 * | |-- setting
964 * \endverbatim
965 * The left-most vertical line is not wanted. It is prevented by setting the
966 * appropriate bit in the \a parent_last parameter.
968 * @param settings_ptr Pointer to current values of all settings
969 * @param left Left-most position in window/panel to start drawing \a first_row
970 * @param right Right-most x position to draw strings at.
971 * @param y Upper-most position in window/panel to start drawing \a first_row
972 * @param first_row First row number to draw
973 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
974 * @param selected Selected entry by the user.
975 * @param cur_row Current row number (internal variable)
976 * @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)
977 * @return Row number of the next row to draw
979 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
981 if (this->IsFiltered()) return cur_row;
982 if (cur_row >= max_row) return cur_row;
984 bool rtl = _current_text_dir == TD_RTL;
985 int offset = rtl ? -4 : 4;
986 int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
988 int x = rtl ? right : left;
989 if (cur_row >= first_row) {
990 int colour = _colour_gradient[COLOUR_ORANGE][4];
991 y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
993 /* Draw vertical for parent nesting levels */
994 for (uint lvl = 0; lvl < this->level; lvl++) {
995 if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
996 x += level_width;
998 /* draw own |- prefix */
999 int halfway_y = y + SETTING_HEIGHT / 2;
1000 int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
1001 GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
1002 /* Small horizontal line from the last vertical line */
1003 GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
1004 x += level_width;
1006 this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
1008 cur_row++;
1010 return cur_row;
1013 /* == SettingEntry methods == */
1016 * Constructor for a single setting in the 'advanced settings' window
1017 * @param name Name of the setting in the setting table
1019 SettingEntry::SettingEntry(const char *name)
1021 this->name = name;
1022 this->setting = nullptr;
1026 * Initialization of a setting entry
1027 * @param level Page nesting level of this entry
1029 void SettingEntry::Init(byte level)
1031 BaseSettingEntry::Init(level);
1032 this->setting = GetSettingFromName(this->name)->AsIntSetting();
1035 /* Sets the given setting entry to its default value */
1036 void SettingEntry::ResetAll()
1038 SetSettingValue(this->setting, this->setting->def);
1042 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
1043 * @param new_val New value for the button flags
1044 * @see SettingEntryFlags
1046 void SettingEntry::SetButtons(byte new_val)
1048 assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
1049 this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
1052 /** Return number of rows needed to display the (filtered) entry */
1053 uint SettingEntry::Length() const
1055 return this->IsFiltered() ? 0 : 1;
1059 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
1060 * @param maxw Maximal width of a line help text.
1061 * @return Biggest height needed to display any help text of this node (and its descendants).
1063 uint SettingEntry::GetMaxHelpHeight(int maxw)
1065 return GetStringHeight(this->GetHelpText(), maxw);
1069 * Checks whether an entry shall be made visible based on the restriction mode.
1070 * @param mode The current status of the restriction drop down box.
1071 * @return true if the entry shall be visible.
1073 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode) const
1075 /* There shall not be any restriction, i.e. all settings shall be visible. */
1076 if (mode == RM_ALL) return true;
1078 const IntSettingDesc *sd = this->setting;
1080 if (mode == RM_BASIC) return (this->setting->cat & SC_BASIC_LIST) != 0;
1081 if (mode == RM_ADVANCED) return (this->setting->cat & SC_ADVANCED_LIST) != 0;
1083 /* Read the current value. */
1084 const void *object = ResolveObject(&GetGameSettings(), sd);
1085 int64 current_value = sd->Read(object);
1086 int64 filter_value;
1088 if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1089 /* This entry shall only be visible, if the value deviates from its default value. */
1091 /* Read the default value. */
1092 filter_value = sd->def;
1093 } else {
1094 assert(mode == RM_CHANGED_AGAINST_NEW);
1095 /* This entry shall only be visible, if the value deviates from
1096 * its value is used when starting a new game. */
1098 /* Make sure we're not comparing the new game settings against itself. */
1099 assert(&GetGameSettings() != &_settings_newgame);
1101 /* Read the new game's value. */
1102 filter_value = sd->Read(ResolveObject(&_settings_newgame, sd));
1105 return current_value != filter_value;
1109 * Update the filter state.
1110 * @param filter Filter
1111 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1112 * @return true if item remains visible
1114 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1116 CLRBITS(this->flags, SEF_FILTERED);
1118 bool visible = true;
1120 const IntSettingDesc *sd = this->setting;
1121 if (!force_visible && !filter.string.IsEmpty()) {
1122 /* Process the search text filter for this item. */
1123 filter.string.ResetState();
1125 SetDParam(0, STR_EMPTY);
1126 filter.string.AddLine(sd->str);
1127 filter.string.AddLine(this->GetHelpText());
1129 visible = filter.string.GetState();
1132 if (visible) {
1133 if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1134 filter.type_hides = true;
1135 visible = false;
1137 if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1138 while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1139 visible = false;
1143 if (!visible) SETBITS(this->flags, SEF_FILTERED);
1144 return visible;
1147 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd)
1149 if ((sd->flags & SF_PER_COMPANY) != 0) {
1150 if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1151 return &Company::Get(_local_company)->settings;
1153 return &_settings_client.company;
1155 return settings_ptr;
1159 * Set the DParams for drawing the value of a setting.
1160 * @param first_param First DParam to use
1161 * @param value Setting value to set params for.
1163 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1165 if (this->setting->IsBoolSetting()) {
1166 SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1167 } else {
1168 if ((this->setting->flags & SF_GUI_DROPDOWN) != 0) {
1169 SetDParam(first_param++, this->setting->str_val - this->setting->min + value);
1170 } else if ((this->setting->flags & SF_GUI_NEGATIVE_IS_SPECIAL) != 0) {
1171 SetDParam(first_param++, this->setting->str_val + ((value >= 0) ? 1 : 0));
1172 value = abs(value);
1173 } else {
1174 SetDParam(first_param++, this->setting->str_val + ((value == 0 && (this->setting->flags & SF_GUI_0_IS_SPECIAL) != 0) ? 1 : 0));
1176 SetDParam(first_param++, value);
1181 * Function to draw setting value (button + text + current value)
1182 * @param settings_ptr Pointer to current values of all settings
1183 * @param left Left-most position in window/panel to start drawing
1184 * @param right Right-most position in window/panel to draw
1185 * @param y Upper-most position in window/panel to start drawing
1186 * @param highlight Highlight entry.
1188 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1190 const IntSettingDesc *sd = this->setting;
1191 int state = this->flags & SEF_BUTTONS_MASK;
1193 bool rtl = _current_text_dir == TD_RTL;
1194 uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1195 uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + 5);
1196 uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + 5 : 0);
1197 uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1199 /* We do not allow changes of some items when we are a client in a networkgame */
1200 bool editable = sd->IsEditable();
1202 SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1203 int32 value = sd->Read(ResolveObject(settings_ptr, sd));
1204 if (sd->IsBoolSetting()) {
1205 /* Draw checkbox for boolean-value either on/off */
1206 DrawBoolButton(buttons_left, button_y, value != 0, editable);
1207 } else if ((sd->flags & SF_GUI_DROPDOWN) != 0) {
1208 /* Draw [v] button for settings of an enum-type */
1209 DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1210 } else {
1211 /* Draw [<][>] boxes for settings of an integer-type */
1212 DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1213 editable && value != (sd->flags & SF_GUI_0_IS_SPECIAL ? 0 : sd->min), editable && (uint32)value != sd->max);
1215 this->SetValueDParams(1, value);
1216 DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sd->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1219 /* == SettingsContainer methods == */
1222 * Initialization of an entire setting page
1223 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1225 void SettingsContainer::Init(byte level)
1227 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1228 (*it)->Init(level);
1232 /** Resets all settings to their default values */
1233 void SettingsContainer::ResetAll()
1235 for (auto settings_entry : this->entries) {
1236 settings_entry->ResetAll();
1240 /** Recursively close all folds of sub-pages */
1241 void SettingsContainer::FoldAll()
1243 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1244 (*it)->FoldAll();
1248 /** Recursively open all folds of sub-pages */
1249 void SettingsContainer::UnFoldAll()
1251 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1252 (*it)->UnFoldAll();
1257 * Recursively accumulate the folding state of the tree.
1258 * @param[in,out] all_folded Set to false, if one entry is not folded.
1259 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1261 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1263 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1264 (*it)->GetFoldingState(all_folded, all_unfolded);
1269 * Update the filter state.
1270 * @param filter Filter
1271 * @param force_visible Whether to force all items visible, no matter what
1272 * @return true if item remains visible
1274 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1276 bool visible = false;
1277 bool first_visible = true;
1278 for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1279 visible |= (*it)->UpdateFilterState(filter, force_visible);
1280 (*it)->SetLastField(first_visible);
1281 if (visible && first_visible) first_visible = false;
1283 return visible;
1288 * Check whether an entry is visible and not folded or filtered away.
1289 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1290 * @param item Entry to search for.
1291 * @return true if entry is visible.
1293 bool SettingsContainer::IsVisible(const BaseSettingEntry *item) const
1295 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1296 if ((*it)->IsVisible(item)) return true;
1298 return false;
1301 /** Return number of rows needed to display the whole page */
1302 uint SettingsContainer::Length() const
1304 uint length = 0;
1305 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1306 length += (*it)->Length();
1308 return length;
1312 * Find the setting entry at row number \a row_num
1313 * @param row_num Index of entry to return
1314 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1315 * @return The requested setting entry or \c nullptr if it does not exist
1317 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1319 BaseSettingEntry *pe = nullptr;
1320 for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1321 pe = (*it)->FindEntry(row_num, cur_row);
1322 if (pe != nullptr) {
1323 break;
1326 return pe;
1330 * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1331 * @param maxw Maximal width of a line help text.
1332 * @return Biggest height needed to display any help text of this (sub-)tree.
1334 uint SettingsContainer::GetMaxHelpHeight(int maxw)
1336 uint biggest = 0;
1337 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1338 biggest = std::max(biggest, (*it)->GetMaxHelpHeight(maxw));
1340 return biggest;
1345 * Draw a row in the settings panel.
1347 * @param settings_ptr Pointer to current values of all settings
1348 * @param left Left-most position in window/panel to start drawing \a first_row
1349 * @param right Right-most x position to draw strings at.
1350 * @param y Upper-most position in window/panel to start drawing \a first_row
1351 * @param first_row First row number to draw
1352 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1353 * @param selected Selected entry by the user.
1354 * @param cur_row Current row number (internal variable)
1355 * @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)
1356 * @return Row number of the next row to draw
1358 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
1360 for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1361 cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1362 if (cur_row >= max_row) {
1363 break;
1366 return cur_row;
1369 /* == SettingsPage methods == */
1372 * Constructor for a sub-page in the 'advanced settings' window
1373 * @param title Title of the sub-page
1375 SettingsPage::SettingsPage(StringID title)
1377 this->title = title;
1378 this->folded = true;
1382 * Initialization of an entire setting page
1383 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1385 void SettingsPage::Init(byte level)
1387 BaseSettingEntry::Init(level);
1388 SettingsContainer::Init(level + 1);
1391 /** Resets all settings to their default values */
1392 void SettingsPage::ResetAll()
1394 for (auto settings_entry : this->entries) {
1395 settings_entry->ResetAll();
1399 /** Recursively close all (filtered) folds of sub-pages */
1400 void SettingsPage::FoldAll()
1402 if (this->IsFiltered()) return;
1403 this->folded = true;
1405 SettingsContainer::FoldAll();
1408 /** Recursively open all (filtered) folds of sub-pages */
1409 void SettingsPage::UnFoldAll()
1411 if (this->IsFiltered()) return;
1412 this->folded = false;
1414 SettingsContainer::UnFoldAll();
1418 * Recursively accumulate the folding state of the (filtered) tree.
1419 * @param[in,out] all_folded Set to false, if one entry is not folded.
1420 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1422 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1424 if (this->IsFiltered()) return;
1426 if (this->folded) {
1427 all_unfolded = false;
1428 } else {
1429 all_folded = false;
1432 SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1436 * Update the filter state.
1437 * @param filter Filter
1438 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1439 * @return true if item remains visible
1441 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1443 if (!force_visible && !filter.string.IsEmpty()) {
1444 filter.string.ResetState();
1445 filter.string.AddLine(this->title);
1446 force_visible = filter.string.GetState();
1449 bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1450 if (visible) {
1451 CLRBITS(this->flags, SEF_FILTERED);
1452 } else {
1453 SETBITS(this->flags, SEF_FILTERED);
1455 return visible;
1459 * Check whether an entry is visible and not folded or filtered away.
1460 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1461 * @param item Entry to search for.
1462 * @return true if entry is visible.
1464 bool SettingsPage::IsVisible(const BaseSettingEntry *item) const
1466 if (this->IsFiltered()) return false;
1467 if (this == item) return true;
1468 if (this->folded) return false;
1470 return SettingsContainer::IsVisible(item);
1473 /** Return number of rows needed to display the (filtered) entry */
1474 uint SettingsPage::Length() const
1476 if (this->IsFiltered()) return 0;
1477 if (this->folded) return 1; // Only displaying the title
1479 return 1 + SettingsContainer::Length();
1483 * Find setting entry at row \a row_num
1484 * @param row_num Index of entry to return
1485 * @param cur_row Current row number
1486 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1488 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1490 if (this->IsFiltered()) return nullptr;
1491 if (row_num == *cur_row) return this;
1492 (*cur_row)++;
1493 if (this->folded) return nullptr;
1495 return SettingsContainer::FindEntry(row_num, cur_row);
1499 * Draw a row in the settings panel.
1501 * @param settings_ptr Pointer to current values of all settings
1502 * @param left Left-most position in window/panel to start drawing \a first_row
1503 * @param right Right-most x position to draw strings at.
1504 * @param y Upper-most position in window/panel to start drawing \a first_row
1505 * @param first_row First row number to draw
1506 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1507 * @param selected Selected entry by the user.
1508 * @param cur_row Current row number (internal variable)
1509 * @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)
1510 * @return Row number of the next row to draw
1512 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
1514 if (this->IsFiltered()) return cur_row;
1515 if (cur_row >= max_row) return cur_row;
1517 cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1519 if (!this->folded) {
1520 if (this->flags & SEF_LAST_FIELD) {
1521 assert(this->level < 8 * sizeof(parent_last));
1522 SetBit(parent_last, this->level); // Add own last-field state
1525 cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1528 return cur_row;
1532 * Function to draw setting value (button + text + current value)
1533 * @param settings_ptr Pointer to current values of all settings
1534 * @param left Left-most position in window/panel to start drawing
1535 * @param right Right-most position in window/panel to draw
1536 * @param y Upper-most position in window/panel to start drawing
1537 * @param highlight Highlight entry.
1539 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1541 bool rtl = _current_text_dir == TD_RTL;
1542 DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1543 DrawString(rtl ? left : left + _circle_size.width + 2, rtl ? right - _circle_size.width - 2 : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1546 /** Construct settings tree */
1547 static SettingsContainer &GetSettingsTree()
1549 static SettingsContainer *main = nullptr;
1551 if (main == nullptr)
1553 /* Build up the dynamic settings-array only once per OpenTTD session */
1554 main = new SettingsContainer();
1556 SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1558 localisation->Add(new SettingEntry("locale.units_velocity"));
1559 localisation->Add(new SettingEntry("locale.units_power"));
1560 localisation->Add(new SettingEntry("locale.units_weight"));
1561 localisation->Add(new SettingEntry("locale.units_volume"));
1562 localisation->Add(new SettingEntry("locale.units_force"));
1563 localisation->Add(new SettingEntry("locale.units_height"));
1564 localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1567 SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1569 graphics->Add(new SettingEntry("gui.zoom_min"));
1570 graphics->Add(new SettingEntry("gui.zoom_max"));
1571 graphics->Add(new SettingEntry("gui.sprite_zoom_min"));
1572 graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1573 graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1576 SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1578 sound->Add(new SettingEntry("sound.click_beep"));
1579 sound->Add(new SettingEntry("sound.confirm"));
1580 sound->Add(new SettingEntry("sound.news_ticker"));
1581 sound->Add(new SettingEntry("sound.news_full"));
1582 sound->Add(new SettingEntry("sound.new_year"));
1583 sound->Add(new SettingEntry("sound.disaster"));
1584 sound->Add(new SettingEntry("sound.vehicle"));
1585 sound->Add(new SettingEntry("sound.ambient"));
1588 SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1590 SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1592 general->Add(new SettingEntry("gui.osk_activation"));
1593 general->Add(new SettingEntry("gui.hover_delay_ms"));
1594 general->Add(new SettingEntry("gui.errmsg_duration"));
1595 general->Add(new SettingEntry("gui.window_snap_radius"));
1596 general->Add(new SettingEntry("gui.window_soft_limit"));
1597 general->Add(new SettingEntry("gui.right_mouse_wnd_close"));
1600 SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1602 viewports->Add(new SettingEntry("gui.auto_scrolling"));
1603 viewports->Add(new SettingEntry("gui.scroll_mode"));
1604 viewports->Add(new SettingEntry("gui.smooth_scroll"));
1605 /* While the horizontal scrollwheel scrolling is written as general code, only
1606 * the cocoa (OSX) driver generates input for it.
1607 * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1608 viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1609 viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1610 #ifdef __APPLE__
1611 /* We might need to emulate a right mouse button on mac */
1612 viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1613 #endif
1614 viewports->Add(new SettingEntry("gui.population_in_label"));
1615 viewports->Add(new SettingEntry("gui.liveries"));
1616 viewports->Add(new SettingEntry("construction.train_signal_side"));
1617 viewports->Add(new SettingEntry("gui.measure_tooltip"));
1618 viewports->Add(new SettingEntry("gui.loading_indicators"));
1619 viewports->Add(new SettingEntry("gui.show_track_reservation"));
1622 SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1624 construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1625 construction->Add(new SettingEntry("gui.enable_signal_gui"));
1626 construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1627 construction->Add(new SettingEntry("gui.quick_goto"));
1628 construction->Add(new SettingEntry("gui.default_rail_type"));
1631 interface->Add(new SettingEntry("gui.fast_forward_speed_limit"));
1632 interface->Add(new SettingEntry("gui.autosave"));
1633 interface->Add(new SettingEntry("gui.toolbar_pos"));
1634 interface->Add(new SettingEntry("gui.statusbar_pos"));
1635 interface->Add(new SettingEntry("gui.prefer_teamchat"));
1636 interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1637 interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1638 interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1639 interface->Add(new SettingEntry("gui.expenses_layout"));
1640 interface->Add(new SettingEntry("gui.show_newgrf_name"));
1643 SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1645 advisors->Add(new SettingEntry("gui.coloured_news_year"));
1646 advisors->Add(new SettingEntry("news_display.general"));
1647 advisors->Add(new SettingEntry("news_display.new_vehicles"));
1648 advisors->Add(new SettingEntry("news_display.accident"));
1649 advisors->Add(new SettingEntry("news_display.company_info"));
1650 advisors->Add(new SettingEntry("news_display.acceptance"));
1651 advisors->Add(new SettingEntry("news_display.arrival_player"));
1652 advisors->Add(new SettingEntry("news_display.arrival_other"));
1653 advisors->Add(new SettingEntry("news_display.advice"));
1654 advisors->Add(new SettingEntry("gui.order_review_system"));
1655 advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1656 advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1657 advisors->Add(new SettingEntry("gui.show_finances"));
1658 advisors->Add(new SettingEntry("news_display.economy"));
1659 advisors->Add(new SettingEntry("news_display.subsidies"));
1660 advisors->Add(new SettingEntry("news_display.open"));
1661 advisors->Add(new SettingEntry("news_display.close"));
1662 advisors->Add(new SettingEntry("news_display.production_player"));
1663 advisors->Add(new SettingEntry("news_display.production_other"));
1664 advisors->Add(new SettingEntry("news_display.production_nobody"));
1667 SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1669 company->Add(new SettingEntry("gui.semaphore_build_before"));
1670 company->Add(new SettingEntry("gui.default_signal_type"));
1671 company->Add(new SettingEntry("gui.cycle_signal_types"));
1672 company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1673 company->Add(new SettingEntry("gui.auto_remove_signals"));
1674 company->Add(new SettingEntry("gui.new_nonstop"));
1675 company->Add(new SettingEntry("gui.stop_location"));
1676 company->Add(new SettingEntry("gui.starting_colour"));
1677 company->Add(new SettingEntry("company.engine_renew"));
1678 company->Add(new SettingEntry("company.engine_renew_months"));
1679 company->Add(new SettingEntry("company.engine_renew_money"));
1680 company->Add(new SettingEntry("vehicle.servint_ispercent"));
1681 company->Add(new SettingEntry("vehicle.servint_trains"));
1682 company->Add(new SettingEntry("vehicle.servint_roadveh"));
1683 company->Add(new SettingEntry("vehicle.servint_ships"));
1684 company->Add(new SettingEntry("vehicle.servint_aircraft"));
1687 SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1689 accounting->Add(new SettingEntry("economy.inflation"));
1690 accounting->Add(new SettingEntry("difficulty.initial_interest"));
1691 accounting->Add(new SettingEntry("difficulty.max_loan"));
1692 accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1693 accounting->Add(new SettingEntry("difficulty.subsidy_duration"));
1694 accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1695 accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1696 accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1697 accounting->Add(new SettingEntry("difficulty.construction_cost"));
1700 SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1702 SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1704 physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1705 physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1706 physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1707 physics->Add(new SettingEntry("vehicle.freight_trains"));
1708 physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1709 physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1710 physics->Add(new SettingEntry("vehicle.smoke_amount"));
1711 physics->Add(new SettingEntry("vehicle.plane_speed"));
1714 SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1716 routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1717 routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1718 routing->Add(new SettingEntry("pf.reverse_at_signals"));
1719 routing->Add(new SettingEntry("pf.forbid_90_deg"));
1720 routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1721 routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1724 vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1725 vehicles->Add(new SettingEntry("order.serviceathelipad"));
1728 SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1730 limitations->Add(new SettingEntry("construction.command_pause_level"));
1731 limitations->Add(new SettingEntry("construction.autoslope"));
1732 limitations->Add(new SettingEntry("construction.extra_dynamite"));
1733 limitations->Add(new SettingEntry("construction.map_height_limit"));
1734 limitations->Add(new SettingEntry("construction.max_bridge_length"));
1735 limitations->Add(new SettingEntry("construction.max_bridge_height"));
1736 limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1737 limitations->Add(new SettingEntry("station.never_expire_airports"));
1738 limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1739 limitations->Add(new SettingEntry("vehicle.max_trains"));
1740 limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1741 limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1742 limitations->Add(new SettingEntry("vehicle.max_ships"));
1743 limitations->Add(new SettingEntry("vehicle.max_train_length"));
1744 limitations->Add(new SettingEntry("station.station_spread"));
1745 limitations->Add(new SettingEntry("station.distant_join_stations"));
1746 limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1747 limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1748 limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1751 SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1753 disasters->Add(new SettingEntry("difficulty.disasters"));
1754 disasters->Add(new SettingEntry("difficulty.economy"));
1755 disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1756 disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1759 SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1761 genworld->Add(new SettingEntry("game_creation.landscape"));
1762 genworld->Add(new SettingEntry("game_creation.land_generator"));
1763 genworld->Add(new SettingEntry("difficulty.terrain_type"));
1764 genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1765 genworld->Add(new SettingEntry("game_creation.variety"));
1766 genworld->Add(new SettingEntry("game_creation.snow_coverage"));
1767 genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1768 genworld->Add(new SettingEntry("game_creation.desert_coverage"));
1769 genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1770 genworld->Add(new SettingEntry("game_creation.tree_placer"));
1771 genworld->Add(new SettingEntry("vehicle.road_side"));
1772 genworld->Add(new SettingEntry("economy.larger_towns"));
1773 genworld->Add(new SettingEntry("economy.initial_city_size"));
1774 genworld->Add(new SettingEntry("economy.town_layout"));
1775 genworld->Add(new SettingEntry("difficulty.industry_density"));
1776 genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1777 genworld->Add(new SettingEntry("game_creation.ending_year"));
1780 SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1782 SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1784 authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1785 authorities->Add(new SettingEntry("economy.bribe"));
1786 authorities->Add(new SettingEntry("economy.exclusive_rights"));
1787 authorities->Add(new SettingEntry("economy.fund_roads"));
1788 authorities->Add(new SettingEntry("economy.fund_buildings"));
1789 authorities->Add(new SettingEntry("economy.station_noise_level"));
1792 SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1794 towns->Add(new SettingEntry("economy.town_growth_rate"));
1795 towns->Add(new SettingEntry("economy.allow_town_roads"));
1796 towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1797 towns->Add(new SettingEntry("economy.found_town"));
1798 towns->Add(new SettingEntry("economy.town_cargogen_mode"));
1801 SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1803 industries->Add(new SettingEntry("construction.raw_industry_construction"));
1804 industries->Add(new SettingEntry("construction.industry_platform"));
1805 industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1806 industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1807 industries->Add(new SettingEntry("economy.type"));
1808 industries->Add(new SettingEntry("station.serve_neutral_industries"));
1811 SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1813 cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1814 cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1815 cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1816 cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1817 cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1818 cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1819 cdist->Add(new SettingEntry("linkgraph.accuracy"));
1820 cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1821 cdist->Add(new SettingEntry("linkgraph.demand_size"));
1822 cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1825 environment->Add(new SettingEntry("station.modified_catchment"));
1826 environment->Add(new SettingEntry("construction.extra_tree_placement"));
1829 SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1831 SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1833 npc->Add(new SettingEntry("script.settings_profile"));
1834 npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1835 npc->Add(new SettingEntry("script.script_max_memory_megabytes"));
1836 npc->Add(new SettingEntry("difficulty.competitor_speed"));
1837 npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1838 npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1839 npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1840 npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1841 npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1844 ai->Add(new SettingEntry("economy.give_money"));
1845 ai->Add(new SettingEntry("economy.allow_shares"));
1846 ai->Add(new SettingEntry("economy.min_years_for_shares"));
1849 SettingsPage *network = main->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK));
1851 network->Add(new SettingEntry("network.use_relay_service"));
1854 main->Init();
1856 return *main;
1859 static const StringID _game_settings_restrict_dropdown[] = {
1860 STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1861 STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1862 STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1863 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1864 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1866 static_assert(lengthof(_game_settings_restrict_dropdown) == RM_END);
1868 /** Warnings about hidden search results. */
1869 enum WarnHiddenResult {
1870 WHR_NONE, ///< Nothing was filtering matches away.
1871 WHR_CATEGORY, ///< Category setting filtered matches away.
1872 WHR_TYPE, ///< Type setting filtered matches away.
1873 WHR_CATEGORY_TYPE, ///< Both category and type settings filtered matches away.
1877 * Callback function for the reset all settings button
1878 * @param w Window which is calling this callback
1879 * @param confirmed boolean value, true when yes was clicked, false otherwise
1881 static void ResetAllSettingsConfirmationCallback(Window *w, bool confirmed)
1883 if (confirmed) {
1884 GetSettingsTree().ResetAll();
1885 GetSettingsTree().FoldAll();
1886 w->InvalidateData();
1890 /** Window to edit settings of the game. */
1891 struct GameSettingsWindow : Window {
1892 static const int SETTINGTREE_LEFT_OFFSET = 5; ///< Position of left edge of setting values
1893 static const int SETTINGTREE_RIGHT_OFFSET = 5; ///< Position of right edge of setting values
1894 static const int SETTINGTREE_TOP_OFFSET = 5; ///< Position of top edge of setting values
1895 static const int SETTINGTREE_BOTTOM_OFFSET = 5; ///< Position of bottom edge of setting values
1897 static GameSettings *settings_ptr; ///< Pointer to the game settings being displayed and modified.
1899 SettingEntry *valuewindow_entry; ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
1900 SettingEntry *clicked_entry; ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
1901 SettingEntry *last_clicked; ///< If non-nullptr, pointer to the last clicked setting.
1902 SettingEntry *valuedropdown_entry; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
1903 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
1905 SettingFilter filter; ///< Filter for the list.
1906 QueryString filter_editbox; ///< Filter editbox;
1907 bool manually_changed_folding; ///< Whether the user expanded/collapsed something manually.
1908 WarnHiddenResult warn_missing; ///< Whether and how to warn about missing search results.
1909 int warn_lines; ///< Number of lines used for warning about missing search results.
1911 Scrollbar *vscroll;
1913 GameSettingsWindow(WindowDesc *desc) : Window(desc), filter_editbox(50)
1915 this->warn_missing = WHR_NONE;
1916 this->warn_lines = 0;
1917 this->filter.mode = (RestrictionMode)_settings_client.gui.settings_restriction_mode;
1918 this->filter.min_cat = RM_ALL;
1919 this->filter.type = ST_ALL;
1920 this->filter.type_hides = false;
1921 this->settings_ptr = &GetGameSettings();
1923 _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1924 GetSettingsTree().FoldAll(); // Close all sub-pages
1926 this->valuewindow_entry = nullptr; // No setting entry for which a entry window is opened
1927 this->clicked_entry = nullptr; // No numeric setting buttons are depressed
1928 this->last_clicked = nullptr;
1929 this->valuedropdown_entry = nullptr;
1930 this->closing_dropdown = false;
1931 this->manually_changed_folding = false;
1933 this->CreateNestedTree();
1934 this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1935 this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS);
1937 this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1938 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
1939 this->SetFocusedWidget(WID_GS_FILTER);
1941 this->InvalidateData();
1944 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1946 switch (widget) {
1947 case WID_GS_OPTIONSPANEL:
1948 resize->height = SETTING_HEIGHT = std::max({(int)_circle_size.height, SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL}) + 1;
1949 resize->width = 1;
1951 size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1952 break;
1954 case WID_GS_HELP_TEXT: {
1955 static const StringID setting_types[] = {
1956 STR_CONFIG_SETTING_TYPE_CLIENT,
1957 STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1958 STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1960 for (uint i = 0; i < lengthof(setting_types); i++) {
1961 SetDParam(0, setting_types[i]);
1962 size->width = std::max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1964 size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1965 std::max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1966 break;
1969 case WID_GS_RESTRICT_CATEGORY:
1970 case WID_GS_RESTRICT_TYPE:
1971 size->width = std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1972 break;
1974 default:
1975 break;
1979 void OnPaint() override
1981 if (this->closing_dropdown) {
1982 this->closing_dropdown = false;
1983 assert(this->valuedropdown_entry != nullptr);
1984 this->valuedropdown_entry->SetButtons(0);
1985 this->valuedropdown_entry = nullptr;
1988 /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1989 const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1990 StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1991 int new_warn_lines;
1992 if (this->warn_missing == WHR_NONE) {
1993 new_warn_lines = 0;
1994 } else {
1995 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1996 new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1998 if (this->warn_lines != new_warn_lines) {
1999 this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
2000 this->warn_lines = new_warn_lines;
2003 this->DrawWidgets();
2005 /* Draw the 'some search results are hidden' notice. */
2006 if (this->warn_missing != WHR_NONE) {
2007 const int left = panel->pos_x;
2008 const int right = left + panel->current_x - 1;
2009 const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
2010 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2011 if (this->warn_lines == 1) {
2012 /* If the warning fits at one line, center it. */
2013 DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
2014 } else {
2015 DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
2020 void SetStringParameters(int widget) const override
2022 switch (widget) {
2023 case WID_GS_RESTRICT_DROPDOWN:
2024 SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
2025 break;
2027 case WID_GS_TYPE_DROPDOWN:
2028 switch (this->filter.type) {
2029 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
2030 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
2031 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
2032 default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
2034 break;
2038 DropDownList BuildDropDownList(int widget) const
2040 DropDownList list;
2041 switch (widget) {
2042 case WID_GS_RESTRICT_DROPDOWN:
2043 for (int mode = 0; mode != RM_END; mode++) {
2044 /* If we are in adv. settings screen for the new game's settings,
2045 * we don't want to allow comparing with new game's settings. */
2046 bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
2048 list.emplace_back(new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled));
2050 break;
2052 case WID_GS_TYPE_DROPDOWN:
2053 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false));
2054 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));
2055 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));
2056 list.emplace_back(new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false));
2057 break;
2059 return list;
2062 void DrawWidget(const Rect &r, int widget) const override
2064 switch (widget) {
2065 case WID_GS_OPTIONSPANEL: {
2066 int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
2067 uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
2068 int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
2069 this->vscroll->GetPosition(), last_row, this->last_clicked);
2070 if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
2071 break;
2074 case WID_GS_HELP_TEXT:
2075 if (this->last_clicked != nullptr) {
2076 const IntSettingDesc *sd = this->last_clicked->setting;
2078 int y = r.top;
2079 switch (sd->GetType()) {
2080 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
2081 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
2082 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
2083 default: NOT_REACHED();
2085 DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
2086 y += FONT_HEIGHT_NORMAL;
2088 this->last_clicked->SetValueDParams(0, sd->def);
2089 DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
2090 y += FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
2092 DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
2094 break;
2096 default:
2097 break;
2102 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2103 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2105 void SetDisplayedHelpText(SettingEntry *pe)
2107 if (this->last_clicked != pe) this->SetDirty();
2108 this->last_clicked = pe;
2111 void OnClick(Point pt, int widget, int click_count) override
2113 switch (widget) {
2114 case WID_GS_EXPAND_ALL:
2115 this->manually_changed_folding = true;
2116 GetSettingsTree().UnFoldAll();
2117 this->InvalidateData();
2118 break;
2120 case WID_GS_COLLAPSE_ALL:
2121 this->manually_changed_folding = true;
2122 GetSettingsTree().FoldAll();
2123 this->InvalidateData();
2124 break;
2126 case WID_GS_RESET_ALL:
2127 ShowQuery(
2128 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_CAPTION,
2129 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT,
2130 this,
2131 ResetAllSettingsConfirmationCallback
2133 break;
2135 case WID_GS_RESTRICT_DROPDOWN: {
2136 DropDownList list = this->BuildDropDownList(widget);
2137 if (!list.empty()) {
2138 ShowDropDownList(this, std::move(list), this->filter.mode, widget);
2140 break;
2143 case WID_GS_TYPE_DROPDOWN: {
2144 DropDownList list = this->BuildDropDownList(widget);
2145 if (!list.empty()) {
2146 ShowDropDownList(this, std::move(list), this->filter.type, widget);
2148 break;
2152 if (widget != WID_GS_OPTIONSPANEL) return;
2154 uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2155 if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2156 btn -= this->warn_lines;
2158 uint cur_row = 0;
2159 BaseSettingEntry *clicked_entry = GetSettingsTree().FindEntry(btn, &cur_row);
2161 if (clicked_entry == nullptr) return; // Clicked below the last setting of the page
2163 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
2164 if (x < 0) return; // Clicked left of the entry
2166 SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2167 if (clicked_page != nullptr) {
2168 this->SetDisplayedHelpText(nullptr);
2169 clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2171 this->manually_changed_folding = true;
2173 this->InvalidateData();
2174 return;
2177 SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2178 assert(pe != nullptr);
2179 const IntSettingDesc *sd = pe->setting;
2181 /* return if action is only active in network, or only settable by server */
2182 if (!sd->IsEditable()) {
2183 this->SetDisplayedHelpText(pe);
2184 return;
2187 int32 value = sd->Read(ResolveObject(settings_ptr, sd));
2189 /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2190 if (x < SETTING_BUTTON_WIDTH && (sd->flags & SF_GUI_DROPDOWN)) {
2191 this->SetDisplayedHelpText(pe);
2193 if (this->valuedropdown_entry == pe) {
2194 /* unclick the dropdown */
2195 HideDropDownMenu(this);
2196 this->closing_dropdown = false;
2197 this->valuedropdown_entry->SetButtons(0);
2198 this->valuedropdown_entry = nullptr;
2199 } else {
2200 if (this->valuedropdown_entry != nullptr) this->valuedropdown_entry->SetButtons(0);
2201 this->closing_dropdown = false;
2203 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2204 int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2206 Rect wi_rect;
2207 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2208 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2209 wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2210 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2212 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2213 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2214 this->valuedropdown_entry = pe;
2215 this->valuedropdown_entry->SetButtons(SEF_LEFT_DEPRESSED);
2217 DropDownList list;
2218 for (int i = sd->min; i <= (int)sd->max; i++) {
2219 list.emplace_back(new DropDownListStringItem(sd->str_val + i - sd->min, i, false));
2222 ShowDropDownListAt(this, std::move(list), value, -1, wi_rect, COLOUR_ORANGE, true);
2225 this->SetDirty();
2226 } else if (x < SETTING_BUTTON_WIDTH) {
2227 this->SetDisplayedHelpText(pe);
2228 int32 oldvalue = value;
2230 if (sd->IsBoolSetting()) {
2231 value ^= 1;
2232 } else {
2233 /* Add a dynamic step-size to the scroller. In a maximum of
2234 * 50-steps you should be able to get from min to max,
2235 * unless specified otherwise in the 'interval' variable
2236 * of the current setting. */
2237 uint32 step = (sd->interval == 0) ? ((sd->max - sd->min) / 50) : sd->interval;
2238 if (step == 0) step = 1;
2240 /* don't allow too fast scrolling */
2241 if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2242 _left_button_clicked = false;
2243 return;
2246 /* Increase or decrease the value and clamp it to extremes */
2247 if (x >= SETTING_BUTTON_WIDTH / 2) {
2248 value += step;
2249 if (sd->min < 0) {
2250 assert((int32)sd->max >= 0);
2251 if (value > (int32)sd->max) value = (int32)sd->max;
2252 } else {
2253 if ((uint32)value > sd->max) value = (int32)sd->max;
2255 if (value < sd->min) value = sd->min; // skip between "disabled" and minimum
2256 } else {
2257 value -= step;
2258 if (value < sd->min) value = (sd->flags & SF_GUI_0_IS_SPECIAL) ? 0 : sd->min;
2261 /* Set up scroller timeout for numeric values */
2262 if (value != oldvalue) {
2263 if (this->clicked_entry != nullptr) { // Release previous buttons if any
2264 this->clicked_entry->SetButtons(0);
2266 this->clicked_entry = pe;
2267 this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2268 this->SetTimeout();
2269 _left_button_clicked = false;
2273 if (value != oldvalue) {
2274 SetSettingValue(sd, value);
2275 this->SetDirty();
2277 } else {
2278 /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2279 if (this->last_clicked == pe && !sd->IsBoolSetting() && !(sd->flags & SF_GUI_DROPDOWN)) {
2280 int64 value64 = value;
2281 /* Show the correct currency-translated value */
2282 if (sd->flags & SF_GUI_CURRENCY) value64 *= _currency->rate;
2284 this->valuewindow_entry = pe;
2285 SetDParam(0, value64);
2286 /* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2287 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 15, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2289 this->SetDisplayedHelpText(pe);
2293 void OnTimeout() override
2295 if (this->clicked_entry != nullptr) { // On timeout, release any depressed buttons
2296 this->clicked_entry->SetButtons(0);
2297 this->clicked_entry = nullptr;
2298 this->SetDirty();
2302 void OnQueryTextFinished(char *str) override
2304 /* The user pressed cancel */
2305 if (str == nullptr) return;
2307 assert(this->valuewindow_entry != nullptr);
2308 const IntSettingDesc *sd = this->valuewindow_entry->setting;
2310 int32 value;
2311 if (!StrEmpty(str)) {
2312 long long llvalue = atoll(str);
2314 /* Save the correct currency-translated value */
2315 if (sd->flags & SF_GUI_CURRENCY) llvalue /= _currency->rate;
2317 value = (int32)ClampToI32(llvalue);
2318 } else {
2319 value = sd->def;
2322 SetSettingValue(this->valuewindow_entry->setting, value);
2323 this->SetDirty();
2326 void OnDropdownSelect(int widget, int index) override
2328 switch (widget) {
2329 case WID_GS_RESTRICT_DROPDOWN:
2330 this->filter.mode = (RestrictionMode)index;
2331 if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2332 this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2334 if (!this->manually_changed_folding) {
2335 /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2336 GetSettingsTree().UpdateFilterState(this->filter, false);
2337 GetSettingsTree().UnFoldAll();
2339 } else {
2340 /* Non-'changes' filter. Save as default. */
2341 _settings_client.gui.settings_restriction_mode = this->filter.mode;
2343 this->InvalidateData();
2344 break;
2346 case WID_GS_TYPE_DROPDOWN:
2347 this->filter.type = (SettingType)index;
2348 this->InvalidateData();
2349 break;
2351 default:
2352 if (widget < 0) {
2353 /* Deal with drop down boxes on the panel. */
2354 assert(this->valuedropdown_entry != nullptr);
2355 const IntSettingDesc *sd = this->valuedropdown_entry->setting;
2356 assert(sd->flags & SF_GUI_DROPDOWN);
2358 SetSettingValue(sd, index);
2359 this->SetDirty();
2361 break;
2365 void OnDropdownClose(Point pt, int widget, int index, bool instant_close) override
2367 if (widget >= 0) {
2368 /* Normally the default implementation of OnDropdownClose() takes care of
2369 * a few things. We want that behaviour here too, but only for
2370 * "normal" dropdown boxes. The special dropdown boxes added for every
2371 * setting that needs one can't have this call. */
2372 Window::OnDropdownClose(pt, widget, index, instant_close);
2373 } else {
2374 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2375 * the same dropdown button was clicked again, and then not open the dropdown again.
2376 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2377 * after OnClick. */
2378 assert(this->valuedropdown_entry != nullptr);
2379 this->closing_dropdown = true;
2380 this->SetDirty();
2384 void OnInvalidateData(int data = 0, bool gui_scope = true) override
2386 if (!gui_scope) return;
2388 /* Update which settings are to be visible. */
2389 RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2390 this->filter.min_cat = min_level;
2391 this->filter.type_hides = false;
2392 GetSettingsTree().UpdateFilterState(this->filter, false);
2394 if (this->filter.string.IsEmpty()) {
2395 this->warn_missing = WHR_NONE;
2396 } else if (min_level < this->filter.min_cat) {
2397 this->warn_missing = this->filter.type_hides ? WHR_CATEGORY_TYPE : WHR_CATEGORY;
2398 } else {
2399 this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2401 this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2403 if (this->last_clicked != nullptr && !GetSettingsTree().IsVisible(this->last_clicked)) {
2404 this->SetDisplayedHelpText(nullptr);
2407 bool all_folded = true;
2408 bool all_unfolded = true;
2409 GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2410 this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2411 this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2414 void OnEditboxChanged(int wid) override
2416 if (wid == WID_GS_FILTER) {
2417 this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2418 if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2419 /* User never expanded/collapsed single pages and entered a filter term.
2420 * Expand everything, to save weird expand clicks, */
2421 GetSettingsTree().UnFoldAll();
2423 this->InvalidateData();
2427 void OnResize() override
2429 this->vscroll->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET);
2433 GameSettings *GameSettingsWindow::settings_ptr = nullptr;
2435 static const NWidgetPart _nested_settings_selection_widgets[] = {
2436 NWidget(NWID_HORIZONTAL),
2437 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2438 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2439 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2440 EndContainer(),
2441 NWidget(WWT_PANEL, COLOUR_MAUVE),
2442 NWidget(NWID_VERTICAL), SetPIP(0, WD_PAR_VSEP_NORMAL, 0), SetPadding(WD_TEXTPANEL_TOP, 0, WD_TEXTPANEL_BOTTOM, 0),
2443 NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2444 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2445 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),
2446 EndContainer(),
2447 NWidget(NWID_HORIZONTAL), SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2448 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2449 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),
2450 EndContainer(),
2451 EndContainer(),
2452 NWidget(NWID_HORIZONTAL), SetPadding(0, 0, WD_TEXTPANEL_BOTTOM, 0),
2453 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
2454 NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2455 NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2456 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2457 EndContainer(),
2458 EndContainer(),
2459 NWidget(NWID_HORIZONTAL),
2460 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GS_OPTIONSPANEL), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR), EndContainer(),
2461 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2462 EndContainer(),
2463 NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2464 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2465 SetPadding(WD_FRAMETEXT_TOP, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_BOTTOM, WD_FRAMETEXT_LEFT),
2466 EndContainer(),
2467 NWidget(NWID_HORIZONTAL),
2468 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2469 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2470 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_RESET_ALL), SetDataTip(STR_CONFIG_SETTING_RESET_ALL, STR_NULL),
2471 NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2472 EndContainer(),
2473 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2474 EndContainer(),
2477 static WindowDesc _settings_selection_desc(
2478 WDP_CENTER, "settings", 510, 450,
2479 WC_GAME_OPTIONS, WC_NONE,
2481 _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2484 /** Open advanced settings window. */
2485 void ShowGameSettings()
2487 CloseWindowByClass(WC_GAME_OPTIONS);
2488 new GameSettingsWindow(&_settings_selection_desc);
2493 * Draw [<][>] boxes.
2494 * @param x the x position to draw
2495 * @param y the y position to draw
2496 * @param button_colour the colour of the button
2497 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2498 * @param clickable_left is the left button clickable?
2499 * @param clickable_right is the right button clickable?
2501 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2503 int colour = _colour_gradient[button_colour][2];
2504 Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2506 DrawFrameRect(x, y, x + dim.width - 1, y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2507 DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2508 DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2509 DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2511 /* Grey out the buttons that aren't clickable */
2512 bool rtl = _current_text_dir == TD_RTL;
2513 if (rtl ? !clickable_right : !clickable_left) {
2514 GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2516 if (rtl ? !clickable_left : !clickable_right) {
2517 GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2522 * Draw a dropdown button.
2523 * @param x the x position to draw
2524 * @param y the y position to draw
2525 * @param button_colour the colour of the button
2526 * @param state true = lowered
2527 * @param clickable is the button clickable?
2529 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2531 int colour = _colour_gradient[button_colour][2];
2533 DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2534 DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2536 if (!clickable) {
2537 GfxFillRect(x + 1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2542 * Draw a toggle button.
2543 * @param x the x position to draw
2544 * @param y the y position to draw
2545 * @param state true = lowered
2546 * @param clickable is the button clickable?
2548 void DrawBoolButton(int x, int y, bool state, bool clickable)
2550 static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2551 DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2554 struct CustomCurrencyWindow : Window {
2555 int query_widget;
2557 CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2559 this->InitNested();
2561 SetButtonState();
2564 void SetButtonState()
2566 this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2567 this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2568 this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2569 this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2572 void SetStringParameters(int widget) const override
2574 switch (widget) {
2575 case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2576 case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2577 case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2578 case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2579 case WID_CC_YEAR:
2580 SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2581 SetDParam(1, _custom_currency.to_euro);
2582 break;
2584 case WID_CC_PREVIEW:
2585 SetDParam(0, 10000);
2586 break;
2590 void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2592 switch (widget) {
2593 /* Set the appropriate width for the edit 'buttons' */
2594 case WID_CC_SEPARATOR_EDIT:
2595 case WID_CC_PREFIX_EDIT:
2596 case WID_CC_SUFFIX_EDIT:
2597 size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2598 break;
2600 /* Make sure the window is wide enough for the widest exchange rate */
2601 case WID_CC_RATE:
2602 SetDParam(0, 1);
2603 SetDParam(1, INT32_MAX);
2604 *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2605 break;
2609 void OnClick(Point pt, int widget, int click_count) override
2611 int line = 0;
2612 int len = 0;
2613 StringID str = 0;
2614 CharSetFilter afilter = CS_ALPHANUMERAL;
2616 switch (widget) {
2617 case WID_CC_RATE_DOWN:
2618 if (_custom_currency.rate > 1) _custom_currency.rate--;
2619 if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2620 this->EnableWidget(WID_CC_RATE_UP);
2621 break;
2623 case WID_CC_RATE_UP:
2624 if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2625 if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2626 this->EnableWidget(WID_CC_RATE_DOWN);
2627 break;
2629 case WID_CC_RATE:
2630 SetDParam(0, _custom_currency.rate);
2631 str = STR_JUST_INT;
2632 len = 5;
2633 line = WID_CC_RATE;
2634 afilter = CS_NUMERAL;
2635 break;
2637 case WID_CC_SEPARATOR_EDIT:
2638 case WID_CC_SEPARATOR:
2639 SetDParamStr(0, _custom_currency.separator);
2640 str = STR_JUST_RAW_STRING;
2641 len = 7;
2642 line = WID_CC_SEPARATOR;
2643 break;
2645 case WID_CC_PREFIX_EDIT:
2646 case WID_CC_PREFIX:
2647 SetDParamStr(0, _custom_currency.prefix);
2648 str = STR_JUST_RAW_STRING;
2649 len = 15;
2650 line = WID_CC_PREFIX;
2651 break;
2653 case WID_CC_SUFFIX_EDIT:
2654 case WID_CC_SUFFIX:
2655 SetDParamStr(0, _custom_currency.suffix);
2656 str = STR_JUST_RAW_STRING;
2657 len = 15;
2658 line = WID_CC_SUFFIX;
2659 break;
2661 case WID_CC_YEAR_DOWN:
2662 _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2663 if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2664 this->EnableWidget(WID_CC_YEAR_UP);
2665 break;
2667 case WID_CC_YEAR_UP:
2668 _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2669 if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2670 this->EnableWidget(WID_CC_YEAR_DOWN);
2671 break;
2673 case WID_CC_YEAR:
2674 SetDParam(0, _custom_currency.to_euro);
2675 str = STR_JUST_INT;
2676 len = 7;
2677 line = WID_CC_YEAR;
2678 afilter = CS_NUMERAL;
2679 break;
2682 if (len != 0) {
2683 this->query_widget = line;
2684 ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2687 this->SetTimeout();
2688 this->SetDirty();
2691 void OnQueryTextFinished(char *str) override
2693 if (str == nullptr) return;
2695 switch (this->query_widget) {
2696 case WID_CC_RATE:
2697 _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2698 break;
2700 case WID_CC_SEPARATOR: // Thousands separator
2701 _custom_currency.separator = str;
2702 break;
2704 case WID_CC_PREFIX:
2705 _custom_currency.prefix = str;
2706 break;
2708 case WID_CC_SUFFIX:
2709 _custom_currency.suffix = str;
2710 break;
2712 case WID_CC_YEAR: { // Year to switch to euro
2713 int val = atoi(str);
2715 _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : std::min(val, MAX_YEAR));
2716 break;
2719 MarkWholeScreenDirty();
2720 SetButtonState();
2723 void OnTimeout() override
2725 this->SetDirty();
2729 static const NWidgetPart _nested_cust_currency_widgets[] = {
2730 NWidget(NWID_HORIZONTAL),
2731 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2732 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2733 EndContainer(),
2734 NWidget(WWT_PANEL, COLOUR_GREY),
2735 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(7, 3, 0),
2736 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2737 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2738 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2739 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2740 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2741 EndContainer(),
2742 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2743 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2744 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2745 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2746 EndContainer(),
2747 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2748 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2749 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2750 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2751 EndContainer(),
2752 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2753 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2754 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2755 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2756 EndContainer(),
2757 NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2758 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2759 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2760 NWidget(NWID_SPACER), SetMinimalSize(5, 0),
2761 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2762 EndContainer(),
2763 EndContainer(),
2764 NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2765 SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2766 EndContainer(),
2769 static WindowDesc _cust_currency_desc(
2770 WDP_CENTER, nullptr, 0, 0,
2771 WC_CUSTOM_CURRENCY, WC_NONE,
2773 _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2776 /** Open custom currency window. */
2777 static void ShowCustCurrency()
2779 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
2780 new CustomCurrencyWindow(&_cust_currency_desc);