OpenTTD
settings_gui.cpp
Go to the documentation of this file.
1 /* $Id: settings_gui.cpp 27366 2015-08-09 10:22: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);
535  break;
536 
538  this->SetMediaSet<BaseGraphics>(index);
539  break;
540 
542  this->SetMediaSet<BaseSounds>(index);
543  break;
544 
546  this->SetMediaSet<BaseMusic>(index);
547  break;
548  }
549  }
550 
556  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
557  {
558  if (!gui_scope) return;
560 
561  bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
562  this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
563 
564  for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
568  }
569 
570  missing_files = BaseMusic::GetUsedSet()->GetNumInvalid() == 0;
571  this->GetWidget<NWidgetCore>(WID_GO_BASE_MUSIC_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_MUSIC_STATUS, STR_NULL);
572  }
573 };
574 
575 static const NWidgetPart _nested_game_options_widgets[] = {
577  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
578  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
579  EndContainer(),
580  NWidget(WWT_PANEL, COLOUR_GREY, WID_GO_BACKGROUND), SetPIP(6, 6, 10),
581  NWidget(NWID_HORIZONTAL), SetPIP(10, 10, 10),
582  NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
583  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_ROAD_VEHICLES_FRAME, STR_NULL),
584  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),
585  EndContainer(),
586  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
587  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),
588  EndContainer(),
589  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
590  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),
592  NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
593  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
594  EndContainer(),
595  EndContainer(),
596  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_ZOOM_FRAME, STR_NULL),
597  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),
598  EndContainer(),
599  EndContainer(),
600 
601  NWidget(NWID_VERTICAL), SetPIP(0, 6, 0),
602  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_TOWN_NAMES_FRAME, STR_NULL),
603  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),
604  EndContainer(),
605  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
606  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),
607  EndContainer(),
608  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
609  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),
610  EndContainer(),
611  NWidget(NWID_SPACER), SetMinimalSize(0, 0), SetFill(0, 1),
612  EndContainer(),
613  EndContainer(),
614 
615  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPadding(0, 10, 0, 10),
616  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
617  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP),
618  NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
619  EndContainer(),
620  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),
622  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),
623  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),
624  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),
625  EndContainer(),
626  EndContainer(),
627 
628  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPadding(0, 10, 0, 10),
629  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
630  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP),
631  NWidget(NWID_SPACER), SetFill(1, 0),
632  EndContainer(),
633  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),
635  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),
636  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),
637  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),
638  EndContainer(),
639  EndContainer(),
640 
641  NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPadding(0, 10, 0, 10),
642  NWidget(NWID_HORIZONTAL), SetPIP(0, 30, 0),
643  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(150, 12), SetDataTip(STR_BLACK_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP),
644  NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_MUSIC_STATUS), SetMinimalSize(150, 12), SetDataTip(STR_EMPTY, STR_NULL), SetFill(1, 0),
645  EndContainer(),
646  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),
648  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),
649  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),
650  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),
651  EndContainer(),
652  EndContainer(),
653  EndContainer(),
654 };
655 
656 static WindowDesc _game_options_desc(
657  WDP_CENTER, "settings_game", 0, 0,
659  0,
660  _nested_game_options_widgets, lengthof(_nested_game_options_widgets)
661 );
662 
665 {
667  new GameOptionsWindow(&_game_options_desc);
668 }
669 
670 static int SETTING_HEIGHT = 11;
671 static const int LEVEL_WIDTH = 15;
672 
681 
682  SEF_LAST_FIELD = 0x04,
683  SEF_FILTERED = 0x08,
684 };
685 
694 };
696 
697 
701  bool type_hides;
704 };
705 
708  byte flags;
709  byte level;
710 
711  BaseSettingEntry() : flags(0), level(0) {}
712  virtual ~BaseSettingEntry() {}
713 
714  virtual void Init(byte level = 0);
715  virtual void FoldAll() {}
716  virtual void UnFoldAll() {}
717 
722  void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
723 
724  virtual uint Length() const = 0;
725  virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const {}
726  virtual bool IsVisible(const BaseSettingEntry *item) const;
727  virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
728  virtual uint GetMaxHelpHeight(int maxw) { return 0; }
729 
734  bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
735 
736  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
737 
738  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;
739 
740 protected:
741  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
742 };
743 
746  const char *name;
748  uint index;
749 
750  SettingEntry(const char *name);
751 
752  virtual void Init(byte level = 0);
753  virtual uint Length() const;
754  virtual uint GetMaxHelpHeight(int maxw);
755  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
756 
757  void SetButtons(byte new_val);
758 
763  inline StringID GetHelpText() const
764  {
765  return this->setting->desc.str_help;
766  }
767 
768  void SetValueDParams(uint first_param, int32 value) const;
769 
770 protected:
771  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
772 
773 private:
775 };
776 
779  typedef std::vector<BaseSettingEntry*> EntryVector;
780  EntryVector entries;
781 
782  template<typename T>
783  T *Add(T *item)
784  {
785  this->entries.push_back(item);
786  return item;
787  }
788 
789  void Init(byte level = 0);
790  void FoldAll();
791  void UnFoldAll();
792 
793  uint Length() const;
794  void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
795  bool IsVisible(const BaseSettingEntry *item) const;
796  BaseSettingEntry *FindEntry(uint row, uint *cur_row);
797  uint GetMaxHelpHeight(int maxw);
798 
799  bool UpdateFilterState(SettingFilter &filter, bool force_visible);
800 
801  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;
802 };
803 
807  bool folded;
808 
810 
811  virtual void Init(byte level = 0);
812  virtual void FoldAll();
813  virtual void UnFoldAll();
814 
815  virtual uint Length() const;
816  virtual void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
817  virtual bool IsVisible(const BaseSettingEntry *item) const;
818  virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
819  virtual uint GetMaxHelpHeight(int maxw) { return SettingsContainer::GetMaxHelpHeight(maxw); }
820 
821  virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible);
822 
823  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;
824 
825 protected:
826  virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const;
827 };
828 
829 /* == BaseSettingEntry methods == */
830 
835 void BaseSettingEntry::Init(byte level)
836 {
837  this->level = level;
838 }
839 
847 {
848  if (this->IsFiltered()) return false;
849  if (this == item) return true;
850  return false;
851 }
852 
859 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
860 {
861  if (this->IsFiltered()) return NULL;
862  if (row_num == *cur_row) return this;
863  (*cur_row)++;
864  return NULL;
865 }
866 
896 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
897 {
898  if (this->IsFiltered()) return cur_row;
899  if (cur_row >= max_row) return cur_row;
900 
901  bool rtl = _current_text_dir == TD_RTL;
902  int offset = rtl ? -4 : 4;
903  int level_width = rtl ? -LEVEL_WIDTH : LEVEL_WIDTH;
904 
905  int x = rtl ? right : left;
906  if (cur_row >= first_row) {
907  int colour = _colour_gradient[COLOUR_ORANGE][4];
908  y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
909 
910  /* Draw vertical for parent nesting levels */
911  for (uint lvl = 0; lvl < this->level; lvl++) {
912  if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
913  x += level_width;
914  }
915  /* draw own |- prefix */
916  int halfway_y = y + SETTING_HEIGHT / 2;
917  int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
918  GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
919  /* Small horizontal line from the last vertical line */
920  GfxDrawLine(x + offset, halfway_y, x + level_width - offset, halfway_y, colour);
921  x += level_width;
922 
923  this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
924  }
925  cur_row++;
926 
927  return cur_row;
928 }
929 
930 /* == SettingEntry methods == */
931 
936 SettingEntry::SettingEntry(const char *name)
937 {
938  this->name = name;
939  this->setting = NULL;
940  this->index = 0;
941 }
942 
947 void SettingEntry::Init(byte level)
948 {
949  BaseSettingEntry::Init(level);
950  this->setting = GetSettingFromName(this->name, &this->index);
951  assert(this->setting != NULL);
952 }
953 
959 void SettingEntry::SetButtons(byte new_val)
960 {
961  assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
962  this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
963 }
964 
967 {
968  return this->IsFiltered() ? 0 : 1;
969 }
970 
977 {
978  return GetStringHeight(this->GetHelpText(), maxw);
979 }
980 
987 {
988  /* There shall not be any restriction, i.e. all settings shall be visible. */
989  if (mode == RM_ALL) return true;
990 
991  GameSettings *settings_ptr = &GetGameSettings();
992  const SettingDesc *sd = this->setting;
993 
994  if (mode == RM_BASIC) return (this->setting->desc.cat & SC_BASIC_LIST) != 0;
995  if (mode == RM_ADVANCED) return (this->setting->desc.cat & SC_ADVANCED_LIST) != 0;
996 
997  /* Read the current value. */
998  const void *var = ResolveVariableAddress(settings_ptr, sd);
999  int64 current_value = ReadValue(var, sd->save.conv);
1000 
1001  int64 filter_value;
1002 
1003  if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1004  /* This entry shall only be visible, if the value deviates from its default value. */
1005 
1006  /* Read the default value. */
1007  filter_value = ReadValue(&sd->desc.def, sd->save.conv);
1008  } else {
1009  assert(mode == RM_CHANGED_AGAINST_NEW);
1010  /* This entry shall only be visible, if the value deviates from
1011  * its value is used when starting a new game. */
1012 
1013  /* Make sure we're not comparing the new game settings against itself. */
1014  assert(settings_ptr != &_settings_newgame);
1015 
1016  /* Read the new game's value. */
1017  var = ResolveVariableAddress(&_settings_newgame, sd);
1018  filter_value = ReadValue(var, sd->save.conv);
1019  }
1020 
1021  return current_value != filter_value;
1022 }
1023 
1030 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1031 {
1032  CLRBITS(this->flags, SEF_FILTERED);
1033 
1034  bool visible = true;
1035 
1036  const SettingDesc *sd = this->setting;
1037  if (!force_visible && !filter.string.IsEmpty()) {
1038  /* Process the search text filter for this item. */
1039  filter.string.ResetState();
1040 
1041  const SettingDescBase *sdb = &sd->desc;
1042 
1043  SetDParam(0, STR_EMPTY);
1044  filter.string.AddLine(sdb->str);
1045  filter.string.AddLine(this->GetHelpText());
1046 
1047  visible = filter.string.GetState();
1048  }
1049 
1050  if (visible) {
1051  if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1052  filter.type_hides = true;
1053  visible = false;
1054  }
1055  if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1056  while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1057  visible = false;
1058  }
1059  }
1060 
1061  if (!visible) SETBITS(this->flags, SEF_FILTERED);
1062  return visible;
1063 }
1064 
1065 
1066 static const void *ResolveVariableAddress(const GameSettings *settings_ptr, const SettingDesc *sd)
1067 {
1068  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
1069  if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1070  return GetVariableAddress(&Company::Get(_local_company)->settings, &sd->save);
1071  } else {
1073  }
1074  } else {
1075  return GetVariableAddress(settings_ptr, &sd->save);
1076  }
1077 }
1078 
1084 void SettingEntry::SetValueDParams(uint first_param, int32 value) const
1085 {
1086  const SettingDescBase *sdb = &this->setting->desc;
1087  if (sdb->cmd == SDT_BOOLX) {
1088  SetDParam(first_param++, value != 0 ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
1089  } else {
1090  if ((sdb->flags & SGF_MULTISTRING) != 0) {
1091  SetDParam(first_param++, sdb->str_val - sdb->min + value);
1092  } else if ((sdb->flags & SGF_DISPLAY_ABS) != 0) {
1093  SetDParam(first_param++, sdb->str_val + ((value >= 0) ? 1 : 0));
1094  value = abs(value);
1095  } else {
1096  SetDParam(first_param++, sdb->str_val + ((value == 0 && (sdb->flags & SGF_0ISDISABLED) != 0) ? 1 : 0));
1097  }
1098  SetDParam(first_param++, value);
1099  }
1100 }
1101 
1110 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1111 {
1112  const SettingDesc *sd = this->setting;
1113  const SettingDescBase *sdb = &sd->desc;
1114  const void *var = ResolveVariableAddress(settings_ptr, sd);
1115  int state = this->flags & SEF_BUTTONS_MASK;
1116 
1117  bool rtl = _current_text_dir == TD_RTL;
1118  uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1119  uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + 5);
1120  uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + 5 : 0);
1121  uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1122 
1123  /* We do not allow changes of some items when we are a client in a networkgame */
1124  bool editable = sd->IsEditable();
1125 
1126  SetDParam(0, highlight ? STR_ORANGE_STRING1_WHITE : STR_ORANGE_STRING1_LTBLUE);
1127  int32 value = (int32)ReadValue(var, sd->save.conv);
1128  if (sdb->cmd == SDT_BOOLX) {
1129  /* Draw checkbox for boolean-value either on/off */
1130  DrawBoolButton(buttons_left, button_y, value != 0, editable);
1131  } else if ((sdb->flags & SGF_MULTISTRING) != 0) {
1132  /* Draw [v] button for settings of an enum-type */
1133  DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1134  } else {
1135  /* Draw [<][>] boxes for settings of an integer-type */
1136  DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1137  editable && value != (sdb->flags & SGF_0ISDISABLED ? 0 : sdb->min), editable && (uint32)value != sdb->max);
1138  }
1139  this->SetValueDParams(1, value);
1140  DrawString(text_left, text_right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, sdb->str, highlight ? TC_WHITE : TC_LIGHT_BLUE);
1141 }
1142 
1143 /* == SettingsContainer methods == */
1144 
1149 void SettingsContainer::Init(byte level)
1150 {
1151  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1152  (*it)->Init(level);
1153  }
1154 }
1155 
1158 {
1159  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1160  (*it)->FoldAll();
1161  }
1162 }
1163 
1166 {
1167  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1168  (*it)->UnFoldAll();
1169  }
1170 }
1171 
1177 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1178 {
1179  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1180  (*it)->GetFoldingState(all_folded, all_unfolded);
1181  }
1182 }
1183 
1190 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1191 {
1192  bool visible = false;
1193  bool first_visible = true;
1194  for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1195  visible |= (*it)->UpdateFilterState(filter, force_visible);
1196  (*it)->SetLastField(first_visible);
1197  if (visible && first_visible) first_visible = false;
1198  }
1199  return visible;
1200 }
1201 
1202 
1210 {
1211  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1212  if ((*it)->IsVisible(item)) return true;
1213  }
1214  return false;
1215 }
1216 
1219 {
1220  uint length = 0;
1221  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1222  length += (*it)->Length();
1223  }
1224  return length;
1225 }
1226 
1233 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1234 {
1235  BaseSettingEntry *pe = NULL;
1236  for (EntryVector::iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1237  pe = (*it)->FindEntry(row_num, cur_row);
1238  if (pe != NULL) {
1239  break;
1240  }
1241  }
1242  return pe;
1243 }
1244 
1251 {
1252  uint biggest = 0;
1253  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1254  biggest = max(biggest, (*it)->GetMaxHelpHeight(maxw));
1255  }
1256  return biggest;
1257 }
1258 
1259 
1274 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
1275 {
1276  for (EntryVector::const_iterator it = this->entries.begin(); it != this->entries.end(); ++it) {
1277  cur_row = (*it)->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1278  if (cur_row >= max_row) {
1279  break;
1280  }
1281  }
1282  return cur_row;
1283 }
1284 
1285 /* == SettingsPage methods == */
1286 
1292 {
1293  this->title = title;
1294  this->folded = true;
1295 }
1296 
1301 void SettingsPage::Init(byte level)
1302 {
1303  BaseSettingEntry::Init(level);
1304  SettingsContainer::Init(level + 1);
1305 }
1306 
1309 {
1310  if (this->IsFiltered()) return;
1311  this->folded = true;
1312 
1314 }
1315 
1318 {
1319  if (this->IsFiltered()) return;
1320  this->folded = false;
1321 
1323 }
1324 
1330 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1331 {
1332  if (this->IsFiltered()) return;
1333 
1334  if (this->folded) {
1335  all_unfolded = false;
1336  } else {
1337  all_folded = false;
1338  }
1339 
1340  SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1341 }
1342 
1349 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1350 {
1351  if (!force_visible && !filter.string.IsEmpty()) {
1352  filter.string.ResetState();
1353  filter.string.AddLine(this->title);
1354  force_visible = filter.string.GetState();
1355  }
1356 
1357  bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1358  if (visible) {
1359  CLRBITS(this->flags, SEF_FILTERED);
1360  } else {
1361  SETBITS(this->flags, SEF_FILTERED);
1362  }
1363  return visible;
1364 }
1365 
1373 {
1374  if (this->IsFiltered()) return false;
1375  if (this == item) return true;
1376  if (this->folded) return false;
1377 
1378  return SettingsContainer::IsVisible(item);
1379 }
1380 
1383 {
1384  if (this->IsFiltered()) return 0;
1385  if (this->folded) return 1; // Only displaying the title
1386 
1387  return 1 + SettingsContainer::Length();
1388 }
1389 
1396 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1397 {
1398  if (this->IsFiltered()) return NULL;
1399  if (row_num == *cur_row) return this;
1400  (*cur_row)++;
1401  if (this->folded) return NULL;
1402 
1403  return SettingsContainer::FindEntry(row_num, cur_row);
1404 }
1405 
1420 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
1421 {
1422  if (this->IsFiltered()) return cur_row;
1423  if (cur_row >= max_row) return cur_row;
1424 
1425  cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1426 
1427  if (!this->folded) {
1428  if (this->flags & SEF_LAST_FIELD) {
1429  assert(this->level < 8 * sizeof(parent_last));
1430  SetBit(parent_last, this->level); // Add own last-field state
1431  }
1432 
1433  cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1434  }
1435 
1436  return cur_row;
1437 }
1438 
1447 void SettingsPage::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1448 {
1449  bool rtl = _current_text_dir == TD_RTL;
1450  DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1451  DrawString(rtl ? left : left + _circle_size.width + 2, rtl ? right - _circle_size.width - 2 : right, y + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) / 2, this->title);
1452 }
1453 
1456 {
1457  static SettingsContainer *main = NULL;
1458 
1459  if (main == NULL)
1460  {
1461  /* Build up the dynamic settings-array only once per OpenTTD session */
1462  main = new SettingsContainer();
1463 
1464  SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1465  {
1466  localisation->Add(new SettingEntry("locale.units_velocity"));
1467  localisation->Add(new SettingEntry("locale.units_power"));
1468  localisation->Add(new SettingEntry("locale.units_weight"));
1469  localisation->Add(new SettingEntry("locale.units_volume"));
1470  localisation->Add(new SettingEntry("locale.units_force"));
1471  localisation->Add(new SettingEntry("locale.units_height"));
1472  localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1473  }
1474 
1475  SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1476  {
1477  graphics->Add(new SettingEntry("gui.zoom_min"));
1478  graphics->Add(new SettingEntry("gui.zoom_max"));
1479  graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1480  graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1481  }
1482 
1483  SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1484  {
1485  sound->Add(new SettingEntry("sound.click_beep"));
1486  sound->Add(new SettingEntry("sound.confirm"));
1487  sound->Add(new SettingEntry("sound.news_ticker"));
1488  sound->Add(new SettingEntry("sound.news_full"));
1489  sound->Add(new SettingEntry("sound.new_year"));
1490  sound->Add(new SettingEntry("sound.disaster"));
1491  sound->Add(new SettingEntry("sound.vehicle"));
1492  sound->Add(new SettingEntry("sound.ambient"));
1493  }
1494 
1495  SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1496  {
1497  SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1498  {
1499  general->Add(new SettingEntry("gui.osk_activation"));
1500  general->Add(new SettingEntry("gui.hover_delay_ms"));
1501  general->Add(new SettingEntry("gui.errmsg_duration"));
1502  general->Add(new SettingEntry("gui.window_snap_radius"));
1503  general->Add(new SettingEntry("gui.window_soft_limit"));
1504  }
1505 
1506  SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
1507  {
1508  viewports->Add(new SettingEntry("gui.auto_scrolling"));
1509  viewports->Add(new SettingEntry("gui.reverse_scroll"));
1510  viewports->Add(new SettingEntry("gui.smooth_scroll"));
1511  viewports->Add(new SettingEntry("gui.left_mouse_btn_scrolling"));
1512  /* While the horizontal scrollwheel scrolling is written as general code, only
1513  * the cocoa (OSX) driver generates input for it.
1514  * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
1515  viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
1516  viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
1517 #ifdef __APPLE__
1518  /* We might need to emulate a right mouse button on mac */
1519  viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
1520 #endif
1521  viewports->Add(new SettingEntry("gui.population_in_label"));
1522  viewports->Add(new SettingEntry("gui.liveries"));
1523  viewports->Add(new SettingEntry("construction.train_signal_side"));
1524  viewports->Add(new SettingEntry("gui.measure_tooltip"));
1525  viewports->Add(new SettingEntry("gui.loading_indicators"));
1526  viewports->Add(new SettingEntry("gui.show_track_reservation"));
1527  }
1528 
1529  SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
1530  {
1531  construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
1532  construction->Add(new SettingEntry("gui.enable_signal_gui"));
1533  construction->Add(new SettingEntry("gui.persistent_buildingtools"));
1534  construction->Add(new SettingEntry("gui.quick_goto"));
1535  construction->Add(new SettingEntry("gui.default_rail_type"));
1536  construction->Add(new SettingEntry("gui.disable_unsuitable_building"));
1537  }
1538 
1539  interface->Add(new SettingEntry("gui.autosave"));
1540  interface->Add(new SettingEntry("gui.toolbar_pos"));
1541  interface->Add(new SettingEntry("gui.statusbar_pos"));
1542  interface->Add(new SettingEntry("gui.prefer_teamchat"));
1543  interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
1544  interface->Add(new SettingEntry("gui.timetable_in_ticks"));
1545  interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
1546  interface->Add(new SettingEntry("gui.expenses_layout"));
1547  }
1548 
1549  SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
1550  {
1551  advisors->Add(new SettingEntry("gui.coloured_news_year"));
1552  advisors->Add(new SettingEntry("news_display.general"));
1553  advisors->Add(new SettingEntry("news_display.new_vehicles"));
1554  advisors->Add(new SettingEntry("news_display.accident"));
1555  advisors->Add(new SettingEntry("news_display.company_info"));
1556  advisors->Add(new SettingEntry("news_display.acceptance"));
1557  advisors->Add(new SettingEntry("news_display.arrival_player"));
1558  advisors->Add(new SettingEntry("news_display.arrival_other"));
1559  advisors->Add(new SettingEntry("news_display.advice"));
1560  advisors->Add(new SettingEntry("gui.order_review_system"));
1561  advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
1562  advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
1563  advisors->Add(new SettingEntry("gui.show_finances"));
1564  advisors->Add(new SettingEntry("news_display.economy"));
1565  advisors->Add(new SettingEntry("news_display.subsidies"));
1566  advisors->Add(new SettingEntry("news_display.open"));
1567  advisors->Add(new SettingEntry("news_display.close"));
1568  advisors->Add(new SettingEntry("news_display.production_player"));
1569  advisors->Add(new SettingEntry("news_display.production_other"));
1570  advisors->Add(new SettingEntry("news_display.production_nobody"));
1571  }
1572 
1573  SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
1574  {
1575  company->Add(new SettingEntry("gui.semaphore_build_before"));
1576  company->Add(new SettingEntry("gui.default_signal_type"));
1577  company->Add(new SettingEntry("gui.cycle_signal_types"));
1578  company->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
1579  company->Add(new SettingEntry("gui.new_nonstop"));
1580  company->Add(new SettingEntry("gui.stop_location"));
1581  company->Add(new SettingEntry("company.engine_renew"));
1582  company->Add(new SettingEntry("company.engine_renew_months"));
1583  company->Add(new SettingEntry("company.engine_renew_money"));
1584  company->Add(new SettingEntry("vehicle.servint_ispercent"));
1585  company->Add(new SettingEntry("vehicle.servint_trains"));
1586  company->Add(new SettingEntry("vehicle.servint_roadveh"));
1587  company->Add(new SettingEntry("vehicle.servint_ships"));
1588  company->Add(new SettingEntry("vehicle.servint_aircraft"));
1589  }
1590 
1591  SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
1592  {
1593  accounting->Add(new SettingEntry("economy.inflation"));
1594  accounting->Add(new SettingEntry("difficulty.initial_interest"));
1595  accounting->Add(new SettingEntry("difficulty.max_loan"));
1596  accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
1597  accounting->Add(new SettingEntry("economy.feeder_payment_share"));
1598  accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
1599  accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
1600  accounting->Add(new SettingEntry("difficulty.construction_cost"));
1601  }
1602 
1603  SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
1604  {
1605  SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
1606  {
1607  physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
1608  physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
1609  physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
1610  physics->Add(new SettingEntry("vehicle.freight_trains"));
1611  physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
1612  physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
1613  physics->Add(new SettingEntry("vehicle.smoke_amount"));
1614  physics->Add(new SettingEntry("vehicle.plane_speed"));
1615  }
1616 
1617  SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
1618  {
1619  routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
1620  routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
1621  routing->Add(new SettingEntry("pf.reverse_at_signals"));
1622  routing->Add(new SettingEntry("pf.forbid_90_deg"));
1623  routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
1624  routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
1625  }
1626 
1627  vehicles->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
1628  vehicles->Add(new SettingEntry("order.serviceathelipad"));
1629  }
1630 
1631  SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
1632  {
1633  limitations->Add(new SettingEntry("construction.command_pause_level"));
1634  limitations->Add(new SettingEntry("construction.autoslope"));
1635  limitations->Add(new SettingEntry("construction.extra_dynamite"));
1636  limitations->Add(new SettingEntry("construction.max_heightlevel"));
1637  limitations->Add(new SettingEntry("construction.max_bridge_length"));
1638  limitations->Add(new SettingEntry("construction.max_bridge_height"));
1639  limitations->Add(new SettingEntry("construction.max_tunnel_length"));
1640  limitations->Add(new SettingEntry("station.never_expire_airports"));
1641  limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
1642  limitations->Add(new SettingEntry("vehicle.max_trains"));
1643  limitations->Add(new SettingEntry("vehicle.max_roadveh"));
1644  limitations->Add(new SettingEntry("vehicle.max_aircraft"));
1645  limitations->Add(new SettingEntry("vehicle.max_ships"));
1646  limitations->Add(new SettingEntry("vehicle.max_train_length"));
1647  limitations->Add(new SettingEntry("station.station_spread"));
1648  limitations->Add(new SettingEntry("station.distant_join_stations"));
1649  limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
1650  limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
1651  limitations->Add(new SettingEntry("vehicle.disable_elrails"));
1652  }
1653 
1654  SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
1655  {
1656  disasters->Add(new SettingEntry("difficulty.disasters"));
1657  disasters->Add(new SettingEntry("difficulty.economy"));
1658  disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
1659  disasters->Add(new SettingEntry("vehicle.plane_crashes"));
1660  }
1661 
1662  SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
1663  {
1664  genworld->Add(new SettingEntry("game_creation.landscape"));
1665  genworld->Add(new SettingEntry("game_creation.land_generator"));
1666  genworld->Add(new SettingEntry("difficulty.terrain_type"));
1667  genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
1668  genworld->Add(new SettingEntry("game_creation.variety"));
1669  genworld->Add(new SettingEntry("game_creation.snow_line_height"));
1670  genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
1671  genworld->Add(new SettingEntry("game_creation.tree_placer"));
1672  genworld->Add(new SettingEntry("vehicle.road_side"));
1673  genworld->Add(new SettingEntry("economy.larger_towns"));
1674  genworld->Add(new SettingEntry("economy.initial_city_size"));
1675  genworld->Add(new SettingEntry("economy.town_layout"));
1676  genworld->Add(new SettingEntry("difficulty.industry_density"));
1677  genworld->Add(new SettingEntry("gui.pause_on_newgame"));
1678  }
1679 
1680  SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
1681  {
1682  SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
1683  {
1684  authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
1685  authorities->Add(new SettingEntry("economy.bribe"));
1686  authorities->Add(new SettingEntry("economy.exclusive_rights"));
1687  authorities->Add(new SettingEntry("economy.fund_roads"));
1688  authorities->Add(new SettingEntry("economy.fund_buildings"));
1689  authorities->Add(new SettingEntry("economy.station_noise_level"));
1690  }
1691 
1692  SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
1693  {
1694  towns->Add(new SettingEntry("economy.town_growth_rate"));
1695  towns->Add(new SettingEntry("economy.allow_town_roads"));
1696  towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
1697  towns->Add(new SettingEntry("economy.found_town"));
1698  }
1699 
1700  SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
1701  {
1702  industries->Add(new SettingEntry("construction.raw_industry_construction"));
1703  industries->Add(new SettingEntry("construction.industry_platform"));
1704  industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
1705  industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
1706  industries->Add(new SettingEntry("economy.smooth_economy"));
1707  }
1708 
1709  SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
1710  {
1711  cdist->Add(new SettingEntry("linkgraph.recalc_time"));
1712  cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
1713  cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
1714  cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
1715  cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
1716  cdist->Add(new SettingEntry("linkgraph.distribution_default"));
1717  cdist->Add(new SettingEntry("linkgraph.accuracy"));
1718  cdist->Add(new SettingEntry("linkgraph.demand_distance"));
1719  cdist->Add(new SettingEntry("linkgraph.demand_size"));
1720  cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
1721  }
1722 
1723  environment->Add(new SettingEntry("station.modified_catchment"));
1724  environment->Add(new SettingEntry("construction.extra_tree_placement"));
1725  }
1726 
1727  SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
1728  {
1729  SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
1730  {
1731  npc->Add(new SettingEntry("script.settings_profile"));
1732  npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
1733  npc->Add(new SettingEntry("difficulty.competitor_speed"));
1734  npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
1735  npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
1736  npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
1737  npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
1738  npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
1739  }
1740 
1741  ai->Add(new SettingEntry("economy.give_money"));
1742  ai->Add(new SettingEntry("economy.allow_shares"));
1743  }
1744 
1745  main->Init();
1746  }
1747  return *main;
1748 }
1749 
1750 static const StringID _game_settings_restrict_dropdown[] = {
1751  STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
1752  STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
1753  STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
1754  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
1755  STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
1756 };
1757 assert_compile(lengthof(_game_settings_restrict_dropdown) == RM_END);
1758 
1765 };
1766 
1769  static const int SETTINGTREE_LEFT_OFFSET = 5;
1770  static const int SETTINGTREE_RIGHT_OFFSET = 5;
1771  static const int SETTINGTREE_TOP_OFFSET = 5;
1772  static const int SETTINGTREE_BOTTOM_OFFSET = 5;
1773 
1775 
1781 
1787 
1788  Scrollbar *vscroll;
1789 
1791  {
1792  this->warn_missing = WHR_NONE;
1793  this->warn_lines = 0;
1795  this->filter.min_cat = RM_ALL;
1796  this->filter.type = ST_ALL;
1797  this->filter.type_hides = false;
1798  this->settings_ptr = &GetGameSettings();
1799 
1800  _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
1801  GetSettingsTree().FoldAll(); // Close all sub-pages
1802 
1803  this->valuewindow_entry = NULL; // No setting entry for which a entry window is opened
1804  this->clicked_entry = NULL; // No numeric setting buttons are depressed
1805  this->last_clicked = NULL;
1806  this->valuedropdown_entry = NULL;
1807  this->closing_dropdown = false;
1808  this->manually_changed_folding = false;
1809 
1810  this->CreateNestedTree();
1811  this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
1813 
1814  this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
1817 
1818  this->InvalidateData();
1819  }
1820 
1821  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1822  {
1823  switch (widget) {
1824  case WID_GS_OPTIONSPANEL:
1825  resize->height = SETTING_HEIGHT = max(max<int>(_circle_size.height, SETTING_BUTTON_HEIGHT), FONT_HEIGHT_NORMAL) + 1;
1826  resize->width = 1;
1827 
1828  size->height = 5 * resize->height + SETTINGTREE_TOP_OFFSET + SETTINGTREE_BOTTOM_OFFSET;
1829  break;
1830 
1831  case WID_GS_HELP_TEXT: {
1832  static const StringID setting_types[] = {
1833  STR_CONFIG_SETTING_TYPE_CLIENT,
1834  STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
1835  STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
1836  };
1837  for (uint i = 0; i < lengthof(setting_types); i++) {
1838  SetDParam(0, setting_types[i]);
1839  size->width = max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width);
1840  }
1841  size->height = 2 * FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL +
1842  max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
1843  break;
1844  }
1845 
1847  case WID_GS_RESTRICT_TYPE:
1848  size->width = max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
1849  break;
1850 
1851  default:
1852  break;
1853  }
1854  }
1855 
1856  virtual void OnPaint()
1857  {
1858  if (this->closing_dropdown) {
1859  this->closing_dropdown = false;
1860  assert(this->valuedropdown_entry != NULL);
1861  this->valuedropdown_entry->SetButtons(0);
1862  this->valuedropdown_entry = NULL;
1863  }
1864 
1865  /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
1866  const NWidgetBase *panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
1867  StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
1868  int new_warn_lines;
1869  if (this->warn_missing == WHR_NONE) {
1870  new_warn_lines = 0;
1871  } else {
1872  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1873  new_warn_lines = GetStringLineCount(warn_str, panel->current_x);
1874  }
1875  if (this->warn_lines != new_warn_lines) {
1876  this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
1877  this->warn_lines = new_warn_lines;
1878  }
1879 
1880  this->DrawWidgets();
1881 
1882  /* Draw the 'some search results are hidden' notice. */
1883  if (this->warn_missing != WHR_NONE) {
1884  const int left = panel->pos_x;
1885  const int right = left + panel->current_x - 1;
1886  const int top = panel->pos_y + WD_FRAMETEXT_TOP + (SETTING_HEIGHT - FONT_HEIGHT_NORMAL) * this->warn_lines / 2;
1887  SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
1888  if (this->warn_lines == 1) {
1889  /* If the warning fits at one line, center it. */
1890  DrawString(left + WD_FRAMETEXT_LEFT, right - WD_FRAMETEXT_RIGHT, top, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1891  } else {
1892  DrawStringMultiLine(left + WD_FRAMERECT_LEFT, right - WD_FRAMERECT_RIGHT, top, INT32_MAX, warn_str, TC_FROMSTRING, SA_HOR_CENTER);
1893  }
1894  }
1895  }
1896 
1897  virtual void SetStringParameters(int widget) const
1898  {
1899  switch (widget) {
1901  SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
1902  break;
1903 
1904  case WID_GS_TYPE_DROPDOWN:
1905  switch (this->filter.type) {
1906  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
1907  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
1908  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
1909  default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
1910  }
1911  break;
1912  }
1913  }
1914 
1915  DropDownList *BuildDropDownList(int widget) const
1916  {
1917  DropDownList *list = NULL;
1918  switch (widget) {
1920  list = new DropDownList();
1921 
1922  for (int mode = 0; mode != RM_END; mode++) {
1923  /* If we are in adv. settings screen for the new game's settings,
1924  * we don't want to allow comparing with new game's settings. */
1925  bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
1926 
1927  *list->Append() = new DropDownListStringItem(_game_settings_restrict_dropdown[mode], mode, disabled);
1928  }
1929  break;
1930 
1931  case WID_GS_TYPE_DROPDOWN:
1932  list = new DropDownList();
1933  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false);
1934  *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);
1935  *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);
1936  *list->Append() = new DropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false);
1937  break;
1938  }
1939  return list;
1940  }
1941 
1942  virtual void DrawWidget(const Rect &r, int widget) const
1943  {
1944  switch (widget) {
1945  case WID_GS_OPTIONSPANEL: {
1946  int top_pos = r.top + SETTINGTREE_TOP_OFFSET + 1 + this->warn_lines * SETTING_HEIGHT;
1947  uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
1948  int next_row = GetSettingsTree().Draw(settings_ptr, r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos,
1949  this->vscroll->GetPosition(), last_row, this->last_clicked);
1950  if (next_row == 0) DrawString(r.left + SETTINGTREE_LEFT_OFFSET, r.right - SETTINGTREE_RIGHT_OFFSET, top_pos, STR_CONFIG_SETTINGS_NONE);
1951  break;
1952  }
1953 
1954  case WID_GS_HELP_TEXT:
1955  if (this->last_clicked != NULL) {
1956  const SettingDesc *sd = this->last_clicked->setting;
1957 
1958  int y = r.top;
1959  switch (sd->GetType()) {
1960  case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
1961  case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
1962  case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
1963  default: NOT_REACHED();
1964  }
1965  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_TYPE);
1966  y += FONT_HEIGHT_NORMAL;
1967 
1968  int32 default_value = ReadValue(&sd->desc.def, sd->save.conv);
1969  this->last_clicked->SetValueDParams(0, default_value);
1970  DrawString(r.left, r.right, y, STR_CONFIG_SETTING_DEFAULT_VALUE);
1972 
1973  DrawStringMultiLine(r.left, r.right, y, r.bottom, this->last_clicked->GetHelpText(), TC_WHITE);
1974  }
1975  break;
1976 
1977  default:
1978  break;
1979  }
1980  }
1981 
1987  {
1988  if (this->last_clicked != pe) this->SetDirty();
1989  this->last_clicked = pe;
1990  }
1991 
1992  virtual void OnClick(Point pt, int widget, int click_count)
1993  {
1994  switch (widget) {
1995  case WID_GS_EXPAND_ALL:
1996  this->manually_changed_folding = true;
1998  this->InvalidateData();
1999  break;
2000 
2001  case WID_GS_COLLAPSE_ALL:
2002  this->manually_changed_folding = true;
2004  this->InvalidateData();
2005  break;
2006 
2007  case WID_GS_RESTRICT_DROPDOWN: {
2008  DropDownList *list = this->BuildDropDownList(widget);
2009  if (list != NULL) {
2010  ShowDropDownList(this, list, this->filter.mode, widget);
2011  }
2012  break;
2013  }
2014 
2015  case WID_GS_TYPE_DROPDOWN: {
2016  DropDownList *list = this->BuildDropDownList(widget);
2017  if (list != NULL) {
2018  ShowDropDownList(this, list, this->filter.type, widget);
2019  }
2020  break;
2021  }
2022  }
2023 
2024  if (widget != WID_GS_OPTIONSPANEL) return;
2025 
2026  uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, SETTINGTREE_TOP_OFFSET);
2027  if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2028  btn -= this->warn_lines;
2029 
2030  uint cur_row = 0;
2032 
2033  if (clicked_entry == NULL) return; // Clicked below the last setting of the page
2034 
2035  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
2036  if (x < 0) return; // Clicked left of the entry
2037 
2038  SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2039  if (clicked_page != NULL) {
2040  this->SetDisplayedHelpText(NULL);
2041  clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2042 
2043  this->manually_changed_folding = true;
2044 
2045  this->InvalidateData();
2046  return;
2047  }
2048 
2049  SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2050  assert(pe != NULL);
2051  const SettingDesc *sd = pe->setting;
2052 
2053  /* return if action is only active in network, or only settable by server */
2054  if (!sd->IsEditable()) {
2055  this->SetDisplayedHelpText(pe);
2056  return;
2057  }
2058 
2059  const void *var = ResolveVariableAddress(settings_ptr, sd);
2060  int32 value = (int32)ReadValue(var, sd->save.conv);
2061 
2062  /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2063  if (x < SETTING_BUTTON_WIDTH && (sd->desc.flags & SGF_MULTISTRING)) {
2064  const SettingDescBase *sdb = &sd->desc;
2065  this->SetDisplayedHelpText(pe);
2066 
2067  if (this->valuedropdown_entry == pe) {
2068  /* unclick the dropdown */
2069  HideDropDownMenu(this);
2070  this->closing_dropdown = false;
2071  this->valuedropdown_entry->SetButtons(0);
2072  this->valuedropdown_entry = NULL;
2073  } else {
2074  if (this->valuedropdown_entry != NULL) this->valuedropdown_entry->SetButtons(0);
2075  this->closing_dropdown = false;
2076 
2077  const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2078  int rel_y = (pt.y - (int)wid->pos_y - SETTINGTREE_TOP_OFFSET) % wid->resize_y;
2079 
2080  Rect wi_rect;
2081  wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2082  wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2083  wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2084  wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2085 
2086  /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2087  if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2088  this->valuedropdown_entry = pe;
2090 
2091  DropDownList *list = new DropDownList();
2092  for (int i = sdb->min; i <= (int)sdb->max; i++) {
2093  *list->Append() = new DropDownListStringItem(sdb->str_val + i - sdb->min, i, false);
2094  }
2095 
2096  ShowDropDownListAt(this, list, value, -1, wi_rect, COLOUR_ORANGE, true);
2097  }
2098  }
2099  this->SetDirty();
2100  } else if (x < SETTING_BUTTON_WIDTH) {
2101  this->SetDisplayedHelpText(pe);
2102  const SettingDescBase *sdb = &sd->desc;
2103  int32 oldvalue = value;
2104 
2105  switch (sdb->cmd) {
2106  case SDT_BOOLX: value ^= 1; break;
2107  case SDT_ONEOFMANY:
2108  case SDT_NUMX: {
2109  /* Add a dynamic step-size to the scroller. In a maximum of
2110  * 50-steps you should be able to get from min to max,
2111  * unless specified otherwise in the 'interval' variable
2112  * of the current setting. */
2113  uint32 step = (sdb->interval == 0) ? ((sdb->max - sdb->min) / 50) : sdb->interval;
2114  if (step == 0) step = 1;
2115 
2116  /* don't allow too fast scrolling */
2117  if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2118  _left_button_clicked = false;
2119  return;
2120  }
2121 
2122  /* Increase or decrease the value and clamp it to extremes */
2123  if (x >= SETTING_BUTTON_WIDTH / 2) {
2124  value += step;
2125  if (sdb->min < 0) {
2126  assert((int32)sdb->max >= 0);
2127  if (value > (int32)sdb->max) value = (int32)sdb->max;
2128  } else {
2129  if ((uint32)value > sdb->max) value = (int32)sdb->max;
2130  }
2131  if (value < sdb->min) value = sdb->min; // skip between "disabled" and minimum
2132  } else {
2133  value -= step;
2134  if (value < sdb->min) value = (sdb->flags & SGF_0ISDISABLED) ? 0 : sdb->min;
2135  }
2136 
2137  /* Set up scroller timeout for numeric values */
2138  if (value != oldvalue) {
2139  if (this->clicked_entry != NULL) { // Release previous buttons if any
2140  this->clicked_entry->SetButtons(0);
2141  }
2142  this->clicked_entry = pe;
2143  this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2144  this->SetTimeout();
2145  _left_button_clicked = false;
2146  }
2147  break;
2148  }
2149 
2150  default: NOT_REACHED();
2151  }
2152 
2153  if (value != oldvalue) {
2154  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2155  SetCompanySetting(pe->index, value);
2156  } else {
2157  SetSettingValue(pe->index, value);
2158  }
2159  this->SetDirty();
2160  }
2161  } else {
2162  /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2163  if (this->last_clicked == pe && sd->desc.cmd != SDT_BOOLX && !(sd->desc.flags & SGF_MULTISTRING)) {
2164  /* Show the correct currency-translated value */
2165  if (sd->desc.flags & SGF_CURRENCY) value *= _currency->rate;
2166 
2167  this->valuewindow_entry = pe;
2168  SetDParam(0, value);
2169  ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_ENABLE_DEFAULT);
2170  }
2171  this->SetDisplayedHelpText(pe);
2172  }
2173  }
2174 
2175  virtual void OnTimeout()
2176  {
2177  if (this->clicked_entry != NULL) { // On timeout, release any depressed buttons
2178  this->clicked_entry->SetButtons(0);
2179  this->clicked_entry = NULL;
2180  this->SetDirty();
2181  }
2182  }
2183 
2184  virtual void OnQueryTextFinished(char *str)
2185  {
2186  /* The user pressed cancel */
2187  if (str == NULL) return;
2188 
2189  assert(this->valuewindow_entry != NULL);
2190  const SettingDesc *sd = this->valuewindow_entry->setting;
2191 
2192  int32 value;
2193  if (!StrEmpty(str)) {
2194  value = atoi(str);
2195 
2196  /* Save the correct currency-translated value */
2197  if (sd->desc.flags & SGF_CURRENCY) value /= _currency->rate;
2198  } else {
2199  value = (int32)(size_t)sd->desc.def;
2200  }
2201 
2202  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2203  SetCompanySetting(this->valuewindow_entry->index, value);
2204  } else {
2205  SetSettingValue(this->valuewindow_entry->index, value);
2206  }
2207  this->SetDirty();
2208  }
2209 
2210  virtual void OnDropdownSelect(int widget, int index)
2211  {
2212  switch (widget) {
2214  this->filter.mode = (RestrictionMode)index;
2215  if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2216  this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2217 
2218  if (!this->manually_changed_folding) {
2219  /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2220  GetSettingsTree().UpdateFilterState(this->filter, false);
2222  }
2223  } else {
2224  /* Non-'changes' filter. Save as default. */
2226  }
2227  this->InvalidateData();
2228  break;
2229 
2230  case WID_GS_TYPE_DROPDOWN:
2231  this->filter.type = (SettingType)index;
2232  this->InvalidateData();
2233  break;
2234 
2235  default:
2236  if (widget < 0) {
2237  /* Deal with drop down boxes on the panel. */
2238  assert(this->valuedropdown_entry != NULL);
2239  const SettingDesc *sd = this->valuedropdown_entry->setting;
2240  assert(sd->desc.flags & SGF_MULTISTRING);
2241 
2242  if ((sd->desc.flags & SGF_PER_COMPANY) != 0) {
2244  } else {
2245  SetSettingValue(this->valuedropdown_entry->index, index);
2246  }
2247 
2248  this->SetDirty();
2249  }
2250  break;
2251  }
2252  }
2253 
2254  virtual void OnDropdownClose(Point pt, int widget, int index, bool instant_close)
2255  {
2256  if (widget >= 0) {
2257  /* Normally the default implementation of OnDropdownClose() takes care of
2258  * a few things. We want that behaviour here too, but only for
2259  * "normal" dropdown boxes. The special dropdown boxes added for every
2260  * setting that needs one can't have this call. */
2261  Window::OnDropdownClose(pt, widget, index, instant_close);
2262  } else {
2263  /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2264  * the same dropdown button was clicked again, and then not open the dropdown again.
2265  * So, we only remember that it was closed, and process it on the next OnPaint, which is
2266  * after OnClick. */
2267  assert(this->valuedropdown_entry != NULL);
2268  this->closing_dropdown = true;
2269  this->SetDirty();
2270  }
2271  }
2272 
2273  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2274  {
2275  if (!gui_scope) return;
2276 
2277  /* Update which settings are to be visible. */
2278  RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2279  this->filter.min_cat = min_level;
2280  this->filter.type_hides = false;
2281  GetSettingsTree().UpdateFilterState(this->filter, false);
2282 
2283  if (this->filter.string.IsEmpty()) {
2284  this->warn_missing = WHR_NONE;
2285  } else if (min_level < this->filter.min_cat) {
2287  } else {
2288  this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2289  }
2290  this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2291 
2292  if (this->last_clicked != NULL && !GetSettingsTree().IsVisible(this->last_clicked)) {
2293  this->SetDisplayedHelpText(NULL);
2294  }
2295 
2296  bool all_folded = true;
2297  bool all_unfolded = true;
2298  GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2299  this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2300  this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2301  }
2302 
2303  virtual void OnEditboxChanged(int wid)
2304  {
2305  if (wid == WID_GS_FILTER) {
2306  this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2307  if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2308  /* User never expanded/collapsed single pages and entered a filter term.
2309  * Expand everything, to save weird expand clicks, */
2311  }
2312  this->InvalidateData();
2313  }
2314  }
2315 
2316  virtual void OnResize()
2317  {
2319  }
2320 };
2321 
2323 
2324 static const NWidgetPart _nested_settings_selection_widgets[] = {
2326  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2327  NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2328  NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2329  EndContainer(),
2330  NWidget(WWT_PANEL, COLOUR_MAUVE),
2333  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2334  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),
2335  EndContainer(),
2337  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2338  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),
2339  EndContainer(),
2340  EndContainer(),
2343  NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2344  NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
2345  SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
2346  EndContainer(),
2347  EndContainer(),
2350  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2351  EndContainer(),
2352  NWidget(WWT_PANEL, COLOUR_MAUVE), SetMinimalSize(400, 40),
2353  NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2355  EndContainer(),
2357  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2358  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2359  NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2360  EndContainer(),
2361  NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2362  EndContainer(),
2363 };
2364 
2365 static WindowDesc _settings_selection_desc(
2366  WDP_CENTER, "settings", 510, 450,
2368  0,
2369  _nested_settings_selection_widgets, lengthof(_nested_settings_selection_widgets)
2370 );
2371 
2374 {
2376  new GameSettingsWindow(&_settings_selection_desc);
2377 }
2378 
2379 
2389 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2390 {
2391  int colour = _colour_gradient[button_colour][2];
2392  Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2393 
2394  DrawFrameRect(x, y, x + dim.width - 1, y + dim.height - 1, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2395  DrawFrameRect(x + dim.width, y, x + dim.width + dim.width - 1, y + dim.height - 1, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2396  DrawSprite(SPR_ARROW_LEFT, PAL_NONE, x + WD_IMGBTN_LEFT, y + WD_IMGBTN_TOP);
2397  DrawSprite(SPR_ARROW_RIGHT, PAL_NONE, x + WD_IMGBTN_LEFT + dim.width, y + WD_IMGBTN_TOP);
2398 
2399  /* Grey out the buttons that aren't clickable */
2400  bool rtl = _current_text_dir == TD_RTL;
2401  if (rtl ? !clickable_right : !clickable_left) {
2402  GfxFillRect(x + 1, y, x + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2403  }
2404  if (rtl ? !clickable_left : !clickable_right) {
2405  GfxFillRect(x + dim.width + 1, y, x + dim.width + dim.width - 1, y + dim.height - 2, colour, FILLRECT_CHECKER);
2406  }
2407 }
2408 
2417 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2418 {
2419  int colour = _colour_gradient[button_colour][2];
2420 
2421  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, button_colour, state ? FR_LOWERED : FR_NONE);
2422  DrawSprite(SPR_ARROW_DOWN, PAL_NONE, x + (SETTING_BUTTON_WIDTH - NWidgetScrollbar::GetVerticalDimension().width) / 2 + state, y + 2 + state);
2423 
2424  if (!clickable) {
2425  GfxFillRect(x + 1, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 2, colour, FILLRECT_CHECKER);
2426  }
2427 }
2428 
2436 void DrawBoolButton(int x, int y, bool state, bool clickable)
2437 {
2438  static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2439  DrawFrameRect(x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2440 }
2441 
2443  int query_widget;
2444 
2445  CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2446  {
2447  this->InitNested();
2448 
2449  SetButtonState();
2450  }
2451 
2452  void SetButtonState()
2453  {
2454  this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2455  this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2456  this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2457  this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == MAX_YEAR);
2458  }
2459 
2460  virtual void SetStringParameters(int widget) const
2461  {
2462  switch (widget) {
2463  case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2464  case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2465  case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2466  case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2467  case WID_CC_YEAR:
2468  SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2469  SetDParam(1, _custom_currency.to_euro);
2470  break;
2471 
2472  case WID_CC_PREVIEW:
2473  SetDParam(0, 10000);
2474  break;
2475  }
2476  }
2477 
2478  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2479  {
2480  switch (widget) {
2481  /* Set the appropriate width for the edit 'buttons' */
2482  case WID_CC_SEPARATOR_EDIT:
2483  case WID_CC_PREFIX_EDIT:
2484  case WID_CC_SUFFIX_EDIT:
2485  size->width = this->GetWidget<NWidgetBase>(WID_CC_RATE_DOWN)->smallest_x + this->GetWidget<NWidgetBase>(WID_CC_RATE_UP)->smallest_x;
2486  break;
2487 
2488  /* Make sure the window is wide enough for the widest exchange rate */
2489  case WID_CC_RATE:
2490  SetDParam(0, 1);
2491  SetDParam(1, INT32_MAX);
2492  *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
2493  break;
2494  }
2495  }
2496 
2497  virtual void OnClick(Point pt, int widget, int click_count)
2498  {
2499  int line = 0;
2500  int len = 0;
2501  StringID str = 0;
2502  CharSetFilter afilter = CS_ALPHANUMERAL;
2503 
2504  switch (widget) {
2505  case WID_CC_RATE_DOWN:
2506  if (_custom_currency.rate > 1) _custom_currency.rate--;
2507  if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
2509  break;
2510 
2511  case WID_CC_RATE_UP:
2512  if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
2513  if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
2515  break;
2516 
2517  case WID_CC_RATE:
2518  SetDParam(0, _custom_currency.rate);
2519  str = STR_JUST_INT;
2520  len = 5;
2521  line = WID_CC_RATE;
2522  afilter = CS_NUMERAL;
2523  break;
2524 
2525  case WID_CC_SEPARATOR_EDIT:
2526  case WID_CC_SEPARATOR:
2527  SetDParamStr(0, _custom_currency.separator);
2528  str = STR_JUST_RAW_STRING;
2529  len = 1;
2530  line = WID_CC_SEPARATOR;
2531  break;
2532 
2533  case WID_CC_PREFIX_EDIT:
2534  case WID_CC_PREFIX:
2535  SetDParamStr(0, _custom_currency.prefix);
2536  str = STR_JUST_RAW_STRING;
2537  len = 12;
2538  line = WID_CC_PREFIX;
2539  break;
2540 
2541  case WID_CC_SUFFIX_EDIT:
2542  case WID_CC_SUFFIX:
2543  SetDParamStr(0, _custom_currency.suffix);
2544  str = STR_JUST_RAW_STRING;
2545  len = 12;
2546  line = WID_CC_SUFFIX;
2547  break;
2548 
2549  case WID_CC_YEAR_DOWN:
2550  _custom_currency.to_euro = (_custom_currency.to_euro <= 2000) ? CF_NOEURO : _custom_currency.to_euro - 1;
2551  if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
2553  break;
2554 
2555  case WID_CC_YEAR_UP:
2556  _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, 2000, MAX_YEAR);
2557  if (_custom_currency.to_euro == MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
2559  break;
2560 
2561  case WID_CC_YEAR:
2562  SetDParam(0, _custom_currency.to_euro);
2563  str = STR_JUST_INT;
2564  len = 7;
2565  line = WID_CC_YEAR;
2566  afilter = CS_NUMERAL;
2567  break;
2568  }
2569 
2570  if (len != 0) {
2571  this->query_widget = line;
2572  ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
2573  }
2574 
2575  this->SetTimeout();
2576  this->SetDirty();
2577  }
2578 
2579  virtual void OnQueryTextFinished(char *str)
2580  {
2581  if (str == NULL) return;
2582 
2583  switch (this->query_widget) {
2584  case WID_CC_RATE:
2585  _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
2586  break;
2587 
2588  case WID_CC_SEPARATOR: // Thousands separator
2589  strecpy(_custom_currency.separator, str, lastof(_custom_currency.separator));
2590  break;
2591 
2592  case WID_CC_PREFIX:
2593  strecpy(_custom_currency.prefix, str, lastof(_custom_currency.prefix));
2594  break;
2595 
2596  case WID_CC_SUFFIX:
2597  strecpy(_custom_currency.suffix, str, lastof(_custom_currency.suffix));
2598  break;
2599 
2600  case WID_CC_YEAR: { // Year to switch to euro
2601  int val = atoi(str);
2602 
2603  _custom_currency.to_euro = (val < 2000 ? CF_NOEURO : min(val, MAX_YEAR));
2604  break;
2605  }
2606  }
2608  SetButtonState();
2609  }
2610 
2611  virtual void OnTimeout()
2612  {
2613  this->SetDirty();
2614  }
2615 };
2616 
2617 static const NWidgetPart _nested_cust_currency_widgets[] = {
2619  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2620  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2621  EndContainer(),
2622  NWidget(WWT_PANEL, COLOUR_GREY),
2624  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2625  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
2626  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
2628  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
2629  EndContainer(),
2630  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2631  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
2633  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
2634  EndContainer(),
2635  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2636  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
2638  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
2639  EndContainer(),
2640  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2641  NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
2643  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
2644  EndContainer(),
2645  NWidget(NWID_HORIZONTAL), SetPIP(10, 0, 5),
2646  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2647  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
2649  NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
2650  EndContainer(),
2651  EndContainer(),
2652  NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
2653  SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP), SetPadding(15, 1, 18, 2),
2654  EndContainer(),
2655 };
2656 
2657 static WindowDesc _cust_currency_desc(
2658  WDP_CENTER, NULL, 0, 0,
2660  0,
2661  _nested_cust_currency_widgets, lengthof(_nested_cust_currency_widgets)
2662 );
2663 
2665 static void ShowCustCurrency()
2666 {
2668  new CustomCurrencyWindow(&_cust_currency_desc);
2669 }