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