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