OpenTTD
settings_gui.cpp
Go to the documentation of this file.
1 /* $Id: settings_gui.cpp 27819 2017-03-23 18:07:04Z peter1138 $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "stdafx.h"
13 #include "currency.h"
14 #include "error.h"
15 #include "settings_gui.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "network/network.h"
19 #include "town.h"
20 #include "settings_internal.h"
21 #include "newgrf_townname.h"
22 #include "strings_func.h"
23 #include "window_func.h"
24 #include "string_func.h"
25 #include "widgets/dropdown_type.h"
26 #include "widgets/dropdown_func.h"
27 #include "highscore.h"
28 #include "base_media_base.h"
29 #include "company_base.h"
30 #include "company_func.h"
31 #include "viewport_func.h"
32 #include "core/geometry_func.hpp"
33 #include "ai/ai.hpp"
34 #include "blitter/factory.hpp"
35 #include "language.h"
36 #include "textfile_gui.h"
37 #include "stringfilter_type.h"
38 #include "querystring_gui.h"
39 #include "signs_func.h"
40 #include "station_func.h"
41 
42 #include <vector>
43 
44 #include "safeguards.h"
45 
46 
47 static const StringID _driveside_dropdown[] = {
48  STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT,
49  STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_RIGHT,
51 };
52 
53 static const StringID _autosave_dropdown[] = {
54  STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
55  STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_1_MONTH,
56  STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_3_MONTHS,
57  STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_6_MONTHS,
58  STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_12_MONTHS,
60 };
61 
62 static const StringID _gui_zoom_dropdown[] = {
63  STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_NORMAL,
64  STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_2X_ZOOM,
65  STR_GAME_OPTIONS_GUI_ZOOM_DROPDOWN_4X_ZOOM,
67 };
68 
69 int _nb_orig_names = SPECSTR_TOWNNAME_LAST - SPECSTR_TOWNNAME_START + 1;
70 static StringID *_grf_names = NULL;
71 static int _nb_grf_names = 0;
72 
74 
75 static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd);
76 
79 {
81  _grf_names = GetGRFTownNameList();
82  _nb_grf_names = 0;
83  for (StringID *s = _grf_names; *s != INVALID_STRING_ID; s++) _nb_grf_names++;
84 }
85 
91 static inline StringID TownName(int town_name)
92 {
93  if (town_name < _nb_orig_names) return STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH + town_name;
94  town_name -= _nb_orig_names;
95  if (town_name < _nb_grf_names) return _grf_names[town_name];
96  return STR_UNDEFINED;
97 }
98 
103 static int GetCurRes()
104 {
105  int i;
106 
107  for (i = 0; i != _num_resolutions; i++) {
108  if ((int)_resolutions[i].width == _screen.width &&
109  (int)_resolutions[i].height == _screen.height) {
110  break;
111  }
112  }
113  return i;
114 }
115 
116 static void ShowCustCurrency();
117 
118 template <class T>
119 static DropDownList *BuiltSetDropDownList(int *selected_index)
120 {
121  int n = T::GetNumSets();
122  *selected_index = T::GetIndexOfUsedSet();
123 
124  DropDownList *list = new DropDownList();
125  for (int i = 0; i < n; i++) {
126  *list->Append() = new DropDownListCharStringItem(T::GetSet(i)->name, i, (_game_mode == GM_MENU) ? false : (*selected_index != i));
127  }
128 
129  return list;
130 }
131 
133 template <class TBaseSet>
135  const TBaseSet* baseset;
137 
138  BaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
139  {
140  const char *textfile = this->baseset->GetTextfile(file_type);
141  this->LoadTextfile(textfile, BASESET_DIR);
142  }
143 
144  /* virtual */ void SetStringParameters(int widget) const
145  {
146  if (widget == WID_TF_CAPTION) {
148  SetDParamStr(1, this->baseset->name);
149  }
150  }
151 };
152 
159 template <class TBaseSet>
160 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet* baseset, StringID content_type)
161 {
163  new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
164 }
165 
167  GameSettings *opt;
168  bool reload;
169 
170  GameOptionsWindow(WindowDesc *desc) : Window(desc)
171  {
172  this->opt = &GetGameSettings();
173  this->reload = false;
174 
176  this->OnInvalidateData(0);
177  }
178 
180  {
182  if (this->reload) _switch_mode = SM_MENU;
183  }
184 
191  DropDownList *BuildDropDownList(int widget, int *selected_index) const
192  {
193  DropDownList *list = NULL;
194  switch (widget) {
195  case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
196  list = new DropDownList();
197  *selected_index = this->opt->locale.currency;
198  StringID *items = BuildCurrencyDropdown();
199  uint64 disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
200 
201  /* Add non-custom currencies; sorted naturally */
202  for (uint i = 0; i < CURRENCY_END; items++, i++) {
203  if (i == CURRENCY_CUSTOM) continue;
204  *list->Append() = new DropDownListStringItem(*items, i, HasBit(disabled, i));
205  }
207 
208  /* Append custom currency at the end */
209  *list->Append() = new DropDownListItem(-1, false); // separator line
210  *list->Append() = new DropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM));
211  break;
212  }
213 
214  case WID_GO_ROADSIDE_DROPDOWN: { // Setup road-side dropdown
215  list = new DropDownList();
216  *selected_index = this->opt->vehicle.road_side;
217  const StringID *items = _driveside_dropdown;
218  uint disabled = 0;
219 
220  /* You can only change the drive side if you are in the menu or ingame with
221  * no vehicles present. In a networking game only the server can change it */
222  extern bool RoadVehiclesAreBuilt();
223  if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !_network_server)) {
224  disabled = ~(1 << this->opt->vehicle.road_side); // disable the other value
225  }
226 
227  for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
228  *list->Append() = new DropDownListStringItem(*items, i, HasBit(disabled, i));
229  }
230  break;
231  }
232 
233  case WID_GO_TOWNNAME_DROPDOWN: { // Setup townname dropdown
234  list = new DropDownList();
235  *selected_index = this->opt->game_creation.town_name;
236 
237  int enabled_item = (_game_mode == GM_MENU || Town::GetNumItems() == 0) ? -1 : *selected_index;
238 
239  /* Add and sort newgrf townnames generators */
240  for (int i = 0; i < _nb_grf_names; i++) {
241  int result = _nb_orig_names + i;
242  *list->Append() = new DropDownListStringItem(_grf_names[i], result, enabled_item != result && enabled_item >= 0);
243  }
245 
246  int newgrf_size = list->Length();
247  /* Insert newgrf_names at the top of the list */
248  if (newgrf_size > 0) {
249  *list->Append() = new DropDownListItem(-1, false); // separator line
250  newgrf_size++;
251  }
252 
253  /* Add and sort original townnames generators */
254  for (int i = 0; i < _nb_orig_names; i++) {
255  *list->Append() = new DropDownListStringItem(STR_GAME_OPTIONS_TOWN_NAME_ORIGINAL_ENGLISH + i, i, enabled_item != i && enabled_item >= 0);
256  }
257  QSortT(list->Begin() + newgrf_size, list->Length() - newgrf_size, DropDownListStringItem::NatSortFunc);
258  break;
259  }
260 
261  case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
262  list = new DropDownList();
263  *selected_index = _settings_client.gui.autosave;
264  const StringID *items = _autosave_dropdown;
265  for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
266  *list->Append() = new DropDownListStringItem(*items, i, false);
267  }
268  break;
269  }
270 
271  case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
272  list = new DropDownList();
273  for (uint i = 0; i < _languages.Length(); i++) {
274  if (&_languages[i] == _current_language) *selected_index = i;
275  *list->Append() = new DropDownListStringItem(SPECSTR_LANGUAGE_START + i, i, false);
276  }
278  break;
279  }
280 
281  case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
282  if (_num_resolutions == 0) break;
283 
284  list = new DropDownList();
285  *selected_index = GetCurRes();
286  for (int i = 0; i < _num_resolutions; i++) {
287  *list->Append() = new DropDownListStringItem(SPECSTR_RESOLUTION_START + i, i, false);
288  }
289  break;
290 
292  list = new DropDownList();
293  *selected_index = ZOOM_LVL_OUT_4X - _gui_zoom;
294  const StringID *items = _gui_zoom_dropdown;
295  for (int i = 0; *items != INVALID_STRING_ID; items++, i++) {
297  }
298  break;
299  }
300 
302  list = BuiltSetDropDownList<BaseGraphics>(selected_index);
303  break;
304 
306  list = BuiltSetDropDownList<BaseSounds>(selected_index);
307  break;
308 
310  list = BuiltSetDropDownList<BaseMusic>(selected_index);
311  break;
312 
313  default:
314  return NULL;
315  }
316 
317  return list;
318  }
319 
320  virtual void SetStringParameters(int widget) const
321  {
322  switch (widget) {
323  case WID_GO_CURRENCY_DROPDOWN: SetDParam(0, _currency_specs[this->opt->locale.currency].name); break;
324  case WID_GO_ROADSIDE_DROPDOWN: SetDParam(0, STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_LEFT + this->opt->vehicle.road_side); break;
326  case WID_GO_AUTOSAVE_DROPDOWN: SetDParam(0, _autosave_dropdown[_settings_client.gui.autosave]); break;
328  case WID_GO_RESOLUTION_DROPDOWN: SetDParam(0, GetCurRes() == _num_resolutions ? STR_GAME_OPTIONS_RESOLUTION_OTHER : SPECSTR_RESOLUTION_START + GetCurRes()); break;
329  case WID_GO_GUI_ZOOM_DROPDOWN: SetDParam(0, _gui_zoom_dropdown[ZOOM_LVL_OUT_4X - _gui_zoom]); break;
331  case WID_GO_BASE_GRF_STATUS: SetDParam(0, BaseGraphics::GetUsedSet()->GetNumInvalid()); break;
334  case WID_GO_BASE_MUSIC_STATUS: SetDParam(0, BaseMusic::GetUsedSet()->GetNumInvalid()); break;
335  }
336  }
337 
338  virtual void DrawWidget(const Rect &r, int widget) const
339  {
340  switch (widget) {
343  DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
344  break;
345 
348  DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
349  break;
350 
353  DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_BLACK_RAW_STRING);
354  break;
355  }
356  }
357 
358  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
359  {
360  switch (widget) {
362  /* Find the biggest description for the default size. */
363  for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
365  size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
366  }
367  break;
368 
370  /* Find the biggest description for the default size. */
371  for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
372  uint invalid_files = BaseGraphics::GetSet(i)->GetNumInvalid();
373  if (invalid_files == 0) continue;
374 
375  SetDParam(0, invalid_files);
376  *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_GRF_STATUS));
377  }
378  break;
379 
381  /* Find the biggest description for the default size. */
382  for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
384  size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
385  }
386  break;
387 
389  /* Find the biggest description for the default size. */
390  for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
391  SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
392  size->height = max(size->height, (uint)GetStringHeight(STR_BLACK_RAW_STRING, size->width));
393  }
394  break;
395 
397  /* Find the biggest description for the default size. */
398  for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
399  uint invalid_files = BaseMusic::GetSet(i)->GetNumInvalid();
400  if (invalid_files == 0) continue;
401 
402  SetDParam(0, invalid_files);
403  *size = maxdim(*size, GetStringBoundingBox(STR_GAME_OPTIONS_BASE_MUSIC_STATUS));
404  }
405  break;
406 
407  default: {
408  int selected;
409  DropDownList *list = this->BuildDropDownList(widget, &selected);
410  if (list != NULL) {
411  /* Find the biggest item for the default size. */
412  for (const DropDownListItem * const *it = list->Begin(); it != list->End(); it++) {
413  Dimension string_dim;
414  int width = (*it)->Width();
415  string_dim.width = width + padding.width;
416  string_dim.height = (*it)->Height(width) + padding.height;
417  *size = maxdim(*size, string_dim);
418  }
419  delete list;
420  }
421  }
422  }
423  }
424 
425  virtual void OnClick(Point pt, int widget, int click_count)
426  {
427  if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
428  if (BaseGraphics::GetUsedSet() == NULL) return;
429 
430  ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
431  return;
432  }
433  if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
434  if (BaseSounds::GetUsedSet() == NULL) return;
435 
436  ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
437  return;
438  }
439  if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
440  if (BaseMusic::GetUsedSet() == NULL) return;
441 
442  ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
443  return;
444  }
445  switch (widget) {
446  case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
447  /* try to toggle full-screen on/off */
448  if (!ToggleFullScreen(!_fullscreen)) {
449  ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
450  }
452  this->SetDirty();
453  break;
454 
455  default: {
456  int selected;
457  DropDownList *list = this->BuildDropDownList(widget, &selected);
458  if (list != NULL) {
459  ShowDropDownList(this, list, selected, widget);
460  } else {
461  if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
462  }
463  break;
464  }
465  }
466  }
467 
473  template <class T>
474  void SetMediaSet(int index)
475  {
476  if (_game_mode == GM_MENU) {
477  const char *name = T::GetSet(index)->name;
478 
479  free(T::ini_set);
480  T::ini_set = stredup(name);
481 
482  T::SetSet(name);
483  this->reload = true;
484  this->InvalidateData();
485  }
486  }
487 
488  virtual void OnDropdownSelect(int widget, int index)
489  {
490  switch (widget) {
491  case WID_GO_CURRENCY_DROPDOWN: // Currency
492  if (index == CURRENCY_CUSTOM) ShowCustCurrency();
493  this->opt->locale.currency = index;
495  break;
496 
497  case WID_GO_ROADSIDE_DROPDOWN: // Road side
498  if (this->opt->vehicle.road_side != index) { // only change if setting changed
499  uint i;
500  if (GetSettingFromName("vehicle.road_side", &i) == NULL) NOT_REACHED();
501  SetSettingValue(i, index);
503  }
504  break;
505 
506  case WID_GO_TOWNNAME_DROPDOWN: // Town names
507  if (_game_mode == GM_MENU || Town::GetNumItems() == 0) {
508  this->opt->game_creation.town_name = index;
510  }
511  break;
512 
513  case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
514  _settings_client.gui.autosave = index;
515  this->SetDirty();
516  break;
517 
518  case WID_GO_LANG_DROPDOWN: // Change interface language
519  ReadLanguagePack(&_languages[index]);
524  break;
525 
526  case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
527  if (index < _num_resolutions && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
528  this->SetDirty();
529  }
530  break;
531 
534  _gui_zoom = (ZoomLevel)(ZOOM_LVL_OUT_4X - index);
540  break;
541 
543  this->SetMediaSet<BaseGraphics>(index);
544  break;
545 
547  this->SetMediaSet<BaseSounds>(index);
548  break;
549 
551  this->SetMediaSet<BaseMusic>(index);
552  break;
553  }
554  }
555 
561  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
562  {
563  if (!gui_scope) return;
565 
566  bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
567  this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
568 
569  for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
573  }
574 
575  missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
576  this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
577  }
578 };
579 
580 static const NWidgetPart _nested_game_options_widgets[] = {
582  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
583  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
584  EndContainer(),
585  NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
586  NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
587  NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
588  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_ROAD_VEHICLES_FRAME, STR_NULL),
589  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_ROADSIDE_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_ROAD_VEHICLES_DROPDOWN_TOOLTIP), SetFill(1, 0),
590  EndContainer(),
591  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
592  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),
593  EndContainer(),
594  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
595  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP), SetFill(1, 0), SetPadding(0, 0, 3, 0),
597  NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
598  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
599  EndContainer(),
600  EndContainer(),
601  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_ZOOM_FRAME, STR_NULL),
602  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),
603  EndContainer(),
604  EndContainer(),
605 
606  NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
607  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_TOWN_NAMES_FRAME, STR_NULL),
608  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_TOWNNAME_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_STRING, STR_GAME_OPTIONS_TOWN_NAMES_DROPDOWN_TOOLTIP), SetFill(1, 0),
609  EndContainer(),
610  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
611  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),
612  EndContainer(),
613  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
614  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),
615  EndContainer(),
616  NWidget(NWID_SPACER), SetMinimalSize(0, 0), SetFill(0, 1),
617  EndContainer(),
618  EndContainer(),
619 
620  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
621  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
622  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
623  NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
624  EndContainer(),
625  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),
627  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),
628  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),
629  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),
630  EndContainer(),
631  EndContainer(),
632 
633  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
634  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
635  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
636  NWidget(NWID_SPACER), SetFill(1, 0),
637  EndContainer(),
638  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),
640  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),
641  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),
642  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),
643  EndContainer(),
644  EndContainer(),
645 
646  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
647  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
648  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
649  NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
650  EndContainer(),
651  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),
653  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),
654  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),
655  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),
656  EndContainer(),
657  EndContainer(),
658  EndContainer(),
659 };
660 
661 static WindowDesc _game_options_desc(
662  WDP_CENTER, "settings_game", 0, 0,
664  0,
665  _nested_game_options_widgets, lengthof(_nested_game_options_widgets)
666 );
667 
670 {
672  new GameOptionsWindow(&_game_options_desc);
673 }
674 
675 static int SETTING_HEIGHT = 11;
676 static const int LEVEL_WIDTH = 15;
677 
686 
687  SEF_LAST_FIELD = 0x04,
688  SEF_FILTERED = 0x08,
689 };
690 
699 };
701 
702 
706  bool type_hides;
709 };
710 
713  byte flags;
714  byte level;
715 
716  BaseSettingEntry() : flags(0), level(0) {}
717  virtual ~BaseSettingEntry() {}
718 
719  virtual void Init(byte level = 0);
720  virtual void FoldAll() {}
721  virtual void UnFoldAll() {}
722 
727  void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
728 
729  virtual uint Length() const = 0;
730  virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
731  virtual bool IsVisible(const BaseSettingEntry *item) const;
732  virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
733  virtual uint GetMaxHelpHeight(int maxw) { return 0; }
734 
739  bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
740 
741  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
742 
743  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;
744 
745 protected:
746  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
747 };
748 
751  const char *name;
753  uint index;
754 
755  SettingEntry(const char *name);
756 
757  virtual void Init(byte level = 0);
758  virtual uint Length() const;
759  virtual uint GetMaxHelpHeight(int maxw);
760  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
761 
762  void SetButtons(byte new_val);
763 
768  inline StringID GetHelpText() const
769  {
770  return this->setting->desc.str_help;
771  }
772 
773  void SetValueDParams(uint first_param, int32 value) const;
774 
775 protected:
776  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
777 
778 private:
780 };
781 
784  typedef std::vector<BaseSettingEntry*> EntryVector;
785  EntryVector entries;
786 
787  template<typename T>
788  T *Add(T *item)
789  {
790  this->entries.push_back(item);
791  return item;
792  }
793 
794  void Init(byte level = 0);
795  void FoldAll();
796  void UnFoldAll();
797 
798  uint Length() const;
799  void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
800  bool IsVisible(const BaseSettingEntry *item) const;
801  BaseSettingEntry *FindEntry(uint row, uint *cur_row);
802  uint GetMaxHelpHeight(int maxw);
803 
804  bool UpdateFilterState(SettingFilter &filter, bool force_visible);
805 
806  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;
807 };
808 
812  bool folded;
813 
815 
816  virtual void Init(byte level = 0);
817  virtual void FoldAll();
818  virtual void UnFoldAll();
819 
820  virtual uint Length() const;
821  virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
822  virtual bool IsVisible(const BaseSettingEntry *item) const;
823  virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
824  virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
825 
826  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
827 
828  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;
829 
830 protected:
831  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
832 };
833 
834 /* == BaseSettingEntry methods == */
835 
840 void BaseSettingEntry::Init(byte level)
841 {
842  this->level = level;
843 }
844 
852 {
853  if (this->IsFiltered()) return false;
854  if (this == item) return true;
855  return false;
856 }
857 
864 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
865 {
866  if (this->IsFiltered()) return NULL;
867  if (row_num == *cur_row) return this;
868  (*cur_row)++;
869  return NULL;
870 }
871 
901 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
902 {
903  if (this->IsFiltered()) return cur_row;
904  if (cur_row >= max_row) return cur_row;
905 
906  bool rtl = _current_text_dir == TD_RTL;
907  int offset = rtl ? -4 : 4;
908  int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
909 
910  int x = rtl ? right : left;
911  if (cur_row >= first_row) {
912  int colour = _colour_gradient[COLOUR_ORANGE][4];
913  y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
914 
915  /* Draw vertical for parent nesting levels */
916  for (uint lvl = 0; lvl < this->level; lvl++) {
917  if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
918  x += level_width;
919  }
920  /* draw own |- prefix */
921  int halfway_y = y + SETTING_HEIGHT / 2;
922  int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
923  GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
924  /* Small horizontal line from the last vertical line */
925  GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
926  x += level_width;
927 
928  this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
929  }
930  cur_row++;
931 
932  return cur_row;
933 }
934 
935 /* == SettingEntry methods == */
936 
941 SettingEntry::SettingEntry(const char *name)
942 {
943  this->name = name;
944  this->setting = NULL;
945  this->index = 0;
946 }
947 
952 void SettingEntry::Init(byte level)
953 {
954  BaseSettingEntry::Init(level);
955  this->setting = GetSettingFromName(this->name, &this->index);
956  assert(this->setting != NULL);
957 }
958 
964 void SettingEntry::SetButtons(byte new_val)
965 {
966  assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
967  this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
968 }
969 
972 {
973  return this->IsFiltered() ? 0 : 1;
974 }
975 
982 {
983  return GetStringHeight(this->GetHelpText(), maxw);
984 }
985 
992 {
993  /* There shall not be any restriction, i.e. all settings shall be visible. */
994  if (mode == RM_ALL) return true;
995 
996  GameSettings *settings_ptr = &GetGameSettings();
997  const SettingDesc *sd = this->setting;
998 
999  if (mode == RM_BASIC) return (this->setting->desc.cat & SC_BASIC_LIST) != 0;
1000  if (mode == RM_ADVANCED) return (this->setting->desc.cat & SC_ADVANCED_LIST) != 0;
1001 
1002  /* Read the current value. */
1003  const void *var = ResolveVariableAddress(settings_ptr, sd);
1004  int64 current_value = ReadValue(var, sd->save.conv);
1005 
1006  int64 filter_value;
1007 
1008  if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1009  /* This entry shall only be visible, if the value deviates from its default value. */
1010 
1011  /* Read the default value. */
1012  filter_value = ReadValue(&sd->desc.def, sd->save.conv);
1013  } else {
1014  assert(mode == RM_CHANGED_AGAINST_NEW);
1015  /* This entry shall only be visible, if the value deviates from
1016  * its value is used when starting a new game. */
1017 
1018  /* Make sure we're not comparing the new game settings against itself. */
1019  assert(settings_ptr != &_settings_newgame);
1020 
1021  /* Read the new game's value. */
1022  var = ResolveVariableAddress(&_settings_newgame, sd);
1023  filter_value = ReadValue(var, sd->save.conv);
1024  }
1025 
1026  return current_value != filter_value;
1027 }
1028 
1035 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1036 {
1037  CLRBITS(this->flags, SEF_FILTERED);
1038 
1039  bool visible = true;
1040 
1041  const SettingDesc *sd = this->setting;
1042  if (!force_visible && !filter.string.IsEmpty()) {
1043  /* Process the search text filter for this item. */
1044  filter.string.ResetState();
1045 
1046  const SettingDescBase *sdb = &sd->desc;
1047 
1048  SetDParam(0, STR_EMPTY);
1049  filter.string.AddLine(sdb->str);
1050  filter.string.AddLine(this->GetHelpText());
1051 
1052  visible = filter.string.GetState();
1053  }
1054 
1055  if (visible) {
1056  if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1057  filter.type_hides = true;
1058  visible = false;
1059  }
1060  if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1061  while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1062  visible = false;
1063  }
1064  }
1065 
1066  if (!visible) SETBITS(this->flags, SEF_FILTERED);
1067  return visible;
1068 }
1069 
1070 
1071 static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd)
1072 {
1073  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
1074  if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1075  return GetVariableAddress(&Company::Get(_local_company)->settings, &sd->save);
1076  } else {
1078  }
1079  } else {
1080  return GetVariableAddress(settings_ptr, &sd->save);
1081  }
1082 }
1083 
1089 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1090 {
1091  const SettingDescBase *sdb = &this->setting->desc;
1092  if (sdb->cmd == SDT_BOOLX) {
1093  SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1094  } else {
1095  if ((sdb->flags & SGF_MULTISTRING) != 0) {
1096  SetDParam(first_param++, sdb->str_val - sdb->min + value);
1097  } else if ((sdb->flags & SGF_DISPLAY_ABS) != 0) {
1098  SetDParam(first_param++, sdb->str_val + ((value >= 0) ? 1 : 0));
1099  value = abs(value);
1100  } else {
1101  SetDParam(first_param++, sdb->str_val + ((value == 0 && (sdb->flags & SGF_0ISDISABLED) != 0) ? 1 : 0));
1102  }
1103  SetDParam(first_param++, value);
1104  }
1105 }
1106 
1115 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1116 {
1117  const SettingDesc *sd = this->setting;
1118  const SettingDescBase *sdb = &sd->desc;
1119  const void *var = ResolveVariableAddress(settings_ptr, sd);
1120  int state = this->flags & SEF_BUTTONS_MASK;
1121 
1122  bool rtl = _current_text_dir == TD_RTL;
1123  uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1124  uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + 5);
1125  uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + 5 : 0);
1126  uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1127 
1128  /* We do not allow changes of some items when we are a client in a networkgame */
1129  bool editable = sd->IsEditable();
1130 
1131  SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1132  int32 value = (int32)ReadValue(var, sd->save.conv);
1133  if (sdb->cmd == SDT_BOOLX) {
1134  /* Draw checkbox for boolean-value either on/off */
1135  DrawBoolButton(buttons_left, button_y, value != 0, editable);
1136  } else if ((sdb->flags & SGF_MULTISTRING) != 0) {
1137  /* Draw [v] button for settings of an enum-type */
1138  DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1139  } else {
1140  /* Draw [<][>] boxes for settings of an integer-type */
1141  DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1142  editable && value != (sdb->flags & SGF_0ISDISABLED ? 0 : sdb->min), editable && (uint32)value != sdb->max);
1143  }
1144  this->SetValueDParams(1, value);
1145  DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sdb->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1146 }
1147 
1148 /* == SettingsContainer methods == */
1149 
1154 void SettingsContainer::Init(byte level)
1155 {
1156  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1157  (*it)->Init(level);
1158  }
1159 }
1160 
1163 {
1164  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1165  (*it)->FoldAll();
1166  }
1167 }
1168 
1171 {
1172  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1173  (*it)->UnFoldAll();
1174  }
1175 }
1176 
1182 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1183 {
1184  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1185  (*it)->GetFoldingState(all_folded, all_unfolded);
1186  }
1187 }
1188 
1195 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1196 {
1197  bool visible = false;
1198  bool first_visible = true;
1199  for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1200  visible |= (*it)->UpdateFilterState(filter, force_visible);
1201  (*it)->SetLastField(first_visible);
1202  if (visible && first_visible) first_visible = false;
1203  }
1204  return visible;
1205 }
1206 
1207 
1215 {
1216  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1217  if ((*it)->IsVisible(item)) return true;
1218  }
1219  return false;
1220 }
1221 
1224 {
1225  uint length = 0;
1226  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1227  length += (*it)->Length();
1228  }
1229  return length;
1230 }
1231 
1238 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1239 {
1240  BaseSettingEntry *pe = NULL;
1241  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1242  pe = (*it)->FindEntry(row_num, cur_row);
1243  if (pe != NULL) {
1244  break;
1245  }
1246  }
1247  return pe;
1248 }
1249 
1256 {
1257  uint biggest = 0;
1258  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1259  biggest = max(biggest, (*it)->GetMaxHelpHeight(maxw));
1260  }
1261  return biggest;
1262 }
1263 
1264 
1279 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
1280 {
1281  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1282  cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1283  if (cur_row >= max_row) {
1284  break;
1285  }
1286  }
1287  return cur_row;
1288 }
1289 
1290 /* == SettingsPage methods == */
1291 
1297 {
1298  this->title = title;
1299  this->folded = true;
1300 }
1301 
1306 void SettingsPage::Init(byte level)
1307 {
1308  BaseSettingEntry::Init(level);
1309  SettingsContainer::Init(level + 1);
1310 }
1311 
1314 {
1315  if (this->IsFiltered()) return;
1316  this->folded = true;
1317 
1319 }
1320 
1323 {
1324  if (this->IsFiltered()) return;
1325  this->folded = false;
1326 
1328 }
1329 
1335 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1336 {
1337  if (this->IsFiltered()) return;
1338 
1339  if (this->folded) {
1340  all_unfolded = false;
1341  } else {
1342  all_folded = false;
1343  }
1344 
1345  SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1346 }
1347 
1354 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1355 {
1356  if (!force_visible && !filter.string.IsEmpty()) {
1357  filter.string.ResetState();
1358  filter.string.AddLine(this->title);
1359  force_visible = filter.string.GetState();
1360  }
1361 
1362  bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1363  if (visible) {
1364  CLRBITS(this->flags, SEF_FILTERED);
1365  } else {
1366  SETBITS(this->flags, SEF_FILTERED);
1367  }
1368  return visible;
1369 }
1370 
1378 {
1379  if (this->IsFiltered()) return false;
1380  if (this == item) return true;
1381  if (this->folded) return false;
1382 
1383  return SettingsContainer::IsVisible(item);
1384 }
1385 
1388 {
1389  if (this->IsFiltered()) return 0;
1390  if (this->folded) return 1; // Only displaying the title
1391 
1392  return 1 + SettingsContainer::Length();
1393 }
1394 
1401 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1402 {
1403  if (this->IsFiltered()) return NULL;
1404  if (row_num == *cur_row) return this;
1405  (*cur_row)++;
1406  if (this->folded) return NULL;
1407 
1408  return SettingsContainer::FindEntry(row_num, cur_row);
1409 }
1410 
1425 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
1426 {
1427  if (this->IsFiltered()) return cur_row;
1428  if (cur_row >= max_row) return cur_row;
1429 
1430  cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1431 
1432  if (!this->folded) {
1433  if (this->flags & SEF_LAST_FIELD) {
1434  assert(this->level < 8 * sizeof(parent_last));
1435  SetBit(parent_last, this->level); // Add own last-field state
1436  }
1437 
1438  cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1439  }
1440 
1441  return cur_row;
1442 }
1443 
1452 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1453 {
1454  bool rtl = _current_text_dir == TD_RTL;
1455  DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1456  DrawString(rtl ? left : left + _circle_size.width + 2, rtl ? right - _circle_size.width - 2 : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1457 }
1458 
1461 {
1462  static SettingsContainer *main = NULL;
1463 
1464  if (main == NULL)
1465  {
1466  /* Build up the dynamic settings-array only once per OpenTTD session */
1467  main = new SettingsContainer();
1468 
1469  SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1470  {
1471  localisation->Add(new SettingEntry("locale.units_velocity"));
1472  localisation->Add(new SettingEntry("locale.units_power"));
1473  localisation->Add(new SettingEntry("locale.units_weight"));
1474  localisation->Add(new SettingEntry("locale.units_volume"));
1475  localisation->Add(new SettingEntry("locale.units_force"));
1476  localisation->Add(new SettingEntry("locale.units_height"));
1477  localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1478  }
1479 
1480  SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1481  {
1482  graphics->Add(new SettingEntry("gui.zoom_min"));
1483  graphics->Add(new SettingEntry("gui.zoom_max"));
1484  graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1485  graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1486  }
1487 
1488  SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1489  {
1490  sound->Add(new SettingEntry("sound.click_beep"));
1491  sound->Add(new SettingEntry("sound.confirm"));
1492  sound->Add(new SettingEntry("sound.news_ticker"));
1493  sound->Add(new SettingEntry("sound.news_full"));
1494  sound->Add(new SettingEntry("sound.new_year"));
1495  sound->Add(new SettingEntry("sound.disaster"));
1496  sound->Add(new SettingEntry("sound.vehicle"));
1497  sound->Add(new SettingEntry("sound.ambient"));
1498  }
1499 
1500  SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1501  {
1502  SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1503  {
1504  general->Add(new SettingEntry("gui.osk_activation"));
1505  general->Add(new SettingEntry("gui.hover_delay_ms"));
1506  general->Add(new SettingEntry("gui.errmsg_duration"));
1507  general->Add(new SettingEntry("gui.window_snap_radius"));
1508  general->Add(new SettingEntry("gui.window_soft_limit"));
1509  }
1510 
1511  SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1512  {
1513  viewports->Add(new SettingEntry("gui.auto_scrolling"));
1514  viewports->Add(new SettingEntry("gui.reverse_scroll"));
1515  viewports->Add(new SettingEntry("gui.smooth_scroll"));
1516  viewports->Add(new SettingEntry("gui.left_mouse_btn_scrolling"));
1517  /* While the horizontal scrollwheel scrolling is written as general code, only
1518  * the cocoa (OSX) driver generates input for it.
1519  * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1520  viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1521  viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1522 #ifdef __APPLE__
1523  /* We might need to emulate a right mouse button on mac */
1524  viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1525 #endif
1526  viewports->Add(new SettingEntry("gui.population_in_label"));
1527  viewports->Add(new SettingEntry("gui.liveries"));
1528  viewports->Add(new SettingEntry("construction.train_signal_side"));
1529  viewports->Add(new SettingEntry("gui.measure_tooltip"));
1530  viewports->Add(new SettingEntry("gui.loading_indicators"));
1531  viewports->Add(new SettingEntry("gui.show_track_reservation"));
1532  }
1533 
1534  SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1535  {
1536  construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1537  construction->Add(new SettingEntry("gui.enable_signal_gui"));
1538  construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1539  construction->Add(new SettingEntry("gui.quick_goto"));
1540  construction->Add(new SettingEntry("gui.default_rail_type"));
1541  construction->Add(new SettingEntry("gui.disable_unsuitable_building"));
1542  }
1543 
1544  interface->Add(new SettingEntry("gui.autosave"));
1545  interface->Add(new SettingEntry("gui.toolbar_pos"));
1546  interface->Add(new SettingEntry("gui.statusbar_pos"));
1547  interface->Add(new SettingEntry("gui.prefer_teamchat"));
1548  interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1549  interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1550  interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1551  interface->Add(new SettingEntry("gui.expenses_layout"));
1552  }
1553 
1554  SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1555  {
1556  advisors->Add(new SettingEntry("gui.coloured_news_year"));
1557  advisors->Add(new SettingEntry("news_display.general"));
1558  advisors->Add(new SettingEntry("news_display.new_vehicles"));
1559  advisors->Add(new SettingEntry("news_display.accident"));
1560  advisors->Add(new SettingEntry("news_display.company_info"));
1561  advisors->Add(new SettingEntry("news_display.acceptance"));
1562  advisors->Add(new SettingEntry("news_display.arrival_player"));
1563  advisors->Add(new SettingEntry("news_display.arrival_other"));
1564  advisors->Add(new SettingEntry("news_display.advice"));
1565  advisors->Add(new SettingEntry("gui.order_review_system"));
1566  advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1567  advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1568  advisors->Add(new SettingEntry("gui.show_finances"));
1569  advisors->Add(new SettingEntry("news_display.economy"));
1570  advisors->Add(new SettingEntry("news_display.subsidies"));
1571  advisors->Add(new SettingEntry("news_display.open"));
1572  advisors->Add(new SettingEntry("news_display.close"));
1573  advisors->Add(new SettingEntry("news_display.production_player"));
1574  advisors->Add(new SettingEntry("news_display.production_other"));
1575  advisors->Add(new SettingEntry("news_display.production_nobody"));
1576  }
1577 
1578  SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1579  {
1580  company->Add(new SettingEntry("gui.semaphore_build_before"));
1581  company->Add(new SettingEntry("gui.default_signal_type"));
1582  company->Add(new SettingEntry("gui.cycle_signal_types"));
1583  company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1584  company->Add(new SettingEntry("gui.new_nonstop"));
1585  company->Add(new SettingEntry("gui.stop_location"));
1586  company->Add(new SettingEntry("company.engine_renew"));
1587  company->Add(new SettingEntry("company.engine_renew_months"));
1588  company->Add(new SettingEntry("company.engine_renew_money"));
1589  company->Add(new SettingEntry("vehicle.servint_ispercent"));
1590  company->Add(new SettingEntry("vehicle.servint_trains"));
1591  company->Add(new SettingEntry("vehicle.servint_roadveh"));
1592  company->Add(new SettingEntry("vehicle.servint_ships"));
1593  company->Add(new SettingEntry("vehicle.servint_aircraft"));
1594  }
1595 
1596  SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1597  {
1598  accounting->Add(new SettingEntry("economy.inflation"));
1599  accounting->Add(new SettingEntry("difficulty.initial_interest"));
1600  accounting->Add(new SettingEntry("difficulty.max_loan"));
1601  accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1602  accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1603  accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1604  accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1605  accounting->Add(new SettingEntry("difficulty.construction_cost"));
1606  }
1607 
1608  SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1609  {
1610  SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1611  {
1612  physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1613  physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1614  physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1615  physics->Add(new SettingEntry("vehicle.freight_trains"));
1616  physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1617  physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1618  physics->Add(new SettingEntry("vehicle.smoke_amount"));
1619  physics->Add(new SettingEntry("vehicle.plane_speed"));
1620  }
1621 
1622  SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1623  {
1624  routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1625  routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1626  routing->Add(new SettingEntry("pf.reverse_at_signals"));
1627  routing->Add(new SettingEntry("pf.forbid_90_deg"));
1628  routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1629  routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1630  }
1631 
1632  vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1633  vehicles->Add(new SettingEntry("order.serviceathelipad"));
1634  }
1635 
1636  SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1637  {
1638  limitations->Add(new SettingEntry("construction.command_pause_level"));
1639  limitations->Add(new SettingEntry("construction.autoslope"));
1640  limitations->Add(new SettingEntry("construction.extra_dynamite"));
1641  limitations->Add(new SettingEntry("construction.max_heightlevel"));
1642  limitations->Add(new SettingEntry("construction.max_bridge_length"));
1643  limitations->Add(new SettingEntry("construction.max_bridge_height"));
1644  limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1645  limitations->Add(new SettingEntry("station.never_expire_airports"));
1646  limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1647  limitations->Add(new SettingEntry("vehicle.max_trains"));
1648  limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1649  limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1650  limitations->Add(new SettingEntry("vehicle.max_ships"));
1651  limitations->Add(new SettingEntry("vehicle.max_train_length"));
1652  limitations->Add(new SettingEntry("station.station_spread"));
1653  limitations->Add(new SettingEntry("station.distant_join_stations"));
1654  limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1655  limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1656  limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1657  }
1658 
1659  SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1660  {
1661  disasters->Add(new SettingEntry("difficulty.disasters"));
1662  disasters->Add(new SettingEntry("difficulty.economy"));
1663  disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1664  disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1665  }
1666 
1667  SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1668  {
1669  genworld->Add(new SettingEntry("game_creation.landscape"));
1670  genworld->Add(new SettingEntry("game_creation.land_generator"));
1671  genworld->Add(new SettingEntry("difficulty.terrain_type"));
1672  genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1673  genworld->Add(new SettingEntry("game_creation.variety"));
1674  genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1675  genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1676  genworld->Add(new SettingEntry("game_creation.tree_placer"));
1677  genworld->Add(new SettingEntry("vehicle.road_side"));
1678  genworld->Add(new SettingEntry("economy.larger_towns"));
1679  genworld->Add(new SettingEntry("economy.initial_city_size"));
1680  genworld->Add(new SettingEntry("economy.town_layout"));
1681  genworld->Add(new SettingEntry("difficulty.industry_density"));
1682  genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1683  }
1684 
1685  SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1686  {
1687  SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1688  {
1689  authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1690  authorities->Add(new SettingEntry("economy.bribe"));
1691  authorities->Add(new SettingEntry("economy.exclusive_rights"));
1692  authorities->Add(new SettingEntry("economy.fund_roads"));
1693  authorities->Add(new SettingEntry("economy.fund_buildings"));
1694  authorities->Add(new SettingEntry("economy.station_noise_level"));
1695  }
1696 
1697  SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1698  {
1699  towns->Add(new SettingEntry("economy.town_growth_rate"));
1700  towns->Add(new SettingEntry("economy.allow_town_roads"));
1701  towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1702  towns->Add(new SettingEntry("economy.found_town"));
1703  }
1704 
1705  SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1706  {
1707  industries->Add(new SettingEntry("construction.raw_industry_construction"));
1708  industries->Add(new SettingEntry("construction.industry_platform"));
1709  industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1710  industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1711  industries->Add(new SettingEntry("economy.smooth_economy"));
1712  }
1713 
1714  SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1715  {
1716  cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1717  cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1718  cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1719  cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1720  cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1721  cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1722  cdist->Add(new SettingEntry("linkgraph.accuracy"));
1723  cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1724  cdist->Add(new SettingEntry("linkgraph.demand_size"));
1725  cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1726  }
1727 
1728  environment->Add(new SettingEntry("station.modified_catchment"));
1729  environment->Add(new SettingEntry("construction.extra_tree_placement"));
1730  }
1731 
1732  SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1733  {
1734  SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1735  {
1736  npc->Add(new SettingEntry("script.settings_profile"));
1737  npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1738  npc->Add(new SettingEntry("difficulty.competitor_speed"));
1739  npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1740  npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1741  npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1742  npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1743  npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1744  }
1745 
1746  ai->Add(new SettingEntry("economy.give_money"));
1747  ai->Add(new SettingEntry("economy.allow_shares"));
1748  }
1749 
1750  main->Init();
1751  }
1752  return *main;
1753 }
1754 
1755 static const StringID _game_settings_restrict_dropdown[] = {
1756  STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1757  STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1758  STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1759  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1760  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1761 };
1762 assert_compile(lengthof(_game_settings_restrict_dropdown) == RM_END);
1763 
1770 };
1771 
1774  static const int SETTINGTREE_LEFT_OFFSET = 5;
1775  static const int SETTINGTREE_RIGHT_OFFSET = 5;
1776  static const int SETTINGTREE_TOP_OFFSET = 5;
1777  static const int SETTINGTREE_BOTTOM_OFFSET = 5;
1778 
1780 
1786 
1792 
1793  Scrollbar *vscroll;
1794 
1796  {
1797  this->warn_missing = WHR_NONE;
1798  this->warn_lines = 0;
1800  this->filter.min_cat = RM_ALL;
1801  this->filter.type = ST_ALL;
1802  this->filter.type_hides = false;
1803  this->settings_ptr = &GetGameSettings();
1804 
1805  _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1806  GetSettingsTree().FoldAll(); // Close all sub-pages
1807 
1808  this->valuewindow_entry = NULL; // No setting entry for which a entry window is opened
1809  this->clicked_entry = NULL; // No numeric setting buttons are depressed
1810  this->last_clicked = NULL;
1811  this->valuedropdown_entry = NULL;
1812  this->closing_dropdown = false;
1813  this->manually_changed_folding = false;
1814 
1815  this->CreateNestedTree();
1816  this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1818 
1819  this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1822 
1823  this->InvalidateData();
1824  }
1825 
1826  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1827  {
1828  switch (widget) {
1829  case WID_GS_OPTIONSPANEL:
1830  resize->height = SETTING_HEIGHT = max(max<int>(_circle_size.height, SETTING_BUTTON_HEIGHT), FONT_HEIGHT_NORMAL) + 1;
1831  resize->width = 1;
1832 
1833  size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1834  break;
1835 
1836  case WID_GS_HELP_TEXT: {
1837  static const StringID setting_types[] = {
1838  STR_CONFIG_SETTING_TYPE_CLIENT,
1839  STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1840  STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1841  };
1842  for (uint i = 0; i < lengthof(setting_types); i++) {
1843  SetDParam(0, setting_types[i]);
1844  size->width = max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1845  }
1846  size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1847  max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1848  break;
1849  }
1850 
1852  case WID_GS_RESTRICT_TYPE:
1853  size->width = max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1854  break;
1855 
1856  default:
1857  break;
1858  }
1859  }
1860 
1861  virtual void OnPaint()
1862  {
1863  if (this->closing_dropdown) {
1864  this->closing_dropdown = false;
1865  assert(this->valuedropdown_entry != NULL);
1866  this->valuedropdown_entry->SetButtons(0);
1867  this->valuedropdown_entry = NULL;
1868  }
1869 
1870  /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1871  const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1872  StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1873  int new_warn_lines;
1874  if (this->warn_missing == WHR_NONE) {
1875  new_warn_lines = 0;
1876  } else {
1877  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1878  new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1879  }
1880  if (this->warn_lines != new_warn_lines) {
1881  this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
1882  this->warn_lines = new_warn_lines;
1883  }
1884 
1885  this->DrawWidgets();
1886 
1887  /* Draw the 'some search results are hidden' notice. */
1888  if (this->warn_missing != WHR_NONE) {
1889  const int left = panel->pos_x;
1890  const int right = left + panel->current_x - 1;
1891  const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
1892  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1893  if (this->warn_lines == 1) {
1894  /* If the warning fits at one line, center it. */
1895  DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1896  } else {
1897  DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1898  }
1899  }
1900  }
1901 
1902  virtual void SetStringParameters(int widget) const
1903  {
1904  switch (widget) {
1906  SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
1907  break;
1908 
1909  case WID_GS_TYPE_DROPDOWN:
1910  switch (this->filter.type) {
1911  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
1912  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
1913  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
1914  default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
1915  }
1916  break;
1917  }
1918  }
1919 
1920  DropDownList *BuildDropDownList(int widget) const
1921  {
1922  DropDownList *list = NULL;
1923  switch (widget) {
1925  list = new DropDownList();
1926 
1927  for (int mode = 0; mode != RM_END; mode++) {
1928  /* If we are in adv. settings screen for the new game's settings,
1929  * we don't want to allow comparing with new game's settings. */
1930  bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
1931 
1932  *list->Append() = new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled);
1933  }
1934  break;
1935 
1936  case WID_GS_TYPE_DROPDOWN:
1937  list = new DropDownList();
1938  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false);
1939  *list->Append() = new DropDownListStringItem(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME, ST_GAME, false);
1940  *list->Append() = new DropDownListStringItem(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME, ST_COMPANY, false);
1941  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false);
1942  break;
1943  }
1944  return list;
1945  }
1946 
1947  virtual void DrawWidget(const Rect &r, int widget) const
1948  {
1949  switch (widget) {
1950  case WID_GS_OPTIONSPANEL: {
1951  int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
1952  uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
1953  int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
1954  this->vscroll->GetPosition(), last_row, this->last_clicked);
1955  if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
1956  break;
1957  }
1958 
1959  case WID_GS_HELP_TEXT:
1960  if (this->last_clicked != NULL) {
1961  const SettingDesc *sd = this->last_clicked->setting;
1962 
1963  int y = r.top;
1964  switch (sd->GetType()) {
1965  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
1966  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
1967  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
1968  default: NOT_REACHED();
1969  }
1970  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
1971  y += FONT_HEIGHT_NORMAL;
1972 
1973  int32 default_value = ReadValue(&sd->desc.def, sd->save.conv);
1974  this->last_clicked->SetValueDParams(0, default_value);
1975  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
1977 
1978  DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
1979  }
1980  break;
1981 
1982  default:
1983  break;
1984  }
1985  }
1986 
1992  {
1993  if (this->last_clicked != pe) this->SetDirty();
1994  this->last_clicked = pe;
1995  }
1996 
1997  virtual void OnClick(Point pt, int widget, int click_count)
1998  {
1999  switch (widget) {
2000  case WID_GS_EXPAND_ALL:
2001  this->manually_changed_folding = true;
2003  this->InvalidateData();
2004  break;
2005 
2006  case WID_GS_COLLAPSE_ALL:
2007  this->manually_changed_folding = true;
2009  this->InvalidateData();
2010  break;
2011 
2012  case WID_GS_RESTRICT_DROPDOWN: {
2013  DropDownList *list = this->BuildDropDownList(widget);
2014  if (list != NULL) {
2015  ShowDropDownList(this, list, this->filter.mode, widget);
2016  }
2017  break;
2018  }
2019 
2020  case WID_GS_TYPE_DROPDOWN: {
2021  DropDownList *list = this->BuildDropDownList(widget);
2022  if (list != NULL) {
2023  ShowDropDownList(this, list, this->filter.type, widget);
2024  }
2025  break;
2026  }
2027  }
2028 
2029  if (widget != WID_GS_OPTIONSPANEL) return;
2030 
2031  uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2032  if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2033  btn -= this->warn_lines;
2034 
2035  uint cur_row = 0;
2037 
2038  if (clicked_entry == NULL) return; // Clicked below the last setting of the page
2039 
2040  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
2041  if (x < 0) return; // Clicked left of the entry
2042 
2043  SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2044  if (clicked_page != NULL) {
2045  this->SetDisplayedHelpText(NULL);
2046  clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2047 
2048  this->manually_changed_folding = true;
2049 
2050  this->InvalidateData();
2051  return;
2052  }
2053 
2054  SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2055  assert(pe != NULL);
2056  const SettingDesc *sd = pe->setting;
2057 
2058  /* return if action is only active in network, or only settable by server */
2059  if (!sd->IsEditable()) {
2060  this->SetDisplayedHelpText(pe);
2061  return;
2062  }
2063 
2064  const void *var = ResolveVariableAddress(settings_ptr, sd);
2065  int32 value = (int32)ReadValue(var, sd->save.conv);
2066 
2067  /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2068  if (x < SETTING_BUTTON_WIDTH && (sd->desc.flags & SGF_MULTISTRING)) {
2069  const SettingDescBase *sdb = &sd->desc;
2070  this->SetDisplayedHelpText(pe);
2071 
2072  if (this->valuedropdown_entry == pe) {
2073  /* unclick the dropdown */
2074  HideDropDownMenu(this);
2075  this->closing_dropdown = false;
2076  this->valuedropdown_entry->SetButtons(0);
2077  this->valuedropdown_entry = NULL;
2078  } else {
2079  if (this->valuedropdown_entry != NULL) this->valuedropdown_entry->SetButtons(0);
2080  this->closing_dropdown = false;
2081 
2082  const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2083  int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2084 
2085  Rect wi_rect;
2086  wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2087  wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2088  wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2089  wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2090 
2091  /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2092  if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2093  this->valuedropdown_entry = pe;
2095 
2096  DropDownList *list = new DropDownList();
2097  for (int i = sdb->min; i <= (int)sdb->max; i++) {
2098  *list->Append() = new DropDownListStringItem(sdb->str_val + i - sdb->min, i, false);
2099  }
2100 
2101  ShowDropDownListAt(this, list, value, -1, wi_rect, COLOUR_ORANGE, true);
2102  }
2103  }
2104  this->SetDirty();
2105  } else if (x < SETTING_BUTTON_WIDTH) {
2106  this->SetDisplayedHelpText(pe);
2107  const SettingDescBase *sdb = &sd->desc;
2108  int32 oldvalue = value;
2109 
2110  switch (sdb->cmd) {
2111  case SDT_BOOLX: value ^= 1; break;
2112  case SDT_ONEOFMANY:
2113  case SDT_NUMX: {
2114  /* Add a dynamic step-size to the scroller. In a maximum of
2115  * 50-steps you should be able to get from min to max,
2116  * unless specified otherwise in the 'interval' variable
2117  * of the current setting. */
2118  uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
2119  if (step == 0) step = 1;
2120 
2121  /* don't allow too fast scrolling */
2122  if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2123  _left_button_clicked = false;
2124  return;
2125  }
2126 
2127  /* Increase or decrease the value and clamp it to extremes */
2128  if (x >= SETTING_BUTTON_WIDTH / 2) {
2129  value += step;
2130  if (sdb->min < 0) {
2131  assert((int32)sdb->max >= 0);
2132  if (value > (int32)sdb->max) value = (int32)sdb->max;
2133  } else {
2134  if ((uint32)value > sdb->max) value = (int32)sdb->max;
2135  }
2136  if (value < sdb->min) value = sdb->min; // skip between "disabled" and minimum
2137  } else {
2138  value -= step;
2139  if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
2140  }
2141 
2142  /* Set up scroller timeout for numeric values */
2143  if (value != oldvalue) {
2144  if (this->clicked_entry != NULL) { // Release previous buttons if any
2145  this->clicked_entry->SetButtons(0);
2146  }
2147  this->clicked_entry = pe;
2148  this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2149  this->SetTimeout();
2150  _left_button_clicked = false;
2151  }
2152  break;
2153  }
2154 
2155  default: NOT_REACHED();
2156  }
2157 
2158  if (value != oldvalue) {
2159  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2160  SetCompanySetting(pe->index, value);
2161  } else {
2162  SetSettingValue(pe->index, value);
2163  }
2164  this->SetDirty();
2165  }
2166  } else {
2167  /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2168  if (this->last_clicked == pe && sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
2169  /* Show the correct currency-translated value */
2170  if (sd->desc.flags & SGF_CURRENCY) value *= _currency->rate;
2171 
2172  this->valuewindow_entry = pe;
2173  SetDParam(0, value);
2174  ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2175  }
2176  this->SetDisplayedHelpText(pe);
2177  }
2178  }
2179 
2180  virtual void OnTimeout()
2181  {
2182  if (this->clicked_entry != NULL) { // On timeout, release any depressed buttons
2183  this->clicked_entry->SetButtons(0);
2184  this->clicked_entry = NULL;
2185  this->SetDirty();
2186  }
2187  }
2188 
2189  virtual void OnQueryTextFinished(char *str)
2190  {
2191  /* The user pressed cancel */
2192  if (str == NULL) return;
2193 
2194  assert(this->valuewindow_entry != NULL);
2195  const SettingDesc *sd = this->valuewindow_entry->setting;
2196 
2197  int32 value;
2198  if (!StrEmpty(str)) {
2199  value = atoi(str);
2200 
2201  /* Save the correct currency-translated value */
2202  if (sd->desc.flags & SGF_CURRENCY) value /= _currency->rate;
2203  } else {
2204  value = (int32)(size_t)sd->desc.def;
2205  }
2206 
2207  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2208  SetCompanySetting(this->valuewindow_entry->index, value);
2209  } else {
2210  SetSettingValue(this->valuewindow_entry->index, value);
2211  }
2212  this->SetDirty();
2213  }
2214 
2215  virtual void OnDropdownSelect(int widget, int index)
2216  {
2217  switch (widget) {
2219  this->filter.mode = (RestrictionMode)index;
2220  if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2221  this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2222 
2223  if (!this->manually_changed_folding) {
2224  /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2225  GetSettingsTree().UpdateFilterState(this->filter, false);
2227  }
2228  } else {
2229  /* Non-'changes' filter. Save as default. */
2231  }
2232  this->InvalidateData();
2233  break;
2234 
2235  case WID_GS_TYPE_DROPDOWN:
2236  this->filter.type = (SettingType)index;
2237  this->InvalidateData();
2238  break;
2239 
2240  default:
2241  if (widget < 0) {
2242  /* Deal with drop down boxes on the panel. */
2243  assert(this->valuedropdown_entry != NULL);
2244  const SettingDesc *sd = this->valuedropdown_entry->setting;
2245  assert(sd->desc.flags & SGF_MULTISTRING);
2246 
2247  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2249  } else {
2250  SetSettingValue(this->valuedropdown_entry->index, index);
2251  }
2252 
2253  this->SetDirty();
2254  }
2255  break;
2256  }
2257  }
2258 
2259  virtual void OnDropdownClose(Point pt, int widget, int index, bool instant_close)
2260  {
2261  if (widget >= 0) {
2262  /* Normally the default implementation of OnDropdownClose() takes care of
2263  * a few things. We want that behaviour here too, but only for
2264  * "normal" dropdown boxes. The special dropdown boxes added for every
2265  * setting that needs one can't have this call. */
2266  Window::OnDropdownClose(pt, widget, index, instant_close);
2267  } else {
2268  /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2269  * the same dropdown button was clicked again, and then not open the dropdown again.
2270  * So, we only remember that it was closed, and process it on the next OnPaint, which is
2271  * after OnClick. */
2272  assert(this->valuedropdown_entry != NULL);
2273  this->closing_dropdown = true;
2274  this->SetDirty();
2275  }
2276  }
2277 
2278  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2279  {
2280  if (!gui_scope) return;
2281 
2282  /* Update which settings are to be visible. */
2283  RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2284  this->filter.min_cat = min_level;
2285  this->filter.type_hides = false;
2286  GetSettingsTree().UpdateFilterState(this->filter, false);
2287 
2288  if (this->filter.string.IsEmpty()) {
2289  this->warn_missing = WHR_NONE;
2290  } else if (min_level < this->filter.min_cat) {
2292  } else {
2293  this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2294  }
2295  this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2296 
2297  if (this->last_clicked != NULL && !GetSettingsTree().IsVisible(this->last_clicked)) {
2298  this->SetDisplayedHelpText(NULL);
2299  }
2300 
2301  bool all_folded = true;
2302  bool all_unfolded = true;
2303  GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2304  this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2305  this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2306  }
2307 
2308  virtual void OnEditboxChanged(int wid)
2309  {
2310  if (wid == WID_GS_FILTER) {
2311  this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2312  if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2313  /* User never expanded/collapsed single pages and entered a filter term.
2314  * Expand everything, to save weird expand clicks, */
2316  }
2317  this->InvalidateData();
2318  }
2319  }
2320 
2321  virtual void OnResize()
2322  {
2324  }
2325 };
2326 
2328 
2329 static const NWidgetPart _nested_settings_selection_widgets[] = {
2331  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2332  NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2333  NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2334  EndContainer(),
2335  NWidget(WWT_PANEL, COLOUR_MAUVE),
2338  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2339  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),
2340  EndContainer(),
2342  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2343  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),
2344  EndContainer(),
2345  EndContainer(),
2348  NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2349  NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2350  SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2351  EndContainer(),
2352  EndContainer(),
2355  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2356  EndContainer(),
2357  NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2358  NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2360  EndContainer(),
2362  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2363  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2364  NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2365  EndContainer(),
2366  NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2367  EndContainer(),
2368 };
2369 
2370 static WindowDesc _settings_selection_desc(
2371  WDP_CENTER, "settings", 510, 450,
2373  0,
2374  _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2375 );
2376 
2379 {
2381  new GameSettingsWindow(&_settings_selection_desc);
2382 }
2383 
2384 
2394 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2395 {
2396  int colour = _colour_gradient[button_colour][2];
2397  Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2398 
2399  DrawFrameRect(x, y, x + dim.width - 1, y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2400  DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2401  DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2402  DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2403 
2404  /* Grey out the buttons that aren't clickable */
2405  bool rtl = _current_text_dir == TD_RTL;
2406  if (rtl ? !clickable_right : !clickable_left) {
2407  GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2408  }
2409  if (rtl ? !clickable_left : !clickable_right) {
2410  GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2411  }
2412 }
2413 
2422 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2423 {
2424  int colour = _colour_gradient[button_colour][2];
2425 
2426  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2427  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2428 
2429  if (!clickable) {
2430  GfxFillRect(x + 1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2431  }
2432 }
2433 
2441 void DrawBoolButton(int x, int y, bool state, bool clickable)
2442 {
2443  static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2444  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2445 }
2446 
2448  int query_widget;
2449 
2450  CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2451  {
2452  this->InitNested();
2453 
2454  SetButtonState();
2455  }
2456 
2457  void SetButtonState()
2458  {
2459  this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2460  this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2461  this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2462  this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2463  }
2464 
2465  virtual void SetStringParameters(int widget) const
2466  {
2467  switch (widget) {
2468  case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2469  case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2470  case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2471  case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2472  case WID_CC_YEAR:
2473  SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2474  SetDParam(1, _custom_currency.to_euro);
2475  break;
2476 
2477  case WID_CC_PREVIEW:
2478  SetDParam(0, 10000);
2479  break;
2480  }
2481  }
2482 
2483  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2484  {
2485  switch (widget) {
2486  /* Set the appropriate width for the edit 'buttons' */
2487  case WID_CC_SEPARATOR_EDIT:
2488  case WID_CC_PREFIX_EDIT:
2489  case WID_CC_SUFFIX_EDIT:
2490  size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2491  break;
2492 
2493  /* Make sure the window is wide enough for the widest exchange rate */
2494  case WID_CC_RATE:
2495  SetDParam(0, 1);
2496  SetDParam(1, INT32_MAX);
2497  *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2498  break;
2499  }
2500  }
2501 
2502  virtual void OnClick(Point pt, int widget, int click_count)
2503  {
2504  int line = 0;
2505  int len = 0;
2506  StringID str = 0;
2507  CharSetFilter afilter = CS_ALPHANUMERAL;
2508 
2509  switch (widget) {
2510  case WID_CC_RATE_DOWN:
2511  if (_custom_currency.rate > 1) _custom_currency.rate--;
2512  if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2514  break;
2515 
2516  case WID_CC_RATE_UP:
2517  if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2518  if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2520  break;
2521 
2522  case WID_CC_RATE:
2523  SetDParam(0, _custom_currency.rate);
2524  str = STR_JUST_INT;
2525  len = 5;
2526  line = WID_CC_RATE;
2527  afilter = CS_NUMERAL;
2528  break;
2529 
2530  case WID_CC_SEPARATOR_EDIT:
2531  case WID_CC_SEPARATOR:
2532  SetDParamStr(0, _custom_currency.separator);
2533  str = STR_JUST_RAW_STRING;
2534  len = 1;
2535  line = WID_CC_SEPARATOR;
2536  break;
2537 
2538  case WID_CC_PREFIX_EDIT:
2539  case WID_CC_PREFIX:
2540  SetDParamStr(0, _custom_currency.prefix);
2541  str = STR_JUST_RAW_STRING;
2542  len = 12;
2543  line = WID_CC_PREFIX;
2544  break;
2545 
2546  case WID_CC_SUFFIX_EDIT:
2547  case WID_CC_SUFFIX:
2548  SetDParamStr(0, _custom_currency.suffix);
2549  str = STR_JUST_RAW_STRING;
2550  len = 12;
2551  line = WID_CC_SUFFIX;
2552  break;
2553 
2554  case WID_CC_YEAR_DOWN:
2555  _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2556  if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2558  break;
2559 
2560  case WID_CC_YEAR_UP:
2561  _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2562  if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2564  break;
2565 
2566  case WID_CC_YEAR:
2567  SetDParam(0, _custom_currency.to_euro);
2568  str = STR_JUST_INT;
2569  len = 7;
2570  line = WID_CC_YEAR;
2571  afilter = CS_NUMERAL;
2572  break;
2573  }
2574 
2575  if (len != 0) {
2576  this->query_widget = line;
2577  ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2578  }
2579 
2580  this->SetTimeout();
2581  this->SetDirty();
2582  }
2583 
2584  virtual void OnQueryTextFinished(char *str)
2585  {
2586  if (str == NULL) return;
2587 
2588  switch (this->query_widget) {
2589  case WID_CC_RATE:
2590  _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2591  break;
2592 
2593  case WID_CC_SEPARATOR: // Thousands separator
2594  strecpy(_custom_currency.separator, str, lastof(_custom_currency.separator));
2595  break;
2596 
2597  case WID_CC_PREFIX:
2598  strecpy(_custom_currency.prefix, str, lastof(_custom_currency.prefix));
2599  break;
2600 
2601  case WID_CC_SUFFIX:
2602  strecpy(_custom_currency.suffix, str, lastof(_custom_currency.suffix));
2603  break;
2604 
2605  case WID_CC_YEAR: { // Year to switch to euro
2606  int val = atoi(str);
2607 
2608  _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : min(val, MAX_YEAR));
2609  break;
2610  }
2611  }
2613  SetButtonState();
2614  }
2615 
2616  virtual void OnTimeout()
2617  {
2618  this->SetDirty();
2619  }
2620 };
2621 
2622 static const NWidgetPart _nested_cust_currency_widgets[] = {
2624  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2625  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2626  EndContainer(),
2627  NWidget(WWT_PANEL, COLOUR_GREY),
2629  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2630  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2631  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2633  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2634  EndContainer(),
2635  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2636  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2638  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2639  EndContainer(),
2640  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2641  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2643  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2644  EndContainer(),
2645  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2646  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2648  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2649  EndContainer(),
2650  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2651  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2652  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2654  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2655  EndContainer(),
2656  EndContainer(),
2657  NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2658  SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2659  EndContainer(),
2660 };
2661 
2662 static WindowDesc _cust_currency_desc(
2663  WDP_CENTER, NULL, 0, 0,
2665  0,
2666  _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2667 );
2668 
2670 static void ShowCustCurrency()
2671 {
2673  new CustomCurrencyWindow(&_cust_currency_desc);
2674 }