OpenTTD
settings_gui.cpp
Go to the documentation of this file.
1 /* $Id: settings_gui.cpp 27825 2017-03-24 18:55:16Z 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  general->Add(new SettingEntry("gui.right_mouse_wnd_close"));
1510  }
1511 
1512  SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1513  {
1514  viewports->Add(new SettingEntry("gui.auto_scrolling"));
1515  viewports->Add(new SettingEntry("gui.reverse_scroll"));
1516  viewports->Add(new SettingEntry("gui.smooth_scroll"));
1517  viewports->Add(new SettingEntry("gui.left_mouse_btn_scrolling"));
1518  /* While the horizontal scrollwheel scrolling is written as general code, only
1519  * the cocoa (OSX) driver generates input for it.
1520  * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1521  viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1522  viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1523 #ifdef __APPLE__
1524  /* We might need to emulate a right mouse button on mac */
1525  viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1526 #endif
1527  viewports->Add(new SettingEntry("gui.population_in_label"));
1528  viewports->Add(new SettingEntry("gui.liveries"));
1529  viewports->Add(new SettingEntry("construction.train_signal_side"));
1530  viewports->Add(new SettingEntry("gui.measure_tooltip"));
1531  viewports->Add(new SettingEntry("gui.loading_indicators"));
1532  viewports->Add(new SettingEntry("gui.show_track_reservation"));
1533  }
1534 
1535  SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1536  {
1537  construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1538  construction->Add(new SettingEntry("gui.enable_signal_gui"));
1539  construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1540  construction->Add(new SettingEntry("gui.quick_goto"));
1541  construction->Add(new SettingEntry("gui.default_rail_type"));
1542  construction->Add(new SettingEntry("gui.disable_unsuitable_building"));
1543  }
1544 
1545  interface->Add(new SettingEntry("gui.autosave"));
1546  interface->Add(new SettingEntry("gui.toolbar_pos"));
1547  interface->Add(new SettingEntry("gui.statusbar_pos"));
1548  interface->Add(new SettingEntry("gui.prefer_teamchat"));
1549  interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1550  interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1551  interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1552  interface->Add(new SettingEntry("gui.expenses_layout"));
1553  }
1554 
1555  SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1556  {
1557  advisors->Add(new SettingEntry("gui.coloured_news_year"));
1558  advisors->Add(new SettingEntry("news_display.general"));
1559  advisors->Add(new SettingEntry("news_display.new_vehicles"));
1560  advisors->Add(new SettingEntry("news_display.accident"));
1561  advisors->Add(new SettingEntry("news_display.company_info"));
1562  advisors->Add(new SettingEntry("news_display.acceptance"));
1563  advisors->Add(new SettingEntry("news_display.arrival_player"));
1564  advisors->Add(new SettingEntry("news_display.arrival_other"));
1565  advisors->Add(new SettingEntry("news_display.advice"));
1566  advisors->Add(new SettingEntry("gui.order_review_system"));
1567  advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1568  advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1569  advisors->Add(new SettingEntry("gui.show_finances"));
1570  advisors->Add(new SettingEntry("news_display.economy"));
1571  advisors->Add(new SettingEntry("news_display.subsidies"));
1572  advisors->Add(new SettingEntry("news_display.open"));
1573  advisors->Add(new SettingEntry("news_display.close"));
1574  advisors->Add(new SettingEntry("news_display.production_player"));
1575  advisors->Add(new SettingEntry("news_display.production_other"));
1576  advisors->Add(new SettingEntry("news_display.production_nobody"));
1577  }
1578 
1579  SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1580  {
1581  company->Add(new SettingEntry("gui.semaphore_build_before"));
1582  company->Add(new SettingEntry("gui.default_signal_type"));
1583  company->Add(new SettingEntry("gui.cycle_signal_types"));
1584  company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1585  company->Add(new SettingEntry("gui.new_nonstop"));
1586  company->Add(new SettingEntry("gui.stop_location"));
1587  company->Add(new SettingEntry("company.engine_renew"));
1588  company->Add(new SettingEntry("company.engine_renew_months"));
1589  company->Add(new SettingEntry("company.engine_renew_money"));
1590  company->Add(new SettingEntry("vehicle.servint_ispercent"));
1591  company->Add(new SettingEntry("vehicle.servint_trains"));
1592  company->Add(new SettingEntry("vehicle.servint_roadveh"));
1593  company->Add(new SettingEntry("vehicle.servint_ships"));
1594  company->Add(new SettingEntry("vehicle.servint_aircraft"));
1595  }
1596 
1597  SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1598  {
1599  accounting->Add(new SettingEntry("economy.inflation"));
1600  accounting->Add(new SettingEntry("difficulty.initial_interest"));
1601  accounting->Add(new SettingEntry("difficulty.max_loan"));
1602  accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1603  accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1604  accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1605  accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1606  accounting->Add(new SettingEntry("difficulty.construction_cost"));
1607  }
1608 
1609  SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1610  {
1611  SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1612  {
1613  physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1614  physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1615  physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1616  physics->Add(new SettingEntry("vehicle.freight_trains"));
1617  physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1618  physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1619  physics->Add(new SettingEntry("vehicle.smoke_amount"));
1620  physics->Add(new SettingEntry("vehicle.plane_speed"));
1621  }
1622 
1623  SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1624  {
1625  routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1626  routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1627  routing->Add(new SettingEntry("pf.reverse_at_signals"));
1628  routing->Add(new SettingEntry("pf.forbid_90_deg"));
1629  routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1630  routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1631  }
1632 
1633  vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1634  vehicles->Add(new SettingEntry("order.serviceathelipad"));
1635  }
1636 
1637  SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1638  {
1639  limitations->Add(new SettingEntry("construction.command_pause_level"));
1640  limitations->Add(new SettingEntry("construction.autoslope"));
1641  limitations->Add(new SettingEntry("construction.extra_dynamite"));
1642  limitations->Add(new SettingEntry("construction.max_heightlevel"));
1643  limitations->Add(new SettingEntry("construction.max_bridge_length"));
1644  limitations->Add(new SettingEntry("construction.max_bridge_height"));
1645  limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1646  limitations->Add(new SettingEntry("station.never_expire_airports"));
1647  limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1648  limitations->Add(new SettingEntry("vehicle.max_trains"));
1649  limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1650  limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1651  limitations->Add(new SettingEntry("vehicle.max_ships"));
1652  limitations->Add(new SettingEntry("vehicle.max_train_length"));
1653  limitations->Add(new SettingEntry("station.station_spread"));
1654  limitations->Add(new SettingEntry("station.distant_join_stations"));
1655  limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1656  limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1657  limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1658  }
1659 
1660  SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1661  {
1662  disasters->Add(new SettingEntry("difficulty.disasters"));
1663  disasters->Add(new SettingEntry("difficulty.economy"));
1664  disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1665  disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1666  }
1667 
1668  SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1669  {
1670  genworld->Add(new SettingEntry("game_creation.landscape"));
1671  genworld->Add(new SettingEntry("game_creation.land_generator"));
1672  genworld->Add(new SettingEntry("difficulty.terrain_type"));
1673  genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1674  genworld->Add(new SettingEntry("game_creation.variety"));
1675  genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1676  genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1677  genworld->Add(new SettingEntry("game_creation.tree_placer"));
1678  genworld->Add(new SettingEntry("vehicle.road_side"));
1679  genworld->Add(new SettingEntry("economy.larger_towns"));
1680  genworld->Add(new SettingEntry("economy.initial_city_size"));
1681  genworld->Add(new SettingEntry("economy.town_layout"));
1682  genworld->Add(new SettingEntry("difficulty.industry_density"));
1683  genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1684  }
1685 
1686  SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1687  {
1688  SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1689  {
1690  authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1691  authorities->Add(new SettingEntry("economy.bribe"));
1692  authorities->Add(new SettingEntry("economy.exclusive_rights"));
1693  authorities->Add(new SettingEntry("economy.fund_roads"));
1694  authorities->Add(new SettingEntry("economy.fund_buildings"));
1695  authorities->Add(new SettingEntry("economy.station_noise_level"));
1696  }
1697 
1698  SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1699  {
1700  towns->Add(new SettingEntry("economy.town_growth_rate"));
1701  towns->Add(new SettingEntry("economy.allow_town_roads"));
1702  towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1703  towns->Add(new SettingEntry("economy.found_town"));
1704  }
1705 
1706  SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1707  {
1708  industries->Add(new SettingEntry("construction.raw_industry_construction"));
1709  industries->Add(new SettingEntry("construction.industry_platform"));
1710  industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1711  industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1712  industries->Add(new SettingEntry("economy.smooth_economy"));
1713  }
1714 
1715  SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1716  {
1717  cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1718  cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1719  cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1720  cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1721  cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1722  cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1723  cdist->Add(new SettingEntry("linkgraph.accuracy"));
1724  cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1725  cdist->Add(new SettingEntry("linkgraph.demand_size"));
1726  cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1727  }
1728 
1729  environment->Add(new SettingEntry("station.modified_catchment"));
1730  environment->Add(new SettingEntry("construction.extra_tree_placement"));
1731  }
1732 
1733  SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1734  {
1735  SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1736  {
1737  npc->Add(new SettingEntry("script.settings_profile"));
1738  npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1739  npc->Add(new SettingEntry("difficulty.competitor_speed"));
1740  npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1741  npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1742  npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1743  npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1744  npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1745  }
1746 
1747  ai->Add(new SettingEntry("economy.give_money"));
1748  ai->Add(new SettingEntry("economy.allow_shares"));
1749  }
1750 
1751  main->Init();
1752  }
1753  return *main;
1754 }
1755 
1756 static const StringID _game_settings_restrict_dropdown[] = {
1757  STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1758  STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1759  STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1760  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1761  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1762 };
1763 assert_compile(lengthof(_game_settings_restrict_dropdown) == RM_END);
1764 
1771 };
1772 
1775  static const int SETTINGTREE_LEFT_OFFSET = 5;
1776  static const int SETTINGTREE_RIGHT_OFFSET = 5;
1777  static const int SETTINGTREE_TOP_OFFSET = 5;
1778  static const int SETTINGTREE_BOTTOM_OFFSET = 5;
1779 
1781 
1787 
1793 
1794  Scrollbar *vscroll;
1795 
1797  {
1798  this->warn_missing = WHR_NONE;
1799  this->warn_lines = 0;
1801  this->filter.min_cat = RM_ALL;
1802  this->filter.type = ST_ALL;
1803  this->filter.type_hides = false;
1804  this->settings_ptr = &GetGameSettings();
1805 
1806  _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1807  GetSettingsTree().FoldAll(); // Close all sub-pages
1808 
1809  this->valuewindow_entry = NULL; // No setting entry for which a entry window is opened
1810  this->clicked_entry = NULL; // No numeric setting buttons are depressed
1811  this->last_clicked = NULL;
1812  this->valuedropdown_entry = NULL;
1813  this->closing_dropdown = false;
1814  this->manually_changed_folding = false;
1815 
1816  this->CreateNestedTree();
1817  this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1819 
1820  this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1823 
1824  this->InvalidateData();
1825  }
1826 
1827  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1828  {
1829  switch (widget) {
1830  case WID_GS_OPTIONSPANEL:
1831  resize->height = SETTING_HEIGHT = max(max<int>(_circle_size.height, SETTING_BUTTON_HEIGHT), FONT_HEIGHT_NORMAL) + 1;
1832  resize->width = 1;
1833 
1834  size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1835  break;
1836 
1837  case WID_GS_HELP_TEXT: {
1838  static const StringID setting_types[] = {
1839  STR_CONFIG_SETTING_TYPE_CLIENT,
1840  STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1841  STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1842  };
1843  for (uint i = 0; i < lengthof(setting_types); i++) {
1844  SetDParam(0, setting_types[i]);
1845  size->width = max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1846  }
1847  size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1848  max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1849  break;
1850  }
1851 
1853  case WID_GS_RESTRICT_TYPE:
1854  size->width = max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1855  break;
1856 
1857  default:
1858  break;
1859  }
1860  }
1861 
1862  virtual void OnPaint()
1863  {
1864  if (this->closing_dropdown) {
1865  this->closing_dropdown = false;
1866  assert(this->valuedropdown_entry != NULL);
1867  this->valuedropdown_entry->SetButtons(0);
1868  this->valuedropdown_entry = NULL;
1869  }
1870 
1871  /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1872  const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1873  StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1874  int new_warn_lines;
1875  if (this->warn_missing == WHR_NONE) {
1876  new_warn_lines = 0;
1877  } else {
1878  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1879  new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1880  }
1881  if (this->warn_lines != new_warn_lines) {
1882  this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
1883  this->warn_lines = new_warn_lines;
1884  }
1885 
1886  this->DrawWidgets();
1887 
1888  /* Draw the 'some search results are hidden' notice. */
1889  if (this->warn_missing != WHR_NONE) {
1890  const int left = panel->pos_x;
1891  const int right = left + panel->current_x - 1;
1892  const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
1893  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1894  if (this->warn_lines == 1) {
1895  /* If the warning fits at one line, center it. */
1896  DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1897  } else {
1898  DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1899  }
1900  }
1901  }
1902 
1903  virtual void SetStringParameters(int widget) const
1904  {
1905  switch (widget) {
1907  SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
1908  break;
1909 
1910  case WID_GS_TYPE_DROPDOWN:
1911  switch (this->filter.type) {
1912  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
1913  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
1914  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
1915  default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
1916  }
1917  break;
1918  }
1919  }
1920 
1921  DropDownList *BuildDropDownList(int widget) const
1922  {
1923  DropDownList *list = NULL;
1924  switch (widget) {
1926  list = new DropDownList();
1927 
1928  for (int mode = 0; mode != RM_END; mode++) {
1929  /* If we are in adv. settings screen for the new game's settings,
1930  * we don't want to allow comparing with new game's settings. */
1931  bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
1932 
1933  *list->Append() = new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled);
1934  }
1935  break;
1936 
1937  case WID_GS_TYPE_DROPDOWN:
1938  list = new DropDownList();
1939  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false);
1940  *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);
1941  *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);
1942  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false);
1943  break;
1944  }
1945  return list;
1946  }
1947 
1948  virtual void DrawWidget(const Rect &r, int widget) const
1949  {
1950  switch (widget) {
1951  case WID_GS_OPTIONSPANEL: {
1952  int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
1953  uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
1954  int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
1955  this->vscroll->GetPosition(), last_row, this->last_clicked);
1956  if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
1957  break;
1958  }
1959 
1960  case WID_GS_HELP_TEXT:
1961  if (this->last_clicked != NULL) {
1962  const SettingDesc *sd = this->last_clicked->setting;
1963 
1964  int y = r.top;
1965  switch (sd->GetType()) {
1966  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
1967  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
1968  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
1969  default: NOT_REACHED();
1970  }
1971  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
1972  y += FONT_HEIGHT_NORMAL;
1973 
1974  int32 default_value = ReadValue(&sd->desc.def, sd->save.conv);
1975  this->last_clicked->SetValueDParams(0, default_value);
1976  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
1978 
1979  DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
1980  }
1981  break;
1982 
1983  default:
1984  break;
1985  }
1986  }
1987 
1993  {
1994  if (this->last_clicked != pe) this->SetDirty();
1995  this->last_clicked = pe;
1996  }
1997 
1998  virtual void OnClick(Point pt, int widget, int click_count)
1999  {
2000  switch (widget) {
2001  case WID_GS_EXPAND_ALL:
2002  this->manually_changed_folding = true;
2004  this->InvalidateData();
2005  break;
2006 
2007  case WID_GS_COLLAPSE_ALL:
2008  this->manually_changed_folding = true;
2010  this->InvalidateData();
2011  break;
2012 
2013  case WID_GS_RESTRICT_DROPDOWN: {
2014  DropDownList *list = this->BuildDropDownList(widget);
2015  if (list != NULL) {
2016  ShowDropDownList(this, list, this->filter.mode, widget);
2017  }
2018  break;
2019  }
2020 
2021  case WID_GS_TYPE_DROPDOWN: {
2022  DropDownList *list = this->BuildDropDownList(widget);
2023  if (list != NULL) {
2024  ShowDropDownList(this, list, this->filter.type, widget);
2025  }
2026  break;
2027  }
2028  }
2029 
2030  if (widget != WID_GS_OPTIONSPANEL) return;
2031 
2032  uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2033  if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2034  btn -= this->warn_lines;
2035 
2036  uint cur_row = 0;
2038 
2039  if (clicked_entry == NULL) return; // Clicked below the last setting of the page
2040 
2041  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
2042  if (x < 0) return; // Clicked left of the entry
2043 
2044  SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2045  if (clicked_page != NULL) {
2046  this->SetDisplayedHelpText(NULL);
2047  clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2048 
2049  this->manually_changed_folding = true;
2050 
2051  this->InvalidateData();
2052  return;
2053  }
2054 
2055  SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2056  assert(pe != NULL);
2057  const SettingDesc *sd = pe->setting;
2058 
2059  /* return if action is only active in network, or only settable by server */
2060  if (!sd->IsEditable()) {
2061  this->SetDisplayedHelpText(pe);
2062  return;
2063  }
2064 
2065  const void *var = ResolveVariableAddress(settings_ptr, sd);
2066  int32 value = (int32)ReadValue(var, sd->save.conv);
2067 
2068  /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2069  if (x < SETTING_BUTTON_WIDTH && (sd->desc.flags & SGF_MULTISTRING)) {
2070  const SettingDescBase *sdb = &sd->desc;
2071  this->SetDisplayedHelpText(pe);
2072 
2073  if (this->valuedropdown_entry == pe) {
2074  /* unclick the dropdown */
2075  HideDropDownMenu(this);
2076  this->closing_dropdown = false;
2077  this->valuedropdown_entry->SetButtons(0);
2078  this->valuedropdown_entry = NULL;
2079  } else {
2080  if (this->valuedropdown_entry != NULL) this->valuedropdown_entry->SetButtons(0);
2081  this->closing_dropdown = false;
2082 
2083  const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2084  int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2085 
2086  Rect wi_rect;
2087  wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2088  wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2089  wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2090  wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2091 
2092  /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2093  if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2094  this->valuedropdown_entry = pe;
2096 
2097  DropDownList *list = new DropDownList();
2098  for (int i = sdb->min; i <= (int)sdb->max; i++) {
2099  *list->Append() = new DropDownListStringItem(sdb->str_val + i - sdb->min, i, false);
2100  }
2101 
2102  ShowDropDownListAt(this, list, value, -1, wi_rect, COLOUR_ORANGE, true);
2103  }
2104  }
2105  this->SetDirty();
2106  } else if (x < SETTING_BUTTON_WIDTH) {
2107  this->SetDisplayedHelpText(pe);
2108  const SettingDescBase *sdb = &sd->desc;
2109  int32 oldvalue = value;
2110 
2111  switch (sdb->cmd) {
2112  case SDT_BOOLX: value ^= 1; break;
2113  case SDT_ONEOFMANY:
2114  case SDT_NUMX: {
2115  /* Add a dynamic step-size to the scroller. In a maximum of
2116  * 50-steps you should be able to get from min to max,
2117  * unless specified otherwise in the 'interval' variable
2118  * of the current setting. */
2119  uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
2120  if (step == 0) step = 1;
2121 
2122  /* don't allow too fast scrolling */
2123  if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2124  _left_button_clicked = false;
2125  return;
2126  }
2127 
2128  /* Increase or decrease the value and clamp it to extremes */
2129  if (x >= SETTING_BUTTON_WIDTH / 2) {
2130  value += step;
2131  if (sdb->min < 0) {
2132  assert((int32)sdb->max >= 0);
2133  if (value > (int32)sdb->max) value = (int32)sdb->max;
2134  } else {
2135  if ((uint32)value > sdb->max) value = (int32)sdb->max;
2136  }
2137  if (value < sdb->min) value = sdb->min; // skip between "disabled" and minimum
2138  } else {
2139  value -= step;
2140  if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
2141  }
2142 
2143  /* Set up scroller timeout for numeric values */
2144  if (value != oldvalue) {
2145  if (this->clicked_entry != NULL) { // Release previous buttons if any
2146  this->clicked_entry->SetButtons(0);
2147  }
2148  this->clicked_entry = pe;
2149  this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2150  this->SetTimeout();
2151  _left_button_clicked = false;
2152  }
2153  break;
2154  }
2155 
2156  default: NOT_REACHED();
2157  }
2158 
2159  if (value != oldvalue) {
2160  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2161  SetCompanySetting(pe->index, value);
2162  } else {
2163  SetSettingValue(pe->index, value);
2164  }
2165  this->SetDirty();
2166  }
2167  } else {
2168  /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2169  if (this->last_clicked == pe && sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
2170  /* Show the correct currency-translated value */
2171  if (sd->desc.flags & SGF_CURRENCY) value *= _currency->rate;
2172 
2173  this->valuewindow_entry = pe;
2174  SetDParam(0, value);
2175  ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2176  }
2177  this->SetDisplayedHelpText(pe);
2178  }
2179  }
2180 
2181  virtual void OnTimeout()
2182  {
2183  if (this->clicked_entry != NULL) { // On timeout, release any depressed buttons
2184  this->clicked_entry->SetButtons(0);
2185  this->clicked_entry = NULL;
2186  this->SetDirty();
2187  }
2188  }
2189 
2190  virtual void OnQueryTextFinished(char *str)
2191  {
2192  /* The user pressed cancel */
2193  if (str == NULL) return;
2194 
2195  assert(this->valuewindow_entry != NULL);
2196  const SettingDesc *sd = this->valuewindow_entry->setting;
2197 
2198  int32 value;
2199  if (!StrEmpty(str)) {
2200  value = atoi(str);
2201 
2202  /* Save the correct currency-translated value */
2203  if (sd->desc.flags & SGF_CURRENCY) value /= _currency->rate;
2204  } else {
2205  value = (int32)(size_t)sd->desc.def;
2206  }
2207 
2208  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2209  SetCompanySetting(this->valuewindow_entry->index, value);
2210  } else {
2211  SetSettingValue(this->valuewindow_entry->index, value);
2212  }
2213  this->SetDirty();
2214  }
2215 
2216  virtual void OnDropdownSelect(int widget, int index)
2217  {
2218  switch (widget) {
2220  this->filter.mode = (RestrictionMode)index;
2221  if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2222  this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2223 
2224  if (!this->manually_changed_folding) {
2225  /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2226  GetSettingsTree().UpdateFilterState(this->filter, false);
2228  }
2229  } else {
2230  /* Non-'changes' filter. Save as default. */
2232  }
2233  this->InvalidateData();
2234  break;
2235 
2236  case WID_GS_TYPE_DROPDOWN:
2237  this->filter.type = (SettingType)index;
2238  this->InvalidateData();
2239  break;
2240 
2241  default:
2242  if (widget < 0) {
2243  /* Deal with drop down boxes on the panel. */
2244  assert(this->valuedropdown_entry != NULL);
2245  const SettingDesc *sd = this->valuedropdown_entry->setting;
2246  assert(sd->desc.flags & SGF_MULTISTRING);
2247 
2248  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2250  } else {
2251  SetSettingValue(this->valuedropdown_entry->index, index);
2252  }
2253 
2254  this->SetDirty();
2255  }
2256  break;
2257  }
2258  }
2259 
2260  virtual void OnDropdownClose(Point pt, int widget, int index, bool instant_close)
2261  {
2262  if (widget >= 0) {
2263  /* Normally the default implementation of OnDropdownClose() takes care of
2264  * a few things. We want that behaviour here too, but only for
2265  * "normal" dropdown boxes. The special dropdown boxes added for every
2266  * setting that needs one can't have this call. */
2267  Window::OnDropdownClose(pt, widget, index, instant_close);
2268  } else {
2269  /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2270  * the same dropdown button was clicked again, and then not open the dropdown again.
2271  * So, we only remember that it was closed, and process it on the next OnPaint, which is
2272  * after OnClick. */
2273  assert(this->valuedropdown_entry != NULL);
2274  this->closing_dropdown = true;
2275  this->SetDirty();
2276  }
2277  }
2278 
2279  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2280  {
2281  if (!gui_scope) return;
2282 
2283  /* Update which settings are to be visible. */
2284  RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2285  this->filter.min_cat = min_level;
2286  this->filter.type_hides = false;
2287  GetSettingsTree().UpdateFilterState(this->filter, false);
2288 
2289  if (this->filter.string.IsEmpty()) {
2290  this->warn_missing = WHR_NONE;
2291  } else if (min_level < this->filter.min_cat) {
2293  } else {
2294  this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2295  }
2296  this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2297 
2298  if (this->last_clicked != NULL && !GetSettingsTree().IsVisible(this->last_clicked)) {
2299  this->SetDisplayedHelpText(NULL);
2300  }
2301 
2302  bool all_folded = true;
2303  bool all_unfolded = true;
2304  GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2305  this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2306  this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2307  }
2308 
2309  virtual void OnEditboxChanged(int wid)
2310  {
2311  if (wid == WID_GS_FILTER) {
2312  this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2313  if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2314  /* User never expanded/collapsed single pages and entered a filter term.
2315  * Expand everything, to save weird expand clicks, */
2317  }
2318  this->InvalidateData();
2319  }
2320  }
2321 
2322  virtual void OnResize()
2323  {
2325  }
2326 };
2327 
2329 
2330 static const NWidgetPart _nested_settings_selection_widgets[] = {
2332  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2333  NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2334  NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2335  EndContainer(),
2336  NWidget(WWT_PANEL, COLOUR_MAUVE),
2339  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2340  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),
2341  EndContainer(),
2343  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2344  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),
2345  EndContainer(),
2346  EndContainer(),
2349  NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2350  NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2351  SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2352  EndContainer(),
2353  EndContainer(),
2356  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2357  EndContainer(),
2358  NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2359  NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2361  EndContainer(),
2363  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2364  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2365  NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2366  EndContainer(),
2367  NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2368  EndContainer(),
2369 };
2370 
2371 static WindowDesc _settings_selection_desc(
2372  WDP_CENTER, "settings", 510, 450,
2374  0,
2375  _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2376 );
2377 
2380 {
2382  new GameSettingsWindow(&_settings_selection_desc);
2383 }
2384 
2385 
2395 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2396 {
2397  int colour = _colour_gradient[button_colour][2];
2398  Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2399 
2400  DrawFrameRect(x, y, x + dim.width - 1, y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2401  DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2402  DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2403  DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2404 
2405  /* Grey out the buttons that aren't clickable */
2406  bool rtl = _current_text_dir == TD_RTL;
2407  if (rtl ? !clickable_right : !clickable_left) {
2408  GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2409  }
2410  if (rtl ? !clickable_left : !clickable_right) {
2411  GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2412  }
2413 }
2414 
2423 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2424 {
2425  int colour = _colour_gradient[button_colour][2];
2426 
2427  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2428  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2429 
2430  if (!clickable) {
2431  GfxFillRect(x + 1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2432  }
2433 }
2434 
2442 void DrawBoolButton(int x, int y, bool state, bool clickable)
2443 {
2444  static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2445  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2446 }
2447 
2449  int query_widget;
2450 
2451  CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2452  {
2453  this->InitNested();
2454 
2455  SetButtonState();
2456  }
2457 
2458  void SetButtonState()
2459  {
2460  this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2461  this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2462  this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2463  this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2464  }
2465 
2466  virtual void SetStringParameters(int widget) const
2467  {
2468  switch (widget) {
2469  case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2470  case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2471  case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2472  case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2473  case WID_CC_YEAR:
2474  SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2475  SetDParam(1, _custom_currency.to_euro);
2476  break;
2477 
2478  case WID_CC_PREVIEW:
2479  SetDParam(0, 10000);
2480  break;
2481  }
2482  }
2483 
2484  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2485  {
2486  switch (widget) {
2487  /* Set the appropriate width for the edit 'buttons' */
2488  case WID_CC_SEPARATOR_EDIT:
2489  case WID_CC_PREFIX_EDIT:
2490  case WID_CC_SUFFIX_EDIT:
2491  size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2492  break;
2493 
2494  /* Make sure the window is wide enough for the widest exchange rate */
2495  case WID_CC_RATE:
2496  SetDParam(0, 1);
2497  SetDParam(1, INT32_MAX);
2498  *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2499  break;
2500  }
2501  }
2502 
2503  virtual void OnClick(Point pt, int widget, int click_count)
2504  {
2505  int line = 0;
2506  int len = 0;
2507  StringID str = 0;
2508  CharSetFilter afilter = CS_ALPHANUMERAL;
2509 
2510  switch (widget) {
2511  case WID_CC_RATE_DOWN:
2512  if (_custom_currency.rate > 1) _custom_currency.rate--;
2513  if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2515  break;
2516 
2517  case WID_CC_RATE_UP:
2518  if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2519  if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2521  break;
2522 
2523  case WID_CC_RATE:
2524  SetDParam(0, _custom_currency.rate);
2525  str = STR_JUST_INT;
2526  len = 5;
2527  line = WID_CC_RATE;
2528  afilter = CS_NUMERAL;
2529  break;
2530 
2531  case WID_CC_SEPARATOR_EDIT:
2532  case WID_CC_SEPARATOR:
2533  SetDParamStr(0, _custom_currency.separator);
2534  str = STR_JUST_RAW_STRING;
2535  len = 1;
2536  line = WID_CC_SEPARATOR;
2537  break;
2538 
2539  case WID_CC_PREFIX_EDIT:
2540  case WID_CC_PREFIX:
2541  SetDParamStr(0, _custom_currency.prefix);
2542  str = STR_JUST_RAW_STRING;
2543  len = 12;
2544  line = WID_CC_PREFIX;
2545  break;
2546 
2547  case WID_CC_SUFFIX_EDIT:
2548  case WID_CC_SUFFIX:
2549  SetDParamStr(0, _custom_currency.suffix);
2550  str = STR_JUST_RAW_STRING;
2551  len = 12;
2552  line = WID_CC_SUFFIX;
2553  break;
2554 
2555  case WID_CC_YEAR_DOWN:
2556  _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2557  if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2559  break;
2560 
2561  case WID_CC_YEAR_UP:
2562  _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2563  if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2565  break;
2566 
2567  case WID_CC_YEAR:
2568  SetDParam(0, _custom_currency.to_euro);
2569  str = STR_JUST_INT;
2570  len = 7;
2571  line = WID_CC_YEAR;
2572  afilter = CS_NUMERAL;
2573  break;
2574  }
2575 
2576  if (len != 0) {
2577  this->query_widget = line;
2578  ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2579  }
2580 
2581  this->SetTimeout();
2582  this->SetDirty();
2583  }
2584 
2585  virtual void OnQueryTextFinished(char *str)
2586  {
2587  if (str == NULL) return;
2588 
2589  switch (this->query_widget) {
2590  case WID_CC_RATE:
2591  _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2592  break;
2593 
2594  case WID_CC_SEPARATOR: // Thousands separator
2595  strecpy(_custom_currency.separator, str, lastof(_custom_currency.separator));
2596  break;
2597 
2598  case WID_CC_PREFIX:
2599  strecpy(_custom_currency.prefix, str, lastof(_custom_currency.prefix));
2600  break;
2601 
2602  case WID_CC_SUFFIX:
2603  strecpy(_custom_currency.suffix, str, lastof(_custom_currency.suffix));
2604  break;
2605 
2606  case WID_CC_YEAR: { // Year to switch to euro
2607  int val = atoi(str);
2608 
2609  _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : min(val, MAX_YEAR));
2610  break;
2611  }
2612  }
2614  SetButtonState();
2615  }
2616 
2617  virtual void OnTimeout()
2618  {
2619  this->SetDirty();
2620  }
2621 };
2622 
2623 static const NWidgetPart _nested_cust_currency_widgets[] = {
2625  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2626  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2627  EndContainer(),
2628  NWidget(WWT_PANEL, COLOUR_GREY),
2630  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2631  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2632  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2634  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2635  EndContainer(),
2636  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2637  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2639  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2640  EndContainer(),
2641  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2642  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2644  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2645  EndContainer(),
2646  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2647  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2649  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2650  EndContainer(),
2651  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2652  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2653  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2655  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2656  EndContainer(),
2657  EndContainer(),
2658  NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2659  SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2660  EndContainer(),
2661 };
2662 
2663 static WindowDesc _cust_currency_desc(
2664  WDP_CENTER, NULL, 0, 0,
2666  0,
2667  _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2668 );
2669 
2671 static void ShowCustCurrency()
2672 {
2674  new CustomCurrencyWindow(&_cust_currency_desc);
2675 }