Make DeleteStaleLinks static
[openttd/fttd.git] / src / newgrf_gui.cpp
blob9ff1fc3fb6ed2a5a23867214c72ed8de2f41ad4a
1 /* $Id$ */
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 */
10 /** @file newgrf_gui.cpp GUI to change NewGRF settings. */
12 #include "stdafx.h"
13 #include "error.h"
14 #include "string.h"
15 #include "settings_gui.h"
16 #include "newgrf.h"
17 #include "strings_func.h"
18 #include "window_func.h"
19 #include "gamelog.h"
20 #include "settings_type.h"
21 #include "settings_func.h"
22 #include "widgets/dropdown_type.h"
23 #include "widgets/dropdown_func.h"
24 #include "network/network.h"
25 #include "network/network_content.h"
26 #include "sortlist_type.h"
27 #include "stringfilter_type.h"
28 #include "querystring_gui.h"
29 #include "core/geometry_func.hpp"
30 #include "newgrf_text.h"
31 #include "textfile.h"
32 #include "tilehighlight_func.h"
33 #include "fios.h"
35 #include "widgets/newgrf_widget.h"
36 #include "widgets/misc_widget.h"
38 #include "table/sprites.h"
40 #include <map>
42 /* Maximum number of NewGRFs that may be loaded. Six reserved slots are:
43 * 0 - config, 1 - sound, 2 - base, 3 - logos, 4 - climate, 5 - extra */
44 static const int MAX_NEWGRFS = MAX_FILE_SLOTS - 6;
46 /**
47 * Show the first NewGRF error we can find.
49 void ShowNewGRFError()
51 /* Do not show errors when entering the main screen */
52 if (_game_mode == GM_MENU) return;
54 for (const GRFConfig *c = _grfconfig; c != NULL; c = c->next) {
55 /* We only want to show fatal errors */
56 if (c->error == NULL || c->error->severity != STR_NEWGRF_ERROR_MSG_FATAL) continue;
58 SetDParam (0, c->error->custom_message == NULL ? c->error->message : STR_JUST_RAW_STRING);
59 SetDParamStr(1, c->error->custom_message);
60 SetDParamStr(2, c->filename);
61 SetDParamStr(3, c->error->data);
62 for (uint i = 0; i < lengthof(c->error->param_value); i++) {
63 SetDParam(4 + i, c->error->param_value[i]);
65 ShowErrorMessage(STR_NEWGRF_ERROR_FATAL_POPUP, INVALID_STRING_ID, WL_CRITICAL);
66 break;
70 static void ShowNewGRFInfo (const GRFConfig *c, BlitArea *dpi,
71 uint x, uint y, uint right, uint bottom, bool show_params)
73 if (c->error != NULL) {
74 char message[512];
75 SetDParamStr(0, c->error->custom_message); // is skipped by built-in messages
76 SetDParamStr(1, c->filename);
77 SetDParamStr(2, c->error->data);
78 for (uint i = 0; i < lengthof(c->error->param_value); i++) {
79 SetDParam(3 + i, c->error->param_value[i]);
81 GetString (message, c->error->custom_message == NULL ? c->error->message : STR_JUST_RAW_STRING);
83 SetDParamStr(0, message);
84 y = DrawStringMultiLine (dpi, x, right, y, bottom, c->error->severity);
87 /* Draw filename or not if it is not known (GRF sent over internet) */
88 if (c->filename != NULL) {
89 SetDParamStr(0, c->filename);
90 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_FILENAME);
93 /* Prepare and draw GRF ID */
94 char buff [36];
95 bstrfmt (buff, "%08X", BSWAP32(c->ident.grfid));
96 SetDParamStr(0, buff);
97 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_GRF_ID);
99 if ((_settings_client.gui.newgrf_developer_tools || _settings_client.gui.newgrf_show_old_versions) && c->version != 0) {
100 SetDParam(0, c->version);
101 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_VERSION);
103 if ((_settings_client.gui.newgrf_developer_tools || _settings_client.gui.newgrf_show_old_versions) && c->min_loadable_version != 0) {
104 SetDParam(0, c->min_loadable_version);
105 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_MIN_VERSION);
108 /* Prepare and draw MD5 sum */
109 md5sumToString (buff, c->ident.md5sum);
110 SetDParamStr(0, buff);
111 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_MD5SUM);
113 /* Show GRF parameter list */
114 if (show_params) {
115 sstring<512> buff;
116 if (c->num_params > 0) {
117 GRFBuildParamList (&buff, c);
118 SetDParam(0, STR_JUST_RAW_STRING);
119 SetDParamStr (1, buff.c_str());
120 } else {
121 SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE);
123 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_PARAMETER);
125 /* Draw the palette of the NewGRF */
126 if (c->palette & GRFP_BLT_32BPP) {
127 SetDParamStr(0, (c->palette & GRFP_USE_WINDOWS) ? "Legacy (W) / 32 bpp" : "Default (D) / 32 bpp");
128 } else {
129 SetDParamStr(0, (c->palette & GRFP_USE_WINDOWS) ? "Legacy (W)" : "Default (D)");
131 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_PALETTE);
134 /* Show flags */
135 if (c->status == GCS_NOT_FOUND) y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_NOT_FOUND);
136 if (c->status == GCS_DISABLED) y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_DISABLED);
137 if (HasBit(c->flags, GCF_INVALID)) y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_INCOMPATIBLE);
138 if (HasBit(c->flags, GCF_COMPATIBLE)) y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_COMPATIBLE_LOADED);
140 /* Draw GRF info if it exists */
141 if (!StrEmpty(c->GetDescription())) {
142 SetDParamStr(0, c->GetDescription());
143 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_BLACK_RAW_STRING);
144 } else {
145 y = DrawStringMultiLine (dpi, x, right, y, bottom, STR_NEWGRF_SETTINGS_NO_INFO);
150 * Window for setting the parameters of a NewGRF.
152 struct NewGRFParametersWindow : public Window {
153 static GRFParameterInfo dummy_parameter_info; ///< Dummy info in case a newgrf didn't provide info about some parameter.
154 GRFConfig *grf_config; ///< Set the parameters of this GRFConfig.
155 uint clicked_button; ///< The row in which a button was clicked or UINT_MAX.
156 bool clicked_increase; ///< True if the increase button was clicked, false for the decrease button.
157 bool clicked_dropdown; ///< Whether the dropdown is open.
158 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
159 int timeout; ///< How long before we unpress the last-pressed button?
160 uint clicked_row; ///< The selected parameter
161 int line_height; ///< Height of a row in the matrix widget.
162 Scrollbar *vscroll;
163 bool action14present; ///< True if action14 information is present.
164 bool editable; ///< Allow editing parameters.
166 NewGRFParametersWindow (const WindowDesc *desc, GRFConfig *c, bool editable) : Window(desc),
167 grf_config(c),
168 clicked_button(UINT_MAX),
169 clicked_increase(false),
170 clicked_dropdown(false),
171 closing_dropdown(false),
172 timeout(0),
173 clicked_row(UINT_MAX),
174 line_height(0),
175 vscroll(NULL),
176 action14present ((c->num_valid_params != lengthof(c->param) || c->param_info.Length() != 0)),
177 editable(editable)
179 this->CreateNestedTree();
180 this->vscroll = this->GetScrollbar(WID_NP_SCROLLBAR);
181 this->GetWidget<NWidgetStacked>(WID_NP_SHOW_NUMPAR)->SetDisplayedPlane(this->action14present ? SZSP_HORIZONTAL : 0);
182 this->GetWidget<NWidgetStacked>(WID_NP_SHOW_DESCRIPTION)->SetDisplayedPlane(this->action14present ? 0 : SZSP_HORIZONTAL);
183 this->InitNested(); // Initializes 'this->line_height' as side effect.
185 this->SetWidgetDisabledState(WID_NP_RESET, !this->editable);
187 this->InvalidateData();
191 * Get a dummy parameter-info object with default information.
192 * @param nr The param number that should be changed.
193 * @return GRFParameterInfo with dummy information about the given parameter.
195 static GRFParameterInfo *GetDummyParameterInfo(uint nr)
197 dummy_parameter_info.param_nr = nr;
198 return &dummy_parameter_info;
201 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
203 switch (widget) {
204 case WID_NP_NUMPAR_DEC:
205 case WID_NP_NUMPAR_INC: {
206 size->width = max(SETTING_BUTTON_WIDTH / 2, FONT_HEIGHT_NORMAL);
207 size->height = max(SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL);
208 break;
211 case WID_NP_NUMPAR: {
212 SetDParamMaxValue(0, lengthof(this->grf_config->param));
213 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
214 d.width += padding.width;
215 d.height += padding.height;
216 *size = maxdim(*size, d);
217 break;
220 case WID_NP_BACKGROUND:
221 this->line_height = max(SETTING_BUTTON_HEIGHT, FONT_HEIGHT_NORMAL) + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
223 resize->width = 1;
224 resize->height = this->line_height;
225 size->height = 5 * this->line_height;
226 break;
228 case WID_NP_DESCRIPTION:
229 /* Minimum size of 4 lines. The 500 is the default size of the window. */
230 const uint width = 500 - WD_FRAMERECT_LEFT - WD_FRAMERECT_RIGHT;
231 uint hmax = FONT_HEIGHT_NORMAL * 4;
232 for (uint i = 0; i < this->grf_config->param_info.Length(); i++) {
233 const GRFParameterInfo *par_info = this->grf_config->param_info[i];
234 if (par_info == NULL) continue;
235 const char *desc = par_info->desc.get_string();
236 if (desc == NULL) continue;
237 uint h = GetStringHeight (desc, width);
238 hmax = max (h, hmax);
240 size->height = hmax + WD_TEXTPANEL_TOP + WD_TEXTPANEL_BOTTOM;
241 break;
245 virtual void SetStringParameters(int widget) const
247 switch (widget) {
248 case WID_NP_NUMPAR:
249 SetDParam(0, this->vscroll->GetCount());
250 break;
254 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
256 if (widget == WID_NP_DESCRIPTION) {
257 const GRFParameterInfo *par_info = (this->clicked_row < this->grf_config->param_info.Length()) ? this->grf_config->param_info[this->clicked_row] : NULL;
258 if (par_info == NULL) return;
259 const char *desc = par_info->desc.get_string();
260 if (desc == NULL) return;
261 DrawStringMultiLine (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_TEXTPANEL_TOP, r.bottom - WD_TEXTPANEL_BOTTOM, desc, TC_BLACK);
262 return;
263 } else if (widget != WID_NP_BACKGROUND) {
264 return;
267 bool rtl = _current_text_dir == TD_RTL;
268 uint buttons_left = rtl ? r.right - SETTING_BUTTON_WIDTH - 3 : r.left + 4;
269 uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : SETTING_BUTTON_WIDTH + 8);
270 uint text_right = r.right - (rtl ? SETTING_BUTTON_WIDTH + 8 : WD_FRAMERECT_RIGHT);
272 int y = r.top;
273 int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
274 int text_y_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2;
275 for (uint i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
276 GRFParameterInfo *par_info = (i < this->grf_config->param_info.Length()) ? this->grf_config->param_info[i] : NULL;
277 if (par_info == NULL) par_info = GetDummyParameterInfo(i);
278 uint32 current_value = par_info->GetValue(this->grf_config);
279 bool selected = (i == this->clicked_row);
281 if (par_info->type == PTYPE_BOOL) {
282 DrawBoolButton (dpi, buttons_left, y + button_y_offset, current_value != 0, this->editable);
283 SetDParam(2, par_info->GetValue(this->grf_config) == 0 ? STR_CONFIG_SETTING_OFF : STR_CONFIG_SETTING_ON);
284 } else if (par_info->type == PTYPE_UINT_ENUM) {
285 if (par_info->complete_labels) {
286 DrawDropDownButton (dpi, buttons_left, y + button_y_offset, COLOUR_YELLOW, this->clicked_row == i && this->clicked_dropdown, this->editable);
287 } else {
288 DrawArrowButtons (dpi, buttons_left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, this->editable && current_value > par_info->min_value, this->editable && current_value < par_info->max_value);
290 SetDParam(2, STR_JUST_INT);
291 SetDParam(3, current_value);
292 std::map <uint32, GRFTextMap>::iterator iter =
293 par_info->value_names.find (current_value);
294 if (iter != par_info->value_names.end()) {
295 const char *label = iter->second.get_string();
296 if (label != NULL) {
297 SetDParam(2, STR_JUST_RAW_STRING);
298 SetDParamStr(3, label);
303 const char *name = par_info->name.get_string();
304 if (name != NULL) {
305 SetDParam(0, STR_JUST_RAW_STRING);
306 SetDParamStr(1, name);
307 } else {
308 SetDParam(0, STR_NEWGRF_PARAMETERS_DEFAULT_NAME);
309 SetDParam(1, i + 1);
312 DrawString (dpi, text_left, text_right, y + text_y_offset, STR_NEWGRF_PARAMETERS_SETTING, selected ? TC_WHITE : TC_LIGHT_BLUE);
313 y += this->line_height;
317 void OnPaint (BlitArea *dpi) OVERRIDE
319 if (this->closing_dropdown) {
320 this->closing_dropdown = false;
321 this->clicked_dropdown = false;
323 this->DrawWidgets (dpi);
326 virtual void OnClick(Point pt, int widget, int click_count)
328 switch (widget) {
329 case WID_NP_NUMPAR_DEC:
330 if (this->editable && !this->action14present && this->grf_config->num_params > 0) {
331 this->grf_config->num_params--;
332 this->InvalidateData();
333 SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE);
335 break;
337 case WID_NP_NUMPAR_INC: {
338 GRFConfig *c = this->grf_config;
339 if (this->editable && !this->action14present && c->num_params < c->num_valid_params) {
340 c->param[c->num_params++] = 0;
341 this->InvalidateData();
342 SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE);
344 break;
347 case WID_NP_BACKGROUND: {
348 if (!this->editable) break;
349 uint num = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NP_BACKGROUND);
350 if (num >= this->vscroll->GetCount()) break;
351 if (this->clicked_row != num) {
352 DeleteChildWindows(WC_QUERY_STRING);
353 HideDropDownMenu(this);
354 this->clicked_row = num;
355 this->clicked_dropdown = false;
358 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_NP_BACKGROUND);
359 int x = pt.x - wid->pos_x;
360 if (_current_text_dir == TD_RTL) x = wid->current_x - 1 - x;
361 x -= 4;
363 GRFParameterInfo *par_info = (num < this->grf_config->param_info.Length()) ? this->grf_config->param_info[num] : NULL;
364 if (par_info == NULL) par_info = GetDummyParameterInfo(num);
366 /* One of the arrows is clicked */
367 uint32 old_val = par_info->GetValue(this->grf_config);
368 if (par_info->type != PTYPE_BOOL && IsInsideMM(x, 0, SETTING_BUTTON_WIDTH) && par_info->complete_labels) {
369 if (this->clicked_dropdown) {
370 /* unclick the dropdown */
371 HideDropDownMenu(this);
372 this->clicked_dropdown = false;
373 this->closing_dropdown = false;
374 } else {
375 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_NP_BACKGROUND);
376 int rel_y = (pt.y - (int)wid->pos_y) % this->line_height;
378 Rect wi_rect;
379 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);;
380 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
381 wi_rect.top = pt.y - rel_y + (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
382 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
384 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
385 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
386 this->clicked_dropdown = true;
387 this->closing_dropdown = false;
389 DropDownList *list = new DropDownList();
390 for (uint32 i = par_info->min_value; i <= par_info->max_value; i++) {
391 *list->Append() = new DropDownListCharStringItem (par_info->value_names[i].get_string(), i, false);
394 ShowDropDownListAt(this, list, old_val, -1, wi_rect, COLOUR_ORANGE, true);
397 } else if (IsInsideMM(x, 0, SETTING_BUTTON_WIDTH)) {
398 uint32 val = old_val;
399 if (par_info->type == PTYPE_BOOL) {
400 val = !val;
401 } else {
402 if (x >= SETTING_BUTTON_WIDTH / 2) {
403 /* Increase button clicked */
404 if (val < par_info->max_value) val++;
405 this->clicked_increase = true;
406 } else {
407 /* Decrease button clicked */
408 if (val > par_info->min_value) val--;
409 this->clicked_increase = false;
412 if (val != old_val) {
413 par_info->SetValue(this->grf_config, val);
415 this->clicked_button = num;
416 this->timeout = 5;
418 } else if (par_info->type == PTYPE_UINT_ENUM && !par_info->complete_labels && click_count >= 2) {
419 /* Display a query box so users can enter a custom value. */
420 SetDParam(0, old_val);
421 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_NONE);
423 this->SetDirty();
424 break;
427 case WID_NP_RESET:
428 if (!this->editable) break;
429 this->grf_config->SetParameterDefaults();
430 this->InvalidateData();
431 SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE);
432 break;
434 case WID_NP_ACCEPT:
435 this->Delete();
436 break;
440 virtual void OnQueryTextFinished(char *str)
442 if (StrEmpty(str)) return;
443 int32 value = atoi(str);
444 GRFParameterInfo *par_info = ((uint)this->clicked_row < this->grf_config->param_info.Length()) ? this->grf_config->param_info[this->clicked_row] : NULL;
445 if (par_info == NULL) par_info = GetDummyParameterInfo(this->clicked_row);
446 uint32 val = Clamp<uint32>(value, par_info->min_value, par_info->max_value);
447 par_info->SetValue(this->grf_config, val);
448 this->SetDirty();
451 virtual void OnDropdownSelect(int widget, int index)
453 assert(this->clicked_dropdown);
454 GRFParameterInfo *par_info = ((uint)this->clicked_row < this->grf_config->param_info.Length()) ? this->grf_config->param_info[this->clicked_row] : NULL;
455 if (par_info == NULL) par_info = GetDummyParameterInfo(this->clicked_row);
456 par_info->SetValue(this->grf_config, index);
457 this->SetDirty();
460 virtual void OnDropdownClose(Point pt, int widget, int index, bool instant_close)
462 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
463 * the same dropdown button was clicked again, and then not open the dropdown again.
464 * So, we only remember that it was closed, and process it on the next OnPaint, which is
465 * after OnClick. */
466 assert(this->clicked_dropdown);
467 this->closing_dropdown = true;
468 this->SetDirty();
471 virtual void OnResize()
473 this->vscroll->SetCapacityFromWidget(this, WID_NP_BACKGROUND);
477 * Some data on this window has become invalid.
478 * @param data Information about the changed data.
479 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
481 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
483 if (!gui_scope) return;
484 if (!this->action14present) {
485 this->SetWidgetDisabledState(WID_NP_NUMPAR_DEC, !this->editable || this->grf_config->num_params == 0);
486 this->SetWidgetDisabledState(WID_NP_NUMPAR_INC, !this->editable || this->grf_config->num_params >= this->grf_config->num_valid_params);
489 this->vscroll->SetCount(this->action14present ? this->grf_config->num_valid_params : this->grf_config->num_params);
490 if (this->clicked_row != UINT_MAX && this->clicked_row >= this->vscroll->GetCount()) {
491 this->clicked_row = UINT_MAX;
492 DeleteChildWindows(WC_QUERY_STRING);
496 virtual void OnTick()
498 if (--this->timeout == 0) {
499 this->clicked_button = UINT_MAX;
500 this->SetDirty();
504 GRFParameterInfo NewGRFParametersWindow::dummy_parameter_info(0);
507 static const NWidgetPart _nested_newgrf_parameter_widgets[] = {
508 NWidget(NWID_HORIZONTAL),
509 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
510 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_PARAMETERS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
511 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
512 EndContainer(),
513 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NP_SHOW_NUMPAR),
514 NWidget(WWT_PANEL, COLOUR_MAUVE), SetResize(1, 0), SetFill(1, 0), SetPIP(4, 0, 4),
515 NWidget(NWID_HORIZONTAL), SetPIP(4, 0, 4),
516 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_NP_NUMPAR_DEC), SetMinimalSize(12, 12), SetDataTip(AWV_DECREASE, STR_NULL),
517 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_NP_NUMPAR_INC), SetMinimalSize(12, 12), SetDataTip(AWV_INCREASE, STR_NULL),
518 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_NP_NUMPAR), SetResize(1, 0), SetFill(1, 0), SetPadding(0, 0, 0, 4), SetDataTip(STR_NEWGRF_PARAMETERS_NUM_PARAM, STR_NULL),
519 EndContainer(),
520 EndContainer(),
521 EndContainer(),
522 NWidget(NWID_HORIZONTAL),
523 NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_NP_BACKGROUND), SetMinimalSize(188, 182), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_NP_SCROLLBAR),
524 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_NP_SCROLLBAR),
525 EndContainer(),
526 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NP_SHOW_DESCRIPTION),
527 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_NP_DESCRIPTION), SetResize(1, 0), SetFill(1, 0),
528 EndContainer(),
529 EndContainer(),
530 NWidget(NWID_HORIZONTAL),
531 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
532 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_NP_ACCEPT), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NEWGRF_PARAMETERS_CLOSE, STR_NULL),
533 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_NP_RESET), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_NEWGRF_PARAMETERS_RESET, STR_NEWGRF_PARAMETERS_RESET_TOOLTIP),
534 EndContainer(),
535 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
536 EndContainer(),
539 /** Window preferences for the change grf parameters window */
540 static WindowDesc::Prefs _newgrf_parameters_prefs ("settings_newgrf_config");
542 /** Window definition for the change grf parameters window */
543 static const WindowDesc _newgrf_parameters_desc(
544 WDP_CENTER, 500, 208,
545 WC_GRF_PARAMETERS, WC_NONE,
547 _nested_newgrf_parameter_widgets, lengthof(_nested_newgrf_parameter_widgets),
548 &_newgrf_parameters_prefs
551 static void OpenGRFParameterWindow(GRFConfig *c, bool editable)
553 DeleteWindowByClass(WC_GRF_PARAMETERS);
554 new NewGRFParametersWindow(&_newgrf_parameters_desc, c, editable);
557 /** Window for displaying the textfile of a NewGRF. */
558 struct NewGRFTextfileWindow : public TextfileWindow {
559 const GRFConfig *grf_config; ///< View the textfile of this GRFConfig.
561 NewGRFTextfileWindow (TextfileType file_type, const GRFConfig *c)
562 : TextfileWindow (c->GetTextfile(file_type)), grf_config(c)
564 this->CheckForMissingGlyphs();
567 /* virtual */ void SetStringParameters(int widget) const
569 if (widget == WID_TF_CAPTION) {
570 SetDParam(0, STR_CONTENT_TYPE_NEWGRF);
571 SetDParamStr(1, this->grf_config->GetName());
576 void ShowNewGRFTextfileWindow(TextfileType file_type, const GRFConfig *c)
578 DeleteWindowByClass(WC_TEXTFILE);
579 new NewGRFTextfileWindow(file_type, c);
582 static GRFPresetList _grf_preset_list; ///< List of known NewGRF presets. @see GetGRFPresetList
584 class DropDownListPresetItem : public DropDownListItem {
585 public:
586 DropDownListPresetItem(int result) : DropDownListItem(result, false) {}
588 virtual ~DropDownListPresetItem() {}
590 bool Selectable() const
592 return true;
595 void Draw (BlitArea *dpi, int left, int right, int top, int bottom, bool sel, int bg_colour) const OVERRIDE
597 DrawString (dpi, left + 2, right + 2, top, _grf_preset_list[this->result], sel ? TC_WHITE : TC_BLACK);
602 typedef std::map<uint32, const GRFConfig *> GrfIdMap; ///< Map of grfid to the grf config.
605 * Add all grf configs from \a c into the map.
606 * @param c Grf list to add.
607 * @param grfid_map Map to add them to.
609 static void FillGrfidMap(const GRFConfig *c, GrfIdMap *grfid_map)
611 while (c != NULL) {
612 std::pair<uint32, const GRFConfig *> p(c->ident.grfid, c);
613 grfid_map->insert(p);
614 c = c->next;
618 static void NewGRFConfirmationCallback(Window *w, bool confirmed);
619 static void ShowSavePresetWindow(const char *initial_text);
622 * Window for showing NewGRF files
624 struct NewGRFWindow : public Window, NewGRFScanCallback {
625 typedef GUIList<const GRFConfig *, StringFilter &> GUIGRFConfigList;
627 static const uint EDITBOX_MAX_SIZE = 50;
629 static Listing last_sorting; ///< Default sorting of #GUIGRFConfigList.
630 static Filtering last_filtering; ///< Default filtering of #GUIGRFConfigList.
631 static GUIGRFConfigList::SortFunction * const sorter_funcs[]; ///< Sort functions of the #GUIGRFConfigList.
632 static GUIGRFConfigList::FilterFunction * const filter_funcs[]; ///< Filter functions of the #GUIGRFConfigList.
634 GUIGRFConfigList avails; ///< Available (non-active) grfs.
635 const GRFConfig *avail_sel; ///< Currently selected available grf. \c NULL is none is selected.
636 int avail_pos; ///< Index of #avail_sel if existing, else \c -1.
637 StringFilter string_filter; ///< Filter for available grf.
638 QueryStringN<EDITBOX_MAX_SIZE> filter_editbox; ///< Filter editbox;
640 GRFConfig *actives; ///< Temporary active grf list to which changes are made.
641 GRFConfig *active_sel; ///< Selected active grf item.
643 GRFConfig **orig_list; ///< List active grfs in the game. Used as initial value, may be updated by the window.
644 bool editable; ///< Is the window editable?
645 bool show_params; ///< Are the grf-parameters shown in the info-panel?
646 bool execute; ///< On pressing 'apply changes' are grf changes applied immediately, or only list is updated.
647 int preset; ///< Selected preset or \c -1 if none selected.
648 int active_over; ///< Active GRF item over which another one is dragged, \c -1 if none.
650 Scrollbar *vscroll;
651 Scrollbar *vscroll2;
653 NewGRFWindow (const WindowDesc *desc, bool editable, bool show_params, bool execute, GRFConfig **orig_list) :
654 Window (desc), NewGRFScanCallback(),
655 avails(), avail_sel (NULL), avail_pos (-1),
656 string_filter(), filter_editbox(),
657 actives (NULL), active_sel (NULL), orig_list (orig_list),
658 editable (editable), show_params (show_params),
659 execute (execute), preset (-1), active_over (-1)
661 CopyGRFConfigList(&this->actives, *orig_list, false);
662 GetGRFPresetList(&_grf_preset_list);
664 this->CreateNestedTree();
665 this->vscroll = this->GetScrollbar(WID_NS_SCROLLBAR);
666 this->vscroll2 = this->GetScrollbar(WID_NS_SCROLL2BAR);
668 this->GetWidget<NWidgetStacked>(WID_NS_SHOW_REMOVE)->SetDisplayedPlane(this->editable ? 0 : 1);
669 this->GetWidget<NWidgetStacked>(WID_NS_SHOW_APPLY)->SetDisplayedPlane(this->editable ? 0 : this->show_params ? 1 : SZSP_HORIZONTAL);
670 this->InitNested(WN_GAME_OPTIONS_NEWGRF_STATE);
672 this->querystrings[WID_NS_FILTER] = &this->filter_editbox;
673 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
674 if (editable) {
675 this->SetFocusedWidget(WID_NS_FILTER);
676 } else {
677 this->DisableWidget(WID_NS_FILTER);
680 this->avails.SetListing(this->last_sorting);
681 this->avails.SetFiltering(this->last_filtering);
682 this->avails.SetSortFuncs(this->sorter_funcs);
683 this->avails.SetFilterFuncs(this->filter_funcs);
684 this->avails.ForceRebuild();
686 this->OnInvalidateData(GOID_NEWGRF_LIST_EDITED);
689 void OnDelete (void) FINAL_OVERRIDE
691 DeleteWindowByClass(WC_GRF_PARAMETERS);
692 DeleteWindowByClass(WC_TEXTFILE);
693 DeleteWindowByClass(WC_SAVE_PRESET);
695 if (this->editable && !this->execute) {
696 CopyGRFConfigList(this->orig_list, this->actives, true);
697 ResetGRFConfig(false);
698 ReloadNewGRFData();
701 /* Remove the temporary copy of grf-list used in window */
702 ClearGRFConfigList(&this->actives);
703 _grf_preset_list.Clear();
707 * Test whether the currently active set of NewGRFs can be upgraded with the available NewGRFs.
708 * @return Whether an upgrade is possible.
710 bool CanUpgradeCurrent()
712 GrfIdMap grfid_map;
713 FillGrfidMap(this->actives, &grfid_map);
715 for (const GRFConfig *a = _all_grfs; a != NULL; a = a->next) {
716 GrfIdMap::const_iterator iter = grfid_map.find(a->ident.grfid);
717 if (iter != grfid_map.end() && a->version > iter->second->version) return true;
719 return false;
722 /** Upgrade the currently active set of NewGRFs. */
723 void UpgradeCurrent()
725 GrfIdMap grfid_map;
726 FillGrfidMap(this->actives, &grfid_map);
728 for (const GRFConfig *a = _all_grfs; a != NULL; a = a->next) {
729 GrfIdMap::iterator iter = grfid_map.find(a->ident.grfid);
730 if (iter == grfid_map.end() || iter->second->version >= a->version) continue;
732 GRFConfig **c = &this->actives;
733 while (*c != iter->second) c = &(*c)->next;
734 GRFConfig *d = new GRFConfig(*a);
735 d->next = (*c)->next;
736 d->CopyParams(**c);
737 if (this->active_sel == *c) this->active_sel = NULL;
738 delete *c;
739 *c = d;
740 iter->second = d;
744 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
746 switch (widget) {
747 case WID_NS_FILE_LIST:
749 Dimension d = maxdim(GetSpriteSize(SPR_SQUARE), GetSpriteSize(SPR_WARNING_SIGN));
750 resize->height = max(d.height + 2U, FONT_HEIGHT_NORMAL + 2U);
751 size->height = max(size->height, WD_FRAMERECT_TOP + 6 * resize->height + WD_FRAMERECT_BOTTOM);
752 break;
755 case WID_NS_AVAIL_LIST:
756 resize->height = max(12, FONT_HEIGHT_NORMAL + 2);
757 size->height = max(size->height, WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM);
758 break;
760 case WID_NS_NEWGRF_INFO_TITLE: {
761 Dimension dim = GetStringBoundingBox(STR_NEWGRF_SETTINGS_INFO_TITLE);
762 size->height = max(size->height, dim.height + WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM);
763 size->width = max(size->width, dim.width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT);
764 break;
767 case WID_NS_NEWGRF_INFO:
768 size->height = max(size->height, WD_FRAMERECT_TOP + 10 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM + padding.height + 2);
769 break;
771 case WID_NS_PRESET_LIST: {
772 Dimension d = GetStringBoundingBox(STR_NUM_CUSTOM);
773 for (uint i = 0; i < _grf_preset_list.Length(); i++) {
774 if (_grf_preset_list[i] != NULL) {
775 SetDParamStr(0, _grf_preset_list[i]);
776 d = maxdim(d, GetStringBoundingBox(STR_JUST_RAW_STRING));
779 d.width += padding.width;
780 *size = maxdim(d, *size);
781 break;
784 case WID_NS_CONTENT_DOWNLOAD:
785 case WID_NS_CONTENT_DOWNLOAD2: {
786 Dimension d = GetStringBoundingBox(STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON);
787 *size = maxdim(d, GetStringBoundingBox(STR_INTRO_ONLINE_CONTENT));
788 size->width += padding.width;
789 size->height += padding.height;
790 break;
795 virtual void OnResize()
797 this->vscroll->SetCapacityFromWidget(this, WID_NS_FILE_LIST);
798 this->vscroll2->SetCapacityFromWidget(this, WID_NS_AVAIL_LIST);
801 virtual void SetStringParameters(int widget) const
803 switch (widget) {
804 case WID_NS_PRESET_LIST:
805 if (this->preset == -1) {
806 SetDParam(0, STR_NUM_CUSTOM);
807 } else {
808 SetDParam(0, STR_JUST_RAW_STRING);
809 SetDParamStr(1, _grf_preset_list[this->preset]);
811 break;
816 * Pick the palette for the sprite of the grf to display.
817 * @param c grf to display.
818 * @return Palette for the sprite.
820 inline PaletteID GetPalette(const GRFConfig *c) const
822 PaletteID pal;
824 /* Pick a colour */
825 switch (c->status) {
826 case GCS_NOT_FOUND:
827 case GCS_DISABLED:
828 pal = PALETTE_TO_RED;
829 break;
830 case GCS_ACTIVATED:
831 pal = PALETTE_TO_GREEN;
832 break;
833 default:
834 pal = PALETTE_TO_BLUE;
835 break;
838 /* Do not show a "not-failure" colour when it actually failed to load */
839 if (pal != PALETTE_TO_RED) {
840 if (HasBit(c->flags, GCF_STATIC)) {
841 pal = PALETTE_TO_GREY;
842 } else if (HasBit(c->flags, GCF_COMPATIBLE)) {
843 pal = PALETTE_TO_ORANGE;
847 return pal;
850 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
852 switch (widget) {
853 case WID_NS_FILE_LIST: {
854 GfxFillRect (dpi, r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
856 uint step_height = this->GetWidget<NWidgetBase>(WID_NS_FILE_LIST)->resize_y;
857 uint y = r.top + WD_FRAMERECT_TOP;
858 Dimension square = GetSpriteSize(SPR_SQUARE);
859 Dimension warning = GetSpriteSize(SPR_WARNING_SIGN);
860 int square_offset_y = (step_height - square.height) / 2;
861 int warning_offset_y = (step_height - warning.height) / 2;
862 int offset_y = (step_height - FONT_HEIGHT_NORMAL) / 2;
864 bool rtl = _current_text_dir == TD_RTL;
865 uint text_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.left + square.width + 15;
866 uint text_right = rtl ? r.right - square.width - 15 : r.right - WD_FRAMERECT_RIGHT;
867 uint square_left = rtl ? r.right - square.width - 5 : r.left + 5;
868 uint warning_left = rtl ? r.right - square.width - warning.width - 10 : r.left + square.width + 10;
870 int i = 0;
871 for (const GRFConfig *c = this->actives; c != NULL; c = c->next, i++) {
872 if (this->vscroll->IsVisible(i)) {
873 const char *text = c->GetName();
874 bool h = (this->active_sel == c);
875 PaletteID pal = this->GetPalette(c);
877 if (h) {
878 GfxFillRect (dpi, r.left + 1, y, r.right - 1, y + step_height - 1, PC_DARK_BLUE);
879 } else if (i == this->active_over) {
880 /* Get index of current selection. */
881 int active_sel_pos = 0;
882 for (GRFConfig *c = this->actives; c != NULL && c != this->active_sel; c = c->next, active_sel_pos++) {}
883 if (active_sel_pos != this->active_over) {
884 uint top = this->active_over < active_sel_pos ? y + 1 : y + step_height - 2;
885 GfxFillRect (dpi, r.left + WD_FRAMERECT_LEFT, top - 1, r.right - WD_FRAMERECT_RIGHT, top + 1, PC_GREY);
888 DrawSprite (dpi, SPR_SQUARE, pal, square_left, y + square_offset_y);
889 if (c->error != NULL) DrawSprite (dpi, SPR_WARNING_SIGN, 0, warning_left, y + warning_offset_y);
890 uint txtoffset = c->error == NULL ? 0 : warning.width;
891 DrawString (dpi, text_left + (rtl ? 0 : txtoffset), text_right - (rtl ? txtoffset : 0), y + offset_y, text, h ? TC_WHITE : TC_ORANGE);
892 y += step_height;
895 if (i == this->active_over && this->vscroll->IsVisible(i)) { // Highlight is after the last GRF entry.
896 GfxFillRect (dpi, r.left + WD_FRAMERECT_LEFT, y, r.right - WD_FRAMERECT_RIGHT, y + 2, PC_GREY);
898 break;
901 case WID_NS_AVAIL_LIST: {
902 GfxFillRect (dpi, r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, this->active_over == -2 ? PC_DARK_GREY : PC_BLACK);
904 uint step_height = this->GetWidget<NWidgetBase>(WID_NS_AVAIL_LIST)->resize_y;
905 int offset_y = (step_height - FONT_HEIGHT_NORMAL) / 2;
906 uint y = r.top + WD_FRAMERECT_TOP;
907 uint min_index = this->vscroll2->GetPosition();
908 uint max_index = min(min_index + this->vscroll2->GetCapacity(), this->avails.Length());
910 for (uint i = min_index; i < max_index; i++) {
911 const GRFConfig *c = this->avails[i];
912 bool h = (c == this->avail_sel);
913 const char *text = c->GetName();
915 if (h) GfxFillRect (dpi, r.left + 1, y, r.right - 1, y + step_height - 1, PC_DARK_BLUE);
916 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y + offset_y, text, h ? TC_WHITE : TC_SILVER);
917 y += step_height;
919 break;
922 case WID_NS_NEWGRF_INFO_TITLE:
923 /* Create the nice grayish rectangle at the details top. */
924 GfxFillRect (dpi, r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_DARK_BLUE);
925 DrawString (dpi, r.left, r.right, (r.top + r.bottom - FONT_HEIGHT_NORMAL) / 2, STR_NEWGRF_SETTINGS_INFO_TITLE, TC_FROMSTRING, SA_HOR_CENTER);
926 break;
928 case WID_NS_NEWGRF_INFO: {
929 const GRFConfig *selected = this->active_sel;
930 if (selected == NULL) selected = this->avail_sel;
931 if (selected != NULL) {
932 ShowNewGRFInfo (selected, dpi, r.left + WD_FRAMERECT_LEFT, r.top + WD_FRAMERECT_TOP, r.right - WD_FRAMERECT_RIGHT, r.bottom - WD_FRAMERECT_BOTTOM, this->show_params);
934 break;
939 virtual void OnClick(Point pt, int widget, int click_count)
941 if (widget >= WID_NS_NEWGRF_TEXTFILE && widget < WID_NS_NEWGRF_TEXTFILE + TFT_END) {
942 if (this->active_sel == NULL && this->avail_sel == NULL) return;
944 ShowNewGRFTextfileWindow((TextfileType)(widget - WID_NS_NEWGRF_TEXTFILE), this->active_sel != NULL ? this->active_sel : this->avail_sel);
945 return;
948 switch (widget) {
949 case WID_NS_PRESET_LIST: {
950 DropDownList *list = new DropDownList();
952 /* Add 'None' option for clearing list */
953 *list->Append() = new DropDownListStringItem(STR_NONE, -1, false);
955 for (uint i = 0; i < _grf_preset_list.Length(); i++) {
956 if (_grf_preset_list[i] != NULL) {
957 *list->Append() = new DropDownListPresetItem(i);
961 this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
962 ShowDropDownList(this, list, this->preset, WID_NS_PRESET_LIST);
963 break;
966 case WID_NS_OPEN_URL: {
967 const GRFConfig *c = (this->avail_sel == NULL) ? this->active_sel : this->avail_sel;
969 extern void OpenBrowser(const char *url);
970 OpenBrowser(c->GetURL());
971 break;
974 case WID_NS_PRESET_SAVE:
975 ShowSavePresetWindow((this->preset == -1) ? NULL : _grf_preset_list[this->preset]);
976 break;
978 case WID_NS_PRESET_DELETE:
979 if (this->preset == -1) return;
981 DeleteGRFPresetFromConfig(_grf_preset_list[this->preset]);
982 GetGRFPresetList(&_grf_preset_list);
983 this->preset = -1;
984 this->InvalidateData();
985 this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
986 break;
988 case WID_NS_MOVE_UP: { // Move GRF up
989 if (this->active_sel == NULL || !this->editable) break;
991 int pos = 0;
992 for (GRFConfig **pc = &this->actives; *pc != NULL; pc = &(*pc)->next, pos++) {
993 GRFConfig *c = *pc;
994 if (c->next == this->active_sel) {
995 c->next = this->active_sel->next;
996 this->active_sel->next = c;
997 *pc = this->active_sel;
998 break;
1001 this->vscroll->ScrollTowards(pos);
1002 this->preset = -1;
1003 this->InvalidateData();
1004 break;
1007 case WID_NS_MOVE_DOWN: { // Move GRF down
1008 if (this->active_sel == NULL || !this->editable) break;
1010 int pos = 1; // Start at 1 as we swap the selected newgrf with the next one
1011 for (GRFConfig **pc = &this->actives; *pc != NULL; pc = &(*pc)->next, pos++) {
1012 GRFConfig *c = *pc;
1013 if (c == this->active_sel) {
1014 *pc = c->next;
1015 c->next = c->next->next;
1016 (*pc)->next = c;
1017 break;
1020 this->vscroll->ScrollTowards(pos);
1021 this->preset = -1;
1022 this->InvalidateData();
1023 break;
1026 case WID_NS_FILE_LIST: { // Select an active GRF.
1027 ResetPointerMode();
1029 uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NS_FILE_LIST);
1031 GRFConfig *c;
1032 for (c = this->actives; c != NULL && i > 0; c = c->next, i--) {}
1034 if (this->active_sel != c) DeleteWindowByClass(WC_GRF_PARAMETERS);
1035 this->active_sel = c;
1036 this->avail_sel = NULL;
1037 this->avail_pos = -1;
1039 this->InvalidateData();
1040 if (click_count == 1) {
1041 if (this->editable && this->active_sel != NULL) SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
1042 break;
1044 /* FALL THROUGH, with double click. */
1047 case WID_NS_REMOVE: { // Remove GRF
1048 if (this->active_sel == NULL || !this->editable) break;
1049 DeleteWindowByClass(WC_GRF_PARAMETERS);
1051 /* Choose the next GRF file to be the selected file. */
1052 GRFConfig *newsel = this->active_sel->next;
1053 for (GRFConfig **pc = &this->actives; *pc != NULL; pc = &(*pc)->next) {
1054 GRFConfig *c = *pc;
1055 /* If the new selection is empty (i.e. we're deleting the last item
1056 * in the list, pick the file just before the selected file */
1057 if (newsel == NULL && c->next == this->active_sel) newsel = c;
1059 if (c == this->active_sel) {
1060 if (newsel == c) newsel = NULL;
1062 *pc = c->next;
1063 delete c;
1064 break;
1068 this->active_sel = newsel;
1069 this->preset = -1;
1070 this->avail_pos = -1;
1071 this->avail_sel = NULL;
1072 this->avails.ForceRebuild();
1073 this->InvalidateData(GOID_NEWGRF_LIST_EDITED);
1074 break;
1077 case WID_NS_UPGRADE: { // Upgrade GRF.
1078 if (!this->editable || this->actives == NULL) break;
1079 UpgradeCurrent();
1080 this->InvalidateData(GOID_NEWGRF_LIST_EDITED);
1081 break;
1084 case WID_NS_AVAIL_LIST: { // Select a non-active GRF.
1085 ResetPointerMode();
1087 uint i = this->vscroll2->GetScrolledRowFromWidget(pt.y, this, WID_NS_AVAIL_LIST);
1088 this->active_sel = NULL;
1089 DeleteWindowByClass(WC_GRF_PARAMETERS);
1090 if (i < this->avails.Length()) {
1091 this->avail_sel = this->avails[i];
1092 this->avail_pos = i;
1094 this->InvalidateData();
1095 if (click_count == 1) {
1096 if (this->editable && this->avail_sel != NULL && !HasBit(this->avail_sel->flags, GCF_INVALID)) SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
1097 break;
1099 /* FALL THROUGH, with double click. */
1102 case WID_NS_ADD:
1103 if (this->avail_sel == NULL || !this->editable || HasBit(this->avail_sel->flags, GCF_INVALID)) break;
1105 this->AddGRFToActive();
1106 break;
1108 case WID_NS_APPLY_CHANGES: // Apply changes made to GRF list
1109 if (!this->editable) break;
1110 if (this->execute) {
1111 ShowQuery(
1112 STR_NEWGRF_POPUP_CAUTION_CAPTION,
1113 STR_NEWGRF_CONFIRMATION_TEXT,
1114 this,
1115 NewGRFConfirmationCallback
1117 } else {
1118 CopyGRFConfigList(this->orig_list, this->actives, true);
1119 ResetGRFConfig(false);
1120 ReloadNewGRFData();
1122 this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
1123 break;
1125 case WID_NS_VIEW_PARAMETERS:
1126 case WID_NS_SET_PARAMETERS: { // Edit parameters
1127 if (this->active_sel == NULL || !this->show_params || this->active_sel->num_valid_params == 0) break;
1129 OpenGRFParameterWindow(this->active_sel, this->editable);
1130 break;
1133 case WID_NS_TOGGLE_PALETTE:
1134 if (this->active_sel != NULL && this->editable) {
1135 this->active_sel->palette ^= GRFP_USE_MASK;
1136 this->SetDirty();
1138 break;
1140 case WID_NS_CONTENT_DOWNLOAD:
1141 case WID_NS_CONTENT_DOWNLOAD2:
1142 if (!_network_available) {
1143 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
1144 } else {
1145 #if defined(ENABLE_NETWORK)
1146 this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
1148 ShowMissingContentWindow(this->actives);
1149 #endif
1151 break;
1153 case WID_NS_RESCAN_FILES:
1154 case WID_NS_RESCAN_FILES2:
1155 ScanNewGRFFiles(this);
1156 break;
1160 virtual void OnNewGRFsScanned()
1162 this->avail_sel = NULL;
1163 this->avail_pos = -1;
1164 this->avails.ForceRebuild();
1165 this->DeleteChildWindows(WC_QUERY_STRING); // Remove the parameter query window
1166 this->DeleteChildWindows(WC_TEXTFILE); // Remove the view textfile window
1169 virtual void OnDropdownSelect(int widget, int index)
1171 if (!this->editable) return;
1173 ClearGRFConfigList(&this->actives);
1174 this->preset = index;
1176 if (index != -1) {
1177 this->actives = LoadGRFPresetFromConfig(_grf_preset_list[index]);
1179 this->avails.ForceRebuild();
1181 ResetPointerMode();
1182 DeleteWindowByClass(WC_GRF_PARAMETERS);
1183 this->active_sel = NULL;
1184 this->InvalidateData(GOID_NEWGRF_PRESET_LOADED);
1187 virtual void OnQueryTextFinished(char *str)
1189 if (str == NULL) return;
1191 SaveGRFPresetToConfig(str, this->actives);
1192 GetGRFPresetList(&_grf_preset_list);
1194 /* Switch to this preset */
1195 for (uint i = 0; i < _grf_preset_list.Length(); i++) {
1196 if (_grf_preset_list[i] != NULL && strcmp(_grf_preset_list[i], str) == 0) {
1197 this->preset = i;
1198 break;
1202 this->InvalidateData();
1206 * Some data on this window has become invalid.
1207 * @param data Information about the changed data. @see GameOptionsInvalidationData
1208 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1210 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1212 if (!gui_scope) return;
1213 switch (data) {
1214 default:
1215 /* Nothing important to do */
1216 break;
1218 case GOID_NEWGRF_RESCANNED:
1219 /* Search the list for items that are now found and mark them as such. */
1220 for (GRFConfig **l = &this->actives; *l != NULL; l = &(*l)->next) {
1221 GRFConfig *c = *l;
1222 bool compatible = HasBit(c->flags, GCF_COMPATIBLE);
1223 if (c->status != GCS_NOT_FOUND && !compatible) continue;
1225 const GRFConfig *f = FindGRFConfig(c->ident.grfid, FGCM_EXACT, compatible ? c->original_md5sum : c->ident.md5sum);
1226 if (f == NULL || HasBit(f->flags, GCF_INVALID)) continue;
1228 *l = new GRFConfig(*f);
1229 (*l)->next = c->next;
1231 if (active_sel == c) active_sel = *l;
1233 delete c;
1236 this->avails.ForceRebuild();
1237 /* FALL THROUGH */
1238 case GOID_NEWGRF_LIST_EDITED:
1239 this->preset = -1;
1240 /* FALL THROUGH */
1241 case GOID_NEWGRF_PRESET_LOADED: {
1242 /* Update scrollbars */
1243 int i = 0;
1244 for (const GRFConfig *c = this->actives; c != NULL; c = c->next, i++) {}
1246 this->vscroll->SetCount(i + 1); // Reserve empty space for drag and drop handling.
1248 if (this->avail_pos >= 0) this->vscroll2->ScrollTowards(this->avail_pos);
1249 break;
1253 this->BuildAvailables();
1255 this->SetWidgetsDisabledState(!this->editable,
1256 WID_NS_PRESET_LIST,
1257 WID_NS_APPLY_CHANGES,
1258 WID_NS_TOGGLE_PALETTE,
1259 WIDGET_LIST_END
1261 this->SetWidgetDisabledState(WID_NS_ADD, !this->editable || this->avail_sel == NULL || HasBit(this->avail_sel->flags, GCF_INVALID));
1262 this->SetWidgetDisabledState(WID_NS_UPGRADE, !this->editable || this->actives == NULL || !this->CanUpgradeCurrent());
1264 bool disable_all = this->active_sel == NULL || !this->editable;
1265 this->SetWidgetsDisabledState(disable_all,
1266 WID_NS_REMOVE,
1267 WID_NS_MOVE_UP,
1268 WID_NS_MOVE_DOWN,
1269 WIDGET_LIST_END
1272 const GRFConfig *c = (this->avail_sel == NULL) ? this->active_sel : this->avail_sel;
1273 for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
1274 this->SetWidgetDisabledState (WID_NS_NEWGRF_TEXTFILE + tft,
1275 c == NULL || !c->GetTextfile(tft).valid());
1277 this->SetWidgetDisabledState(WID_NS_OPEN_URL, c == NULL || StrEmpty(c->GetURL()));
1279 this->SetWidgetDisabledState(WID_NS_SET_PARAMETERS, !this->show_params || this->active_sel == NULL || this->active_sel->num_valid_params == 0);
1280 this->SetWidgetDisabledState(WID_NS_VIEW_PARAMETERS, !this->show_params || this->active_sel == NULL || this->active_sel->num_valid_params == 0);
1281 this->SetWidgetDisabledState(WID_NS_TOGGLE_PALETTE, disable_all ||
1282 (!(_settings_client.gui.newgrf_developer_tools || _settings_client.gui.scenario_developer) && ((c->palette & GRFP_GRF_MASK) != GRFP_GRF_UNSET)));
1284 if (!disable_all) {
1285 /* All widgets are now enabled, so disable widgets we can't use */
1286 if (this->active_sel == this->actives) this->DisableWidget(WID_NS_MOVE_UP);
1287 if (this->active_sel->next == NULL) this->DisableWidget(WID_NS_MOVE_DOWN);
1288 if (this->active_sel->IsOpenTTDBaseGRF()) this->DisableWidget(WID_NS_REMOVE);
1291 this->SetWidgetDisabledState(WID_NS_PRESET_DELETE, this->preset == -1);
1293 bool has_missing = false;
1294 bool has_compatible = false;
1295 for (const GRFConfig *c = this->actives; !has_missing && c != NULL; c = c->next) {
1296 has_missing |= c->status == GCS_NOT_FOUND;
1297 has_compatible |= HasBit(c->flags, GCF_COMPATIBLE);
1299 uint32 widget_data;
1300 StringID tool_tip;
1301 if (has_missing || has_compatible) {
1302 widget_data = STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_BUTTON;
1303 tool_tip = STR_NEWGRF_SETTINGS_FIND_MISSING_CONTENT_TOOLTIP;
1304 } else {
1305 widget_data = STR_INTRO_ONLINE_CONTENT;
1306 tool_tip = STR_INTRO_TOOLTIP_ONLINE_CONTENT;
1308 this->GetWidget<NWidgetCore>(WID_NS_CONTENT_DOWNLOAD)->widget_data = widget_data;
1309 this->GetWidget<NWidgetCore>(WID_NS_CONTENT_DOWNLOAD)->tool_tip = tool_tip;
1310 this->GetWidget<NWidgetCore>(WID_NS_CONTENT_DOWNLOAD2)->widget_data = widget_data;
1311 this->GetWidget<NWidgetCore>(WID_NS_CONTENT_DOWNLOAD2)->tool_tip = tool_tip;
1313 this->SetWidgetDisabledState(WID_NS_PRESET_SAVE, has_missing);
1316 virtual EventState OnKeyPress(WChar key, uint16 keycode)
1318 if (!this->editable) return ES_NOT_HANDLED;
1320 switch (keycode) {
1321 case WKC_UP:
1322 /* scroll up by one */
1323 if (this->avail_pos > 0) this->avail_pos--;
1324 break;
1326 case WKC_DOWN:
1327 /* scroll down by one */
1328 if (this->avail_pos < (int)this->avails.Length() - 1) this->avail_pos++;
1329 break;
1331 case WKC_PAGEUP:
1332 /* scroll up a page */
1333 this->avail_pos = (this->avail_pos < this->vscroll2->GetCapacity()) ? 0 : this->avail_pos - this->vscroll2->GetCapacity();
1334 break;
1336 case WKC_PAGEDOWN:
1337 /* scroll down a page */
1338 this->avail_pos = min(this->avail_pos + this->vscroll2->GetCapacity(), (int)this->avails.Length() - 1);
1339 break;
1341 case WKC_HOME:
1342 /* jump to beginning */
1343 this->avail_pos = 0;
1344 break;
1346 case WKC_END:
1347 /* jump to end */
1348 this->avail_pos = this->avails.Length() - 1;
1349 break;
1351 default:
1352 return ES_NOT_HANDLED;
1355 if (this->avails.Length() == 0) this->avail_pos = -1;
1356 if (this->avail_pos >= 0) {
1357 this->avail_sel = this->avails[this->avail_pos];
1358 this->vscroll2->ScrollTowards(this->avail_pos);
1359 this->InvalidateData(0);
1362 return ES_HANDLED;
1365 virtual void OnEditboxChanged(int wid)
1367 if (!this->editable) return;
1369 string_filter.SetFilterTerm(this->filter_editbox.GetText());
1370 this->avails.SetFilterState(!string_filter.IsEmpty());
1371 this->avails.ForceRebuild();
1372 this->InvalidateData(0);
1375 virtual void OnDragDrop(Point pt, int widget)
1377 if (!this->editable) return;
1379 if (widget == WID_NS_FILE_LIST) {
1380 if (this->active_sel != NULL) {
1381 /* Get pointer to the selected file in the active list. */
1382 int from_pos = 0;
1383 GRFConfig **from_prev;
1384 for (from_prev = &this->actives; *from_prev != this->active_sel; from_prev = &(*from_prev)->next, from_pos++) {}
1386 /* Gets the drag-and-drop destination offset. Ignore the last dummy line. */
1387 int to_pos = min(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NS_FILE_LIST), this->vscroll->GetCount() - 2);
1388 if (to_pos != from_pos) { // Don't move NewGRF file over itself.
1389 /* Get pointer to destination position. */
1390 GRFConfig **to_prev = &this->actives;
1391 for (int i = from_pos < to_pos ? -1 : 0; *to_prev != NULL && i < to_pos; to_prev = &(*to_prev)->next, i++) {}
1393 /* Detach NewGRF file from its original position. */
1394 *from_prev = this->active_sel->next;
1396 /* Attach NewGRF file to its new position. */
1397 this->active_sel->next = *to_prev;
1398 *to_prev = this->active_sel;
1400 this->vscroll->ScrollTowards(to_pos);
1401 this->preset = -1;
1402 this->InvalidateData();
1404 } else if (this->avail_sel != NULL) {
1405 int to_pos = min(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NS_FILE_LIST), this->vscroll->GetCount() - 1);
1406 this->AddGRFToActive(to_pos);
1408 } else if (widget == WID_NS_AVAIL_LIST && this->active_sel != NULL) {
1409 /* Remove active NewGRF file by dragging it over available list. */
1410 Point dummy = {-1, -1};
1411 this->OnClick(dummy, WID_NS_REMOVE, 1);
1414 ResetPointerMode();
1416 if (this->active_over != -1) {
1417 /* End of drag-and-drop, hide dragged destination highlight. */
1418 this->SetWidgetDirty(this->active_over == -2 ? WID_NS_AVAIL_LIST : WID_NS_FILE_LIST);
1419 this->active_over = -1;
1423 virtual void OnMouseDrag(Point pt, int widget)
1425 if (!this->editable) return;
1427 if (widget == WID_NS_FILE_LIST && (this->active_sel != NULL || this->avail_sel != NULL)) {
1428 /* An NewGRF file is dragged over the active list. */
1429 int to_pos = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NS_FILE_LIST);
1430 /* Skip the last dummy line if the source is from the active list. */
1431 to_pos = min(to_pos, this->vscroll->GetCount() - (this->active_sel != NULL ? 2 : 1));
1433 if (to_pos != this->active_over) {
1434 this->active_over = to_pos;
1435 this->SetWidgetDirty(WID_NS_FILE_LIST);
1437 } else if (widget == WID_NS_AVAIL_LIST && this->active_sel != NULL) {
1438 this->active_over = -2;
1439 this->SetWidgetDirty(WID_NS_AVAIL_LIST);
1440 } else if (this->active_over != -1) {
1441 this->SetWidgetDirty(this->active_over == -2 ? WID_NS_AVAIL_LIST : WID_NS_FILE_LIST);
1442 this->active_over = -1;
1446 private:
1447 /** Sort grfs by name. */
1448 static int CDECL NameSorter(const GRFConfig * const *a, const GRFConfig * const *b)
1450 int i = strnatcmp((*a)->GetName(), (*b)->GetName(), true); // Sort by name (natural sorting).
1451 if (i != 0) return i;
1453 i = (*a)->version - (*b)->version;
1454 if (i != 0) return i;
1456 return memcmp((*a)->ident.md5sum, (*b)->ident.md5sum, lengthof((*b)->ident.md5sum));
1459 /** Filter grfs by tags/name */
1460 static bool CDECL TagNameFilter(const GRFConfig * const *a, StringFilter &filter)
1462 filter.ResetState();
1463 filter.AddLine((*a)->GetName());
1464 filter.AddLine((*a)->filename);
1465 filter.AddLine((*a)->GetDescription());
1466 return filter.GetState();;
1469 void BuildAvailables()
1471 if (!this->avails.NeedRebuild()) return;
1473 this->avails.Clear();
1475 for (const GRFConfig *c = _all_grfs; c != NULL; c = c->next) {
1476 bool found = false;
1477 for (const GRFConfig *grf = this->actives; grf != NULL && !found; grf = grf->next) found = grf->ident.matches (c->ident);
1478 if (found) continue;
1480 if (_settings_client.gui.newgrf_show_old_versions) {
1481 *this->avails.Append() = c;
1482 } else {
1483 const GRFConfig *best = FindGRFConfig(c->ident.grfid, HasBit(c->flags, GCF_INVALID) ? FGCM_NEWEST : FGCM_NEWEST_VALID);
1485 * If the best version is 0, then all NewGRF with this GRF ID
1486 * have version 0, so for backward compatibility reasons we
1487 * want to show them all.
1488 * If we are the best version, then we definitely want to
1489 * show that NewGRF!.
1491 if (best->version == 0 || best->ident.matches (c->ident)) {
1492 *this->avails.Append() = c;
1497 this->avails.Filter(this->string_filter);
1498 this->avails.Compact();
1499 this->avails.RebuildDone();
1500 this->avails.Sort();
1502 if (this->avail_sel != NULL) {
1503 this->avail_pos = this->avails.FindIndex(this->avail_sel);
1504 if (this->avail_pos < 0) this->avail_sel = NULL;
1507 this->vscroll2->SetCount(this->avails.Length()); // Update the scrollbar
1511 * Insert a GRF into the active list.
1512 * @param ins_pos Insert GRF at this position.
1513 * @return True if the GRF was successfully added.
1515 bool AddGRFToActive(int ins_pos = -1)
1517 if (this->avail_sel == NULL || !this->editable || HasBit(this->avail_sel->flags, GCF_INVALID)) return false;
1519 int count = 0;
1520 GRFConfig **entry = NULL;
1521 GRFConfig **list;
1522 /* Find last entry in the list, checking for duplicate grfid on the way */
1523 for (list = &this->actives; *list != NULL; list = &(*list)->next, ins_pos--) {
1524 if (ins_pos == 0) entry = list; // Insert position? Save.
1525 if ((*list)->ident.grfid == this->avail_sel->ident.grfid) {
1526 ShowErrorMessage(STR_NEWGRF_DUPLICATE_GRFID, INVALID_STRING_ID, WL_INFO);
1527 return false;
1529 count++;
1531 if (entry == NULL) entry = list;
1532 if (count >= MAX_NEWGRFS) {
1533 ShowErrorMessage(STR_NEWGRF_TOO_MANY_NEWGRFS, INVALID_STRING_ID, WL_INFO);
1534 return false;
1537 GRFConfig *c = new GRFConfig(*this->avail_sel); // Copy GRF details from scanned list.
1538 c->SetParameterDefaults();
1540 /* Insert GRF config to configuration list. */
1541 c->next = *entry;
1542 *entry = c;
1544 /* Select next (or previous, if last one) item in the list. */
1545 int new_pos = this->avail_pos + 1;
1546 if (new_pos >= (int)this->avails.Length()) new_pos = this->avail_pos - 1;
1547 this->avail_pos = new_pos;
1548 if (new_pos >= 0) this->avail_sel = this->avails[new_pos];
1550 this->avails.ForceRebuild();
1551 this->InvalidateData(GOID_NEWGRF_LIST_EDITED);
1552 return true;
1556 #if defined(ENABLE_NETWORK)
1558 * Show the content list window with all missing grfs from the given list.
1559 * @param list The list of grfs to check for missing / not exactly matching ones.
1561 void ShowMissingContentWindow(const GRFConfig *list)
1563 /* Only show the things in the current list, or everything when nothing's selected */
1564 ContentVector cv;
1565 for (const GRFConfig *c = list; c != NULL; c = c->next) {
1566 if (c->status != GCS_NOT_FOUND && !HasBit(c->flags, GCF_COMPATIBLE)) continue;
1568 ContentInfo *ci = new ContentInfo();
1569 ci->type = CONTENT_TYPE_NEWGRF;
1570 ci->state = ContentInfo::DOES_NOT_EXIST;
1571 bstrcpy (ci->name, c->GetName());
1572 ci->unique_id = BSWAP32(c->ident.grfid);
1573 memcpy(ci->md5sum, HasBit(c->flags, GCF_COMPATIBLE) ? c->original_md5sum : c->ident.md5sum, sizeof(ci->md5sum));
1574 *cv.Append() = ci;
1576 ShowNetworkContentListWindow(cv.Length() == 0 ? NULL : &cv, CONTENT_TYPE_NEWGRF);
1578 #endif
1580 Listing NewGRFWindow::last_sorting = {false, 0};
1581 Filtering NewGRFWindow::last_filtering = {false, 0};
1583 NewGRFWindow::GUIGRFConfigList::SortFunction * const NewGRFWindow::sorter_funcs[] = {
1584 &NameSorter,
1587 NewGRFWindow::GUIGRFConfigList::FilterFunction * const NewGRFWindow::filter_funcs[] = {
1588 &TagNameFilter,
1592 * Custom nested widget container for the NewGRF gui.
1593 * Depending on the space in the gui, it uses either
1594 * - two column mode, put the #acs and the #avs underneath each other and the #info next to it, or
1595 * - three column mode, put the #avs, #acs, and #info each in its own column.
1597 class NWidgetNewGRFDisplay : public NWidgetContainer {
1598 public:
1599 static const uint INTER_LIST_SPACING; ///< Empty vertical space between both lists in the 2 column mode.
1600 static const uint INTER_COLUMN_SPACING; ///< Empty horizontal space between two columns.
1601 static const uint MAX_EXTRA_INFO_WIDTH; ///< Maximal additional width given to the panel.
1602 static const uint MIN_EXTRA_FOR_3_COLUMNS; ///< Minimal additional width needed before switching to 3 columns.
1604 NWidgetBase *avs; ///< Widget with the available grfs list and buttons.
1605 NWidgetBase *acs; ///< Widget with the active grfs list and buttons.
1606 NWidgetBase *inf; ///< Info panel.
1607 bool editable; ///< Editable status of the parent NewGRF window (if \c false, drop all widgets that make the window editable).
1609 NWidgetNewGRFDisplay(NWidgetBase *avs, NWidgetBase *acs, NWidgetBase *inf) : NWidgetContainer(NWID_HORIZONTAL)
1611 this->avs = avs;
1612 this->acs = acs;
1613 this->inf = inf;
1615 this->Add(this->avs);
1616 this->Add(this->acs);
1617 this->Add(this->inf);
1619 this->editable = true; // Temporary setting, 'real' value is set in SetupSmallestSize().
1622 virtual void SetupSmallestSize(Window *w, bool init_array)
1624 /* Copy state flag from the window. */
1625 assert(dynamic_cast<NewGRFWindow *>(w) != NULL);
1626 NewGRFWindow *ngw = (NewGRFWindow *)w;
1627 this->editable = ngw->editable;
1629 this->avs->SetupSmallestSize(w, init_array);
1630 this->acs->SetupSmallestSize(w, init_array);
1631 this->inf->SetupSmallestSize(w, init_array);
1633 uint min_avs_width = this->avs->smallest_x + this->avs->padding_left + this->avs->padding_right;
1634 uint min_acs_width = this->acs->smallest_x + this->acs->padding_left + this->acs->padding_right;
1635 uint min_inf_width = this->inf->smallest_x + this->inf->padding_left + this->inf->padding_right;
1637 uint min_avs_height = this->avs->smallest_y + this->avs->padding_top + this->avs->padding_bottom;
1638 uint min_acs_height = this->acs->smallest_y + this->acs->padding_top + this->acs->padding_bottom;
1639 uint min_inf_height = this->inf->smallest_y + this->inf->padding_top + this->inf->padding_bottom;
1641 /* Smallest window is in two column mode. */
1642 this->smallest_x = max(min_avs_width, min_acs_width) + INTER_COLUMN_SPACING + min_inf_width;
1643 this->smallest_y = max(min_inf_height, min_acs_height + INTER_LIST_SPACING + min_avs_height);
1645 /* Filling. */
1646 this->fill_x = LeastCommonMultiple(this->avs->fill_x, this->acs->fill_x);
1647 if (this->inf->fill_x > 0 && (this->fill_x == 0 || this->fill_x > this->inf->fill_x)) this->fill_x = this->inf->fill_x;
1649 this->fill_y = this->avs->fill_y;
1650 if (this->acs->fill_y > 0 && (this->fill_y == 0 || this->fill_y > this->acs->fill_y)) this->fill_y = this->acs->fill_y;
1651 this->fill_y = LeastCommonMultiple(this->fill_y, this->inf->fill_y);
1653 /* Resizing. */
1654 this->resize_x = LeastCommonMultiple(this->avs->resize_x, this->acs->resize_x);
1655 if (this->inf->resize_x > 0 && (this->resize_x == 0 || this->resize_x > this->inf->resize_x)) this->resize_x = this->inf->resize_x;
1657 this->resize_y = this->avs->resize_y;
1658 if (this->acs->resize_y > 0 && (this->resize_y == 0 || this->resize_y > this->acs->resize_y)) this->resize_y = this->acs->resize_y;
1659 this->resize_y = LeastCommonMultiple(this->resize_y, this->inf->resize_y);
1661 /* Make sure the height suits the 3 column (resp. not-editable) format; the 2 column format can easily fill space between the lists */
1662 this->smallest_y = ComputeMaxSize(min_acs_height, this->smallest_y + this->resize_y - 1, this->resize_y);
1665 virtual void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
1667 this->StoreSizePosition(sizing, x, y, given_width, given_height);
1669 uint min_avs_width = this->avs->smallest_x + this->avs->padding_left + this->avs->padding_right;
1670 uint min_acs_width = this->acs->smallest_x + this->acs->padding_left + this->acs->padding_right;
1671 uint min_inf_width = this->inf->smallest_x + this->inf->padding_left + this->inf->padding_right;
1673 uint min_list_width = max(min_avs_width, min_acs_width); // Smallest width of the lists such that they have equal width (incl padding).
1674 uint avs_extra_width = min_list_width - min_avs_width; // Additional width needed for avs to reach min_list_width.
1675 uint acs_extra_width = min_list_width - min_acs_width; // Additional width needed for acs to reach min_list_width.
1677 /* Use 2 or 3 columns? */
1678 uint min_three_columns = min_avs_width + min_acs_width + min_inf_width + 2 * INTER_COLUMN_SPACING;
1679 uint min_two_columns = min_list_width + min_inf_width + INTER_COLUMN_SPACING;
1680 bool use_three_columns = this->editable && (min_three_columns + MIN_EXTRA_FOR_3_COLUMNS <= given_width);
1682 /* Info panel is a separate column in both modes. Compute its width first. */
1683 uint extra_width, inf_width;
1684 if (use_three_columns) {
1685 extra_width = given_width - min_three_columns;
1686 inf_width = min(MAX_EXTRA_INFO_WIDTH, extra_width / 2);
1687 } else {
1688 extra_width = given_width - min_two_columns;
1689 inf_width = min(MAX_EXTRA_INFO_WIDTH, extra_width / 2);
1691 inf_width = ComputeMaxSize(this->inf->smallest_x, this->inf->smallest_x + inf_width, this->inf->GetHorizontalStepSize(sizing));
1692 extra_width -= inf_width - this->inf->smallest_x;
1694 uint inf_height = ComputeMaxSize(this->inf->smallest_y, given_height, this->inf->GetVerticalStepSize(sizing));
1696 if (use_three_columns) {
1697 /* Three column display, first make both lists equally wide, then divide whatever is left between both lists.
1698 * Only keep track of what avs gets, all other space goes to acs. */
1699 uint avs_width = min(avs_extra_width, extra_width);
1700 extra_width -= avs_width;
1701 extra_width -= min(acs_extra_width, extra_width);
1702 avs_width += extra_width / 2;
1704 avs_width = ComputeMaxSize(this->avs->smallest_x, this->avs->smallest_x + avs_width, this->avs->GetHorizontalStepSize(sizing));
1706 uint acs_width = given_width - // Remaining space, including horizontal padding.
1707 inf_width - this->inf->padding_left - this->inf->padding_right -
1708 avs_width - this->avs->padding_left - this->avs->padding_right - 2 * INTER_COLUMN_SPACING;
1709 acs_width = ComputeMaxSize(min_acs_width, acs_width, this->acs->GetHorizontalStepSize(sizing)) -
1710 this->acs->padding_left - this->acs->padding_right;
1712 /* Never use fill_y on these; the minimal size is chosen, so that the 3 column view looks nice */
1713 uint avs_height = ComputeMaxSize(this->avs->smallest_y, given_height, this->avs->resize_y);
1714 uint acs_height = ComputeMaxSize(this->acs->smallest_y, given_height, this->acs->resize_y);
1716 /* Assign size and position to the children. */
1717 if (rtl) {
1718 x += this->inf->padding_left;
1719 this->inf->AssignSizePosition(sizing, x, y + this->inf->padding_top, inf_width, inf_height, rtl);
1720 x += inf_width + this->inf->padding_right + INTER_COLUMN_SPACING;
1721 } else {
1722 x += this->avs->padding_left;
1723 this->avs->AssignSizePosition(sizing, x, y + this->avs->padding_top, avs_width, avs_height, rtl);
1724 x += avs_width + this->avs->padding_right + INTER_COLUMN_SPACING;
1727 x += this->acs->padding_left;
1728 this->acs->AssignSizePosition(sizing, x, y + this->acs->padding_top, acs_width, acs_height, rtl);
1729 x += acs_width + this->acs->padding_right + INTER_COLUMN_SPACING;
1731 if (rtl) {
1732 x += this->avs->padding_left;
1733 this->avs->AssignSizePosition(sizing, x, y + this->avs->padding_top, avs_width, avs_height, rtl);
1734 } else {
1735 x += this->inf->padding_left;
1736 this->inf->AssignSizePosition(sizing, x, y + this->inf->padding_top, inf_width, inf_height, rtl);
1738 } else {
1739 /* Two columns, all space in extra_width goes to both lists. Since the lists are underneath each other,
1740 * the column is min_list_width wide at least. */
1741 uint avs_width = ComputeMaxSize(this->avs->smallest_x, this->avs->smallest_x + avs_extra_width + extra_width,
1742 this->avs->GetHorizontalStepSize(sizing));
1743 uint acs_width = ComputeMaxSize(this->acs->smallest_x, this->acs->smallest_x + acs_extra_width + extra_width,
1744 this->acs->GetHorizontalStepSize(sizing));
1746 uint min_avs_height = (!this->editable) ? 0 : this->avs->smallest_y + this->avs->padding_top + this->avs->padding_bottom + INTER_LIST_SPACING;
1747 uint min_acs_height = this->acs->smallest_y + this->acs->padding_top + this->acs->padding_bottom;
1748 uint extra_height = given_height - min_acs_height - min_avs_height;
1750 /* Never use fill_y on these; instead use the INTER_LIST_SPACING as filler */
1751 uint avs_height = ComputeMaxSize(this->avs->smallest_y, this->avs->smallest_y + extra_height / 2, this->avs->resize_y);
1752 if (this->editable) extra_height -= avs_height - this->avs->smallest_y;
1753 uint acs_height = ComputeMaxSize(this->acs->smallest_y, this->acs->smallest_y + extra_height, this->acs->resize_y);
1755 /* Assign size and position to the children. */
1756 if (rtl) {
1757 x += this->inf->padding_left;
1758 this->inf->AssignSizePosition(sizing, x, y + this->inf->padding_top, inf_width, inf_height, rtl);
1759 x += inf_width + this->inf->padding_right + INTER_COLUMN_SPACING;
1761 this->acs->AssignSizePosition(sizing, x + this->acs->padding_left, y + this->acs->padding_top, acs_width, acs_height, rtl);
1762 if (this->editable) {
1763 this->avs->AssignSizePosition(sizing, x + this->avs->padding_left, y + given_height - avs_height - this->avs->padding_bottom, avs_width, avs_height, rtl);
1764 } else {
1765 this->avs->AssignSizePosition(sizing, 0, 0, this->avs->smallest_x, this->avs->smallest_y, rtl);
1767 } else {
1768 this->acs->AssignSizePosition(sizing, x + this->acs->padding_left, y + this->acs->padding_top, acs_width, acs_height, rtl);
1769 if (this->editable) {
1770 this->avs->AssignSizePosition(sizing, x + this->avs->padding_left, y + given_height - avs_height - this->avs->padding_bottom, avs_width, avs_height, rtl);
1771 } else {
1772 this->avs->AssignSizePosition(sizing, 0, 0, this->avs->smallest_x, this->avs->smallest_y, rtl);
1774 uint dx = this->acs->current_x + this->acs->padding_left + this->acs->padding_right;
1775 if (this->editable) {
1776 dx = max(dx, this->avs->current_x + this->avs->padding_left + this->avs->padding_right);
1778 x += dx + INTER_COLUMN_SPACING + this->inf->padding_left;
1779 this->inf->AssignSizePosition(sizing, x, y + this->inf->padding_top, inf_width, inf_height, rtl);
1784 virtual NWidgetCore *GetWidgetFromPos(int x, int y)
1786 if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
1788 NWidgetCore *nw = (this->editable) ? this->avs->GetWidgetFromPos(x, y) : NULL;
1789 if (nw == NULL) nw = this->acs->GetWidgetFromPos(x, y);
1790 if (nw == NULL) nw = this->inf->GetWidgetFromPos(x, y);
1791 return nw;
1794 void Draw (BlitArea *dpi, const Window *w) OVERRIDE
1796 if (this->editable) this->avs->Draw (dpi, w);
1797 this->acs->Draw (dpi, w);
1798 this->inf->Draw (dpi, w);
1802 const uint NWidgetNewGRFDisplay::INTER_LIST_SPACING = WD_RESIZEBOX_WIDTH + 1;
1803 const uint NWidgetNewGRFDisplay::INTER_COLUMN_SPACING = WD_RESIZEBOX_WIDTH;
1804 const uint NWidgetNewGRFDisplay::MAX_EXTRA_INFO_WIDTH = 150;
1805 const uint NWidgetNewGRFDisplay::MIN_EXTRA_FOR_3_COLUMNS = 50;
1807 static const NWidgetPart _nested_newgrf_actives_widgets[] = {
1808 /* Left side, presets. */
1809 NWidget(NWID_HORIZONTAL),
1810 NWidget(WWT_TEXT, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_SETTINGS_SELECT_PRESET, STR_NULL),
1811 SetPadding(0, WD_FRAMETEXT_RIGHT, 0, 0),
1812 NWidget(WWT_DROPDOWN, COLOUR_YELLOW, WID_NS_PRESET_LIST), SetFill(1, 0), SetResize(1, 0),
1813 SetDataTip(STR_JUST_STRING, STR_NEWGRF_SETTINGS_PRESET_LIST_TOOLTIP),
1814 EndContainer(),
1815 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1816 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_PRESET_SAVE), SetFill(1, 0), SetResize(1, 0),
1817 SetDataTip(STR_NEWGRF_SETTINGS_PRESET_SAVE, STR_NEWGRF_SETTINGS_PRESET_SAVE_TOOLTIP),
1818 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_PRESET_DELETE), SetFill(1, 0), SetResize(1, 0),
1819 SetDataTip(STR_NEWGRF_SETTINGS_PRESET_DELETE, STR_NEWGRF_SETTINGS_PRESET_DELETE_TOOLTIP),
1820 EndContainer(),
1822 NWidget(NWID_SPACER), SetMinimalSize(0, WD_RESIZEBOX_WIDTH), SetResize(1, 0), SetFill(1, 0),
1823 NWidget(WWT_PANEL, COLOUR_MAUVE),
1824 NWidget(WWT_LABEL, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_SETTINGS_ACTIVE_LIST, STR_NULL),
1825 SetFill(1, 0), SetResize(1, 0), SetPadding(3, WD_FRAMETEXT_RIGHT, 0, WD_FRAMETEXT_LEFT),
1826 /* Left side, active grfs. */
1827 NWidget(NWID_HORIZONTAL), SetPadding(0, 2, 0, 2),
1828 NWidget(WWT_PANEL, COLOUR_MAUVE),
1829 NWidget(WWT_INSET, COLOUR_MAUVE, WID_NS_FILE_LIST), SetMinimalSize(100, 1), SetPadding(2, 2, 2, 2),
1830 SetFill(1, 1), SetResize(1, 1), SetScrollbar(WID_NS_SCROLLBAR), SetDataTip(STR_NULL, STR_NEWGRF_SETTINGS_FILE_TOOLTIP),
1831 EndContainer(),
1832 EndContainer(),
1833 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_NS_SCROLLBAR),
1834 EndContainer(),
1835 /* Buttons. */
1836 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NS_SHOW_REMOVE),
1837 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPadding(2, 2, 2, 2), SetPIP(0, WD_RESIZEBOX_WIDTH, 0),
1838 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_REMOVE), SetFill(1, 0), SetResize(1, 0),
1839 SetDataTip(STR_NEWGRF_SETTINGS_REMOVE, STR_NEWGRF_SETTINGS_REMOVE_TOOLTIP),
1840 NWidget(NWID_VERTICAL),
1841 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_MOVE_UP), SetFill(1, 0), SetResize(1, 0),
1842 SetDataTip(STR_NEWGRF_SETTINGS_MOVEUP, STR_NEWGRF_SETTINGS_MOVEUP_TOOLTIP),
1843 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_MOVE_DOWN), SetFill(1, 0), SetResize(1, 0),
1844 SetDataTip(STR_NEWGRF_SETTINGS_MOVEDOWN, STR_NEWGRF_SETTINGS_MOVEDOWN_TOOLTIP),
1845 EndContainer(),
1846 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_UPGRADE), SetFill(1, 0), SetResize(1, 0),
1847 SetDataTip(STR_NEWGRF_SETTINGS_UPGRADE, STR_NEWGRF_SETTINGS_UPGRADE_TOOLTIP),
1848 EndContainer(),
1850 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPadding(2, 2, 2, 2),
1851 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_RESCAN_FILES2), SetFill(1, 0), SetResize(1, 0),
1852 SetDataTip(STR_NEWGRF_SETTINGS_RESCAN_FILES, STR_NEWGRF_SETTINGS_RESCAN_FILES_TOOLTIP),
1853 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_CONTENT_DOWNLOAD2), SetFill(1, 0), SetResize(1, 0),
1854 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
1855 EndContainer(),
1856 EndContainer(),
1857 EndContainer(),
1860 static const NWidgetPart _nested_newgrf_availables_widgets[] = {
1861 NWidget(WWT_PANEL, COLOUR_MAUVE),
1862 NWidget(WWT_LABEL, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_SETTINGS_INACTIVE_LIST, STR_NULL),
1863 SetFill(1, 0), SetResize(1, 0), SetPadding(3, WD_FRAMETEXT_RIGHT, 0, WD_FRAMETEXT_LEFT),
1864 /* Left side, available grfs, filter edit box. */
1865 NWidget(NWID_HORIZONTAL), SetPadding(WD_TEXTPANEL_TOP, 0, WD_TEXTPANEL_BOTTOM, 0),
1866 SetPIP(WD_FRAMETEXT_LEFT, WD_FRAMETEXT_RIGHT, WD_FRAMETEXT_RIGHT),
1867 NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_NEWGRF_FILTER_TITLE, STR_NULL),
1868 NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_NS_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0),
1869 SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1870 EndContainer(),
1871 /* Left side, available grfs. */
1872 NWidget(NWID_HORIZONTAL), SetPadding(0, 2, 0, 2),
1873 NWidget(WWT_PANEL, COLOUR_MAUVE),
1874 NWidget(WWT_INSET, COLOUR_MAUVE, WID_NS_AVAIL_LIST), SetMinimalSize(100, 1), SetPadding(2, 2, 2, 2),
1875 SetFill(1, 1), SetResize(1, 1), SetScrollbar(WID_NS_SCROLL2BAR),
1876 EndContainer(),
1877 EndContainer(),
1878 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_NS_SCROLL2BAR),
1879 EndContainer(),
1880 /* Left side, available grfs, buttons. */
1881 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPadding(2, 2, 2, 2), SetPIP(0, WD_RESIZEBOX_WIDTH, 0),
1882 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_ADD), SetFill(1, 0), SetResize(1, 0),
1883 SetDataTip(STR_NEWGRF_SETTINGS_ADD, STR_NEWGRF_SETTINGS_ADD_FILE_TOOLTIP),
1884 NWidget(NWID_VERTICAL),
1885 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_RESCAN_FILES), SetFill(1, 0), SetResize(1, 0),
1886 SetDataTip(STR_NEWGRF_SETTINGS_RESCAN_FILES, STR_NEWGRF_SETTINGS_RESCAN_FILES_TOOLTIP),
1887 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_CONTENT_DOWNLOAD), SetFill(1, 0), SetResize(1, 0),
1888 SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
1889 EndContainer(),
1890 EndContainer(),
1891 EndContainer(),
1894 static const NWidgetPart _nested_newgrf_infopanel_widgets[] = {
1895 /* Right side, info panel. */
1896 NWidget(NWID_VERTICAL), SetPadding(0, 0, 2, 0),
1897 NWidget(WWT_PANEL, COLOUR_MAUVE), SetPadding(0, 0, 2, 0),
1898 NWidget(WWT_EMPTY, COLOUR_MAUVE, WID_NS_NEWGRF_INFO_TITLE), SetFill(1, 0), SetResize(1, 0),
1899 NWidget(WWT_EMPTY, COLOUR_MAUVE, WID_NS_NEWGRF_INFO), SetFill(1, 1), SetResize(1, 1), SetMinimalSize(150, 100),
1900 EndContainer(),
1901 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_OPEN_URL), SetFill(1, 0), SetResize(1, 0),
1902 SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
1903 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_NEWGRF_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0),
1904 SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
1905 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1906 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_NEWGRF_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0),
1907 SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
1908 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_NEWGRF_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0),
1909 SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
1910 EndContainer(),
1911 EndContainer(),
1912 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NS_SHOW_APPLY),
1913 /* Right side, buttons. */
1914 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, WD_RESIZEBOX_WIDTH, 0),
1915 NWidget(NWID_VERTICAL),
1916 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_SET_PARAMETERS), SetFill(1, 0), SetResize(1, 0),
1917 SetDataTip(STR_NEWGRF_SETTINGS_SET_PARAMETERS, STR_NULL),
1918 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_TOGGLE_PALETTE), SetFill(1, 0), SetResize(1, 0),
1919 SetDataTip(STR_NEWGRF_SETTINGS_TOGGLE_PALETTE, STR_NEWGRF_SETTINGS_TOGGLE_PALETTE_TOOLTIP),
1920 EndContainer(),
1921 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_APPLY_CHANGES), SetFill(1, 0), SetResize(1, 0),
1922 SetDataTip(STR_NEWGRF_SETTINGS_APPLY_CHANGES, STR_NULL),
1923 EndContainer(),
1924 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_NS_VIEW_PARAMETERS), SetFill(1, 0), SetResize(1, 0),
1925 SetDataTip(STR_NEWGRF_SETTINGS_SHOW_PARAMETERS, STR_NULL),
1926 EndContainer(),
1929 /** Construct nested container widget for managing the lists and the info panel of the NewGRF GUI. */
1930 NWidgetBase* NewGRFDisplay(int *biggest_index)
1932 NWidgetBase *avs = MakeNWidgets(_nested_newgrf_availables_widgets, lengthof(_nested_newgrf_availables_widgets), biggest_index, NULL);
1934 int biggest2;
1935 NWidgetBase *acs = MakeNWidgets(_nested_newgrf_actives_widgets, lengthof(_nested_newgrf_actives_widgets), &biggest2, NULL);
1936 *biggest_index = max(*biggest_index, biggest2);
1938 NWidgetBase *inf = MakeNWidgets(_nested_newgrf_infopanel_widgets, lengthof(_nested_newgrf_infopanel_widgets), &biggest2, NULL);
1939 *biggest_index = max(*biggest_index, biggest2);
1941 return new NWidgetNewGRFDisplay(avs, acs, inf);
1944 /* Widget definition of the manage newgrfs window */
1945 static const NWidgetPart _nested_newgrf_widgets[] = {
1946 NWidget(NWID_HORIZONTAL),
1947 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
1948 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_NEWGRF_SETTINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1949 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
1950 EndContainer(),
1951 NWidget(WWT_PANEL, COLOUR_MAUVE),
1952 NWidgetFunction(NewGRFDisplay), SetPadding(WD_RESIZEBOX_WIDTH, WD_RESIZEBOX_WIDTH, 2, WD_RESIZEBOX_WIDTH),
1953 /* Resize button. */
1954 NWidget(NWID_HORIZONTAL),
1955 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1956 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
1957 EndContainer(),
1958 EndContainer(),
1961 /* Window preferences of the manage newgrfs window */
1962 static WindowDesc::Prefs _newgrf_prefs ("settings_newgrf");
1964 /* Window definition of the manage newgrfs window */
1965 static const WindowDesc _newgrf_desc(
1966 WDP_CENTER, 300, 263,
1967 WC_GAME_OPTIONS, WC_NONE,
1969 _nested_newgrf_widgets, lengthof(_nested_newgrf_widgets),
1970 &_newgrf_prefs
1974 * Callback function for the newgrf 'apply changes' confirmation window
1975 * @param w Window which is calling this callback
1976 * @param confirmed boolean value, true when yes was clicked, false otherwise
1978 static void NewGRFConfirmationCallback(Window *w, bool confirmed)
1980 if (confirmed) {
1981 DeleteWindowByClass(WC_GRF_PARAMETERS);
1982 NewGRFWindow *nw = dynamic_cast<NewGRFWindow*>(w);
1984 GamelogGRFBegin();
1985 GamelogGRFUpdate(_grfconfig, nw->actives); // log GRF changes
1986 CopyGRFConfigList(nw->orig_list, nw->actives, false);
1987 ReloadNewGRFData();
1988 GamelogGRFEnd();
1990 /* Show new, updated list */
1991 GRFConfig *c;
1992 int i = 0;
1993 for (c = nw->actives; c != NULL && c != nw->active_sel; c = c->next, i++) {}
1994 CopyGRFConfigList(&nw->actives, *nw->orig_list, false);
1995 for (c = nw->actives; c != NULL && i > 0; c = c->next, i--) {}
1996 nw->active_sel = c;
1997 nw->avails.ForceRebuild();
1999 w->InvalidateData();
2001 ReInitAllWindows();
2002 DeleteWindowByClass(WC_BUILD_OBJECT);
2009 * Setup the NewGRF gui
2010 * @param editable allow the user to make changes to the grfconfig in the window
2011 * @param show_params show information about what parameters are set for the grf files
2012 * @param exec_changes if changes are made to the list (editable is true), apply these
2013 * changes immediately or only update the list
2014 * @param config pointer to a linked-list of grfconfig's that will be shown
2016 void ShowNewGRFSettings(bool editable, bool show_params, bool exec_changes, GRFConfig **config)
2018 DeleteWindowByClass(WC_GAME_OPTIONS);
2019 new NewGRFWindow(&_newgrf_desc, editable, show_params, exec_changes, config);
2022 /** Widget parts of the save preset window. */
2023 static const NWidgetPart _nested_save_preset_widgets[] = {
2024 NWidget(NWID_HORIZONTAL),
2025 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2026 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_SAVE_PRESET_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2027 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
2028 EndContainer(),
2029 NWidget(WWT_PANEL, COLOUR_GREY),
2030 NWidget(NWID_HORIZONTAL),
2031 NWidget(WWT_INSET, COLOUR_GREY, WID_SVP_PRESET_LIST), SetPadding(2, 1, 0, 2),
2032 SetDataTip(0x0, STR_SAVE_PRESET_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SVP_SCROLLBAR), EndContainer(),
2033 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SVP_SCROLLBAR),
2034 EndContainer(),
2035 NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SVP_EDITBOX), SetPadding(3, 2, 2, 2), SetFill(1, 0), SetResize(1, 0),
2036 SetDataTip(STR_SAVE_PRESET_TITLE, STR_SAVE_PRESET_EDITBOX_TOOLTIP),
2037 EndContainer(),
2038 NWidget(NWID_HORIZONTAL),
2039 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SVP_CANCEL), SetDataTip(STR_SAVE_PRESET_CANCEL, STR_SAVE_PRESET_CANCEL_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
2040 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SVP_SAVE), SetDataTip(STR_SAVE_PRESET_SAVE, STR_SAVE_PRESET_SAVE_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
2041 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
2042 EndContainer(),
2045 /** Window preferences of the preset save window. */
2046 static WindowDesc::Prefs _save_preset_prefs ("save_preset");
2048 /** Window description of the preset save window. */
2049 static const WindowDesc _save_preset_desc(
2050 WDP_CENTER, 140, 110,
2051 WC_SAVE_PRESET, WC_GAME_OPTIONS,
2052 WDF_MODAL,
2053 _nested_save_preset_widgets, lengthof(_nested_save_preset_widgets),
2054 &_save_preset_prefs
2057 /** Class for the save preset window. */
2058 struct SavePresetWindow : public Window {
2059 QueryStringN<32> presetname_editbox; ///< Edit box of the save preset.
2060 GRFPresetList presets; ///< Available presets.
2061 Scrollbar *vscroll; ///< Pointer to the scrollbar widget.
2062 int selected; ///< Selected entry in the preset list, or \c -1 if none selected.
2065 * Constructor of the save preset window.
2066 * @param initial_text Initial text to display in the edit box, or \c NULL.
2068 SavePresetWindow (const char *initial_text) :
2069 Window (&_save_preset_desc), presetname_editbox(),
2070 presets(), vscroll (NULL), selected (-1)
2072 GetGRFPresetList(&this->presets);
2073 if (initial_text != NULL) {
2074 for (uint i = 0; i < this->presets.Length(); i++) {
2075 if (!strcmp(initial_text, this->presets[i])) {
2076 this->selected = i;
2077 break;
2082 this->querystrings[WID_SVP_EDITBOX] = &this->presetname_editbox;
2083 this->presetname_editbox.ok_button = WID_SVP_SAVE;
2084 this->presetname_editbox.cancel_button = WID_SVP_CANCEL;
2086 this->CreateNestedTree();
2087 this->vscroll = this->GetScrollbar(WID_SVP_SCROLLBAR);
2088 this->InitNested(0);
2090 this->vscroll->SetCount(this->presets.Length());
2091 this->SetFocusedWidget(WID_SVP_EDITBOX);
2092 if (initial_text != NULL) this->presetname_editbox.Assign(initial_text);
2095 ~SavePresetWindow()
2099 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2101 switch (widget) {
2102 case WID_SVP_PRESET_LIST: {
2103 resize->height = FONT_HEIGHT_NORMAL + 2U;
2104 size->height = 0;
2105 for (uint i = 0; i < this->presets.Length(); i++) {
2106 Dimension d = GetStringBoundingBox(this->presets[i]);
2107 size->width = max(size->width, d.width + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT);
2108 resize->height = max(resize->height, d.height);
2110 size->height = ClampU(this->presets.Length(), 5, 20) * resize->height + 1;
2111 break;
2116 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
2118 switch (widget) {
2119 case WID_SVP_PRESET_LIST: {
2120 GfxFillRect (dpi, r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
2122 uint step_height = this->GetWidget<NWidgetBase>(WID_SVP_PRESET_LIST)->resize_y;
2123 int offset_y = (step_height - FONT_HEIGHT_NORMAL) / 2;
2124 uint y = r.top + WD_FRAMERECT_TOP;
2125 uint min_index = this->vscroll->GetPosition();
2126 uint max_index = min(min_index + this->vscroll->GetCapacity(), this->presets.Length());
2128 for (uint i = min_index; i < max_index; i++) {
2129 if ((int)i == this->selected) GfxFillRect (dpi, r.left + 1, y, r.right - 1, y + step_height - 2, PC_DARK_BLUE);
2131 const char *text = this->presets[i];
2132 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right, y + offset_y, text, ((int)i == this->selected) ? TC_WHITE : TC_SILVER);
2133 y += step_height;
2135 break;
2140 virtual void OnClick(Point pt, int widget, int click_count)
2142 switch (widget) {
2143 case WID_SVP_PRESET_LIST: {
2144 uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SVP_PRESET_LIST);
2145 if (row < this->presets.Length()) {
2146 this->selected = row;
2147 this->presetname_editbox.Assign(this->presets[row]);
2148 this->SetWidgetDirty(WID_SVP_PRESET_LIST);
2149 this->SetWidgetDirty(WID_SVP_EDITBOX);
2151 break;
2154 case WID_SVP_CANCEL:
2155 this->Delete();
2156 break;
2158 case WID_SVP_SAVE: {
2159 Window *w = FindWindowById(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE);
2160 if (w != NULL && !StrEmpty(this->presetname_editbox.GetText())) w->OnQueryTextFinished(this->presetname_editbox.buffer);
2161 this->Delete();
2162 break;
2167 virtual void OnResize()
2169 this->vscroll->SetCapacityFromWidget(this, WID_SVP_PRESET_LIST);
2174 * Open the window for saving a preset.
2175 * @param initial_text Initial text to display in the edit box, or \c NULL.
2177 static void ShowSavePresetWindow(const char *initial_text)
2179 DeleteWindowByClass(WC_SAVE_PRESET);
2180 new SavePresetWindow(initial_text);
2183 /** Widgets for the progress window. */
2184 static const NWidgetPart _nested_scan_progress_widgets[] = {
2185 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NEWGRF_SCAN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2186 NWidget(WWT_PANEL, COLOUR_GREY),
2187 NWidget(NWID_HORIZONTAL), SetPIP(20, 0, 20),
2188 NWidget(NWID_VERTICAL), SetPIP(11, 8, 11),
2189 NWidget(WWT_LABEL, INVALID_COLOUR), SetDataTip(STR_NEWGRF_SCAN_MESSAGE, STR_NULL), SetFill(1, 0),
2190 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SP_PROGRESS_BAR), SetFill(1, 0),
2191 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SP_PROGRESS_TEXT), SetFill(1, 0),
2192 EndContainer(),
2193 EndContainer(),
2194 EndContainer(),
2197 /** Description of the widgets and other settings of the window. */
2198 static const WindowDesc _scan_progress_desc(
2199 WDP_CENTER, 0, 0,
2200 WC_MODAL_PROGRESS, WC_NONE,
2202 _nested_scan_progress_widgets, lengthof(_nested_scan_progress_widgets)
2205 /** Window for showing the progress of NewGRF scanning. */
2206 struct ScanProgressWindow : public Window {
2207 char *last_name; ///< The name of the last 'seen' NewGRF.
2208 int scanned; ///< The number of NewGRFs that we have seen.
2210 /** Create the window. */
2211 ScanProgressWindow() : Window(&_scan_progress_desc), last_name(NULL), scanned(0)
2213 this->InitNested(1);
2216 /** Free the last name buffer. */
2217 ~ScanProgressWindow()
2219 free(last_name);
2222 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2224 switch (widget) {
2225 case WID_SP_PROGRESS_BAR: {
2226 SetDParamMaxValue(0, 100);
2227 *size = GetStringBoundingBox(STR_GENERATION_PROGRESS);
2228 /* We need some spacing for the 'border' */
2229 size->height += 8;
2230 size->width += 8;
2231 break;
2234 case WID_SP_PROGRESS_TEXT:
2235 SetDParamMaxDigits(0, 4);
2236 SetDParamMaxDigits(1, 4);
2237 /* We really don't know the width. We could determine it by scanning the NewGRFs,
2238 * but this is the status window for scanning them... */
2239 size->width = max(400U, GetStringBoundingBox(STR_NEWGRF_SCAN_STATUS).width);
2240 size->height = FONT_HEIGHT_NORMAL * 2 + WD_PAR_VSEP_NORMAL;
2241 break;
2245 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
2247 switch (widget) {
2248 case WID_SP_PROGRESS_BAR: {
2249 /* Draw the % complete with a bar and a text */
2250 DrawFrameRect (dpi, r.left, r.top, r.right, r.bottom, COLOUR_GREY, FR_BORDERONLY);
2251 uint percent = scanned * 100 / max(1U, _settings_client.gui.last_newgrf_count);
2252 DrawFrameRect (dpi, r.left + 1, r.top + 1, (int)((r.right - r.left - 2) * percent / 100) + r.left + 1, r.bottom - 1, COLOUR_MAUVE, FR_NONE);
2253 SetDParam(0, percent);
2254 DrawString (dpi, r.left, r.right, r.top + 5, STR_GENERATION_PROGRESS, TC_FROMSTRING, SA_HOR_CENTER);
2255 break;
2258 case WID_SP_PROGRESS_TEXT:
2259 SetDParam(0, this->scanned);
2260 SetDParam(1, _settings_client.gui.last_newgrf_count);
2261 DrawString (dpi, r.left, r.right, r.top, STR_NEWGRF_SCAN_STATUS, TC_FROMSTRING, SA_HOR_CENTER);
2263 DrawString (dpi, r.left, r.right, r.top + FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL, this->last_name == NULL ? "" : this->last_name, TC_BLACK, SA_HOR_CENTER);
2264 break;
2269 * Update the NewGRF scan status.
2270 * @param num The number of NewGRFs scanned so far.
2271 * @param name The name of the last scanned NewGRF.
2273 void UpdateNewGRFScanStatus(uint num, const char *name)
2275 free(this->last_name);
2276 if (name == NULL) {
2277 char buf[256];
2278 GetString (buf, STR_NEWGRF_SCAN_ARCHIVES);
2279 this->last_name = xstrdup(buf);
2280 } else {
2281 this->last_name = xstrdup(name);
2283 this->scanned = num;
2284 if (num > _settings_client.gui.last_newgrf_count) _settings_client.gui.last_newgrf_count = num;
2286 this->SetDirty();
2291 * Update the NewGRF scan status.
2292 * @param num The number of NewGRFs scanned so far.
2293 * @param name The name of the last scanned NewGRF.
2295 void UpdateNewGRFScanStatus(uint num, const char *name)
2297 ScanProgressWindow *w = dynamic_cast<ScanProgressWindow *>(FindWindowByClass(WC_MODAL_PROGRESS));
2298 if (w == NULL) w = new ScanProgressWindow();
2299 w->UpdateNewGRFScanStatus(num, name);