Show snow on depot tiles. (Looks weird my ass)
[openttd-joker.git] / src / build_vehicle_gui.cpp
blobc5bb69f29aa7325a42a3696fb9fac860e5be7244
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 build_vehicle_gui.cpp GUI for building vehicles. */
12 #include "stdafx.h"
13 #include "engine_base.h"
14 #include "engine_func.h"
15 #include "station_base.h"
16 #include "network/network.h"
17 #include "articulated_vehicles.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "company_func.h"
21 #include "vehicle_gui.h"
22 #include "newgrf_engine.h"
23 #include "newgrf_text.h"
24 #include "group.h"
25 #include "string_func.h"
26 #include "strings_func.h"
27 #include "window_func.h"
28 #include "date_func.h"
29 #include "vehicle_func.h"
30 #include "widgets/dropdown_func.h"
31 #include "engine_gui.h"
32 #include "cargotype.h"
33 #include "core/geometry_func.hpp"
34 #include "autoreplace_func.h"
35 #include "train.h"
37 #include "widgets/build_vehicle_widget.h"
39 #include "table/strings.h"
41 #include "safeguards.h"
43 /**
44 * Get the height of a single 'entry' in the engine lists.
45 * @param type the vehicle type to get the height of
46 * @return the height for the entry
48 uint GetEngineListHeight(VehicleType type)
50 return max<uint>(FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM, GetVehicleImageCellSize(type, EIT_PURCHASE).height);
53 /**
54 * Normal layout for roadvehicles, ships and airplanes.
56 static const NWidgetPart _nested_build_vehicle_widgets[] = {
57 NWidget(NWID_HORIZONTAL),
58 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
59 NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
60 NWidget(WWT_SHADEBOX, COLOUR_GREY),
61 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
62 NWidget(WWT_STICKYBOX, COLOUR_GREY),
63 EndContainer(),
64 NWidget(WWT_PANEL, COLOUR_GREY),
65 NWidget(NWID_VERTICAL),
66 NWidget(NWID_HORIZONTAL),
67 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
68 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
69 EndContainer(),
70 NWidget(NWID_HORIZONTAL),
71 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_ENGINES),
72 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
73 EndContainer(),
74 EndContainer(),
75 EndContainer(),
76 /* Vehicle list. */
77 NWidget(NWID_HORIZONTAL),
78 NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
79 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR),
80 EndContainer(),
81 /* Panel with details. */
82 NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
83 /* Build/rename buttons, resize button. */
84 NWidget(NWID_HORIZONTAL),
85 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL),
86 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0),
87 EndContainer(),
88 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
89 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
90 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
91 EndContainer(),
94 /* Advanced layout for trains. */
96 static const NWidgetPart _nested_build_vehicle_widgets_train_advanced[] = {
97 NWidget(NWID_HORIZONTAL),
98 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
99 NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
100 NWidget(WWT_SHADEBOX, COLOUR_GREY),
101 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
102 NWidget(WWT_STICKYBOX, COLOUR_GREY),
103 EndContainer(),
105 NWidget(NWID_HORIZONTAL),
106 /* First half of the window contains locomotives. */
107 NWidget(NWID_VERTICAL),
108 NWidget(NWID_HORIZONTAL),
109 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 0),
110 NWidget(WWT_LABEL, COLOUR_GREY, WID_BV_CAPTION_LOCO), SetDataTip(STR_WHITE_STRING, STR_NULL), SetResize(1, 0), SetFill(1, 0),
111 EndContainer(),
112 EndContainer(),
113 NWidget(WWT_PANEL, COLOUR_GREY),
114 NWidget(NWID_VERTICAL),
115 NWidget(NWID_HORIZONTAL),
116 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASSENDING_DESCENDING_LOCO), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
117 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN_LOCO), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
118 EndContainer(),
119 NWidget(NWID_HORIZONTAL),
120 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_LOCOS),
121 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN_LOCO), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
122 EndContainer(),
123 EndContainer(),
124 EndContainer(),
125 /* Vehicle list for locomotives. */
126 NWidget(NWID_HORIZONTAL),
127 NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST_LOCO), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR_LOCO),
128 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR_LOCO),
129 EndContainer(),
130 /* Panel with details for locomotives. */
131 NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL_LOCO), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
132 /* Build/rename buttons, resize button for locomotives. */
133 NWidget(NWID_HORIZONTAL),
134 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL_LOCO),
135 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD_LOCO), SetMinimalSize(50, 1), SetResize(1, 0), SetFill(1, 0),
136 EndContainer(),
137 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE_LOCO), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
138 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME_LOCO), SetResize(1, 0), SetFill(1, 0),
139 EndContainer(),
141 EndContainer(),
142 /* Second half of the window contains wagons. */
143 NWidget(NWID_VERTICAL),
144 NWidget(NWID_HORIZONTAL),
145 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 0),
146 NWidget(WWT_LABEL, COLOUR_GREY, WID_BV_CAPTION_WAGON), SetDataTip(STR_WHITE_STRING, STR_NULL), SetResize(1, 0), SetFill(1, 0),
147 EndContainer(),
148 EndContainer(),
149 NWidget(WWT_PANEL, COLOUR_GREY),
150 NWidget(NWID_VERTICAL),
151 NWidget(NWID_HORIZONTAL),
152 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASSENDING_DESCENDING_WAGON), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0),
153 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN_WAGON), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
154 EndContainer(),
155 NWidget(NWID_HORIZONTAL),
156 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDDEN_WAGONS),
157 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN_WAGON), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
158 EndContainer(),
159 EndContainer(),
160 EndContainer(),
161 /* Vehicle list for wagons. */
162 NWidget(NWID_HORIZONTAL),
163 NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST_WAGON), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR_WAGON),
164 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BV_SCROLLBAR_WAGON),
165 EndContainer(),
166 /* Panel with details for wagons. */
167 NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL_WAGON), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
168 /* Build/rename buttons, resize button for wagons. */
169 NWidget(NWID_HORIZONTAL),
170 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL_WAGON),
171 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD_WAGON), SetMinimalSize(50, 1), SetResize(1, 0), SetFill(1, 0),
172 EndContainer(),
173 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE_WAGON), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
174 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME_WAGON), SetResize(1, 0), SetFill(1, 0),
175 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
176 EndContainer(),
177 EndContainer(),
178 EndContainer(),
181 /** Special cargo filter criteria */
182 static const CargoID CF_ANY = CT_NO_REFIT; ///< Show all vehicles independent of carried cargo (i.e. no filtering)
183 static const CargoID CF_NONE = CT_INVALID; ///< Show only vehicles which do not carry cargo (e.g. train engines)
185 bool _engine_sort_direction; ///< \c false = descending, \c true = ascending.
186 byte _engine_sort_last_criteria[] = {0, 0, 0, 0}; ///< Last set sort criteria, for each vehicle type.
187 bool _engine_sort_last_order[] = {false, false, false, false}; ///< Last set direction of the sort order, for each vehicle type.
188 bool _engine_sort_show_hidden_engines[] = {false, false, false, false}; ///< Last set 'show hidden engines' setting for each vehicle type.
189 bool _engine_sort_show_hidden_locos = false; ///< Last set 'show hidden locos' setting.
190 bool _engine_sort_show_hidden_wagons = false; ///< Last set 'show hidden wagons' setting.
192 static CargoID _engine_sort_last_cargo_criteria[] = {CF_ANY, CF_ANY, CF_ANY, CF_ANY}; ///< Last set filter criteria, for each vehicle type.
194 static bool _internal_sort_order_loco; ///< false = descending, true = ascending
195 static byte _last_sort_criteria_loco = 0;
196 static bool _last_sort_order_loco = false;
197 static CargoID _last_filter_criteria_loco = CF_ANY;
199 static bool _internal_sort_order_wagon; ///< false = descending, true = ascending
200 static byte _last_sort_criteria_wagon = 0;
201 static bool _last_sort_order_wagon = false;
202 static CargoID _last_filter_criteria_wagon = CF_ANY;
205 * Determines order of engines by engineID
206 * @param *a first engine to compare
207 * @param *b second engine to compare
208 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
210 static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b)
212 int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
214 return _engine_sort_direction ? -r : r;
218 * Determines order of engines by introduction date
219 * @param *a first engine to compare
220 * @param *b second engine to compare
221 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
223 static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b)
225 const int va = Engine::Get(*a)->intro_date;
226 const int vb = Engine::Get(*b)->intro_date;
227 const int r = va - vb;
229 /* Use EngineID to sort instead since we want consistent sorting */
230 if (r == 0) return EngineNumberSorter(a, b);
231 return _engine_sort_direction ? -r : r;
235 * Determines order of engines by name
236 * @param *a first engine to compare
237 * @param *b second engine to compare
238 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
240 static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b)
242 static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
243 static char last_name[2][64] = { "\0", "\0" };
245 const EngineID va = *a;
246 const EngineID vb = *b;
248 if (va != last_engine[0]) {
249 last_engine[0] = va;
250 SetDParam(0, va);
251 GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
254 if (vb != last_engine[1]) {
255 last_engine[1] = vb;
256 SetDParam(0, vb);
257 GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
260 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
262 /* Use EngineID to sort instead since we want consistent sorting */
263 if (r == 0) return EngineNumberSorter(a, b);
264 return _engine_sort_direction ? -r : r;
268 * Determines order of engines by reliability
269 * @param *a first engine to compare
270 * @param *b second engine to compare
271 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
273 static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b)
275 const int va = Engine::Get(*a)->reliability;
276 const int vb = Engine::Get(*b)->reliability;
277 const int r = va - vb;
279 /* Use EngineID to sort instead since we want consistent sorting */
280 if (r == 0) return EngineNumberSorter(a, b);
281 return _engine_sort_direction ? -r : r;
285 * Determines order of engines by purchase cost
286 * @param *a first engine to compare
287 * @param *b second engine to compare
288 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
290 static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b)
292 Money va = Engine::Get(*a)->GetCost();
293 Money vb = Engine::Get(*b)->GetCost();
294 int r = ClampToI32(va - vb);
296 /* Use EngineID to sort instead since we want consistent sorting */
297 if (r == 0) return EngineNumberSorter(a, b);
298 return _engine_sort_direction ? -r : r;
302 * Determines order of engines by speed
303 * @param *a first engine to compare
304 * @param *b second engine to compare
305 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
307 static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b)
309 int va = Engine::Get(*a)->GetDisplayMaxSpeed();
310 int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
311 int r = va - vb;
313 /* Use EngineID to sort instead since we want consistent sorting */
314 if (r == 0) return EngineNumberSorter(a, b);
315 return _engine_sort_direction ? -r : r;
319 * Determines order of engines by power
320 * @param *a first engine to compare
321 * @param *b second engine to compare
322 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
324 static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b)
326 int va = Engine::Get(*a)->GetPower();
327 int vb = Engine::Get(*b)->GetPower();
328 int r = va - vb;
330 /* Use EngineID to sort instead since we want consistent sorting */
331 if (r == 0) return EngineNumberSorter(a, b);
332 return _engine_sort_direction ? -r : r;
336 * Determines order of engines by tractive effort
337 * @param *a first engine to compare
338 * @param *b second engine to compare
339 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
341 static int CDECL EngineTractiveEffortSorter(const EngineID *a, const EngineID *b)
343 int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort();
344 int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort();
345 int r = va - vb;
347 /* Use EngineID to sort instead since we want consistent sorting */
348 if (r == 0) return EngineNumberSorter(a, b);
349 return _engine_sort_direction ? -r : r;
353 * Determines order of engines by running costs
354 * @param *a first engine to compare
355 * @param *b second engine to compare
356 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
358 static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b)
360 Money va = Engine::Get(*a)->GetRunningCost();
361 Money vb = Engine::Get(*b)->GetRunningCost();
362 int r = ClampToI32(va - vb);
364 /* Use EngineID to sort instead since we want consistent sorting */
365 if (r == 0) return EngineNumberSorter(a, b);
366 return _engine_sort_direction ? -r : r;
370 * Determines order of engines by running costs
371 * @param *a first engine to compare
372 * @param *b second engine to compare
373 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
375 static int CDECL EnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b)
377 const Engine *e_a = Engine::Get(*a);
378 const Engine *e_b = Engine::Get(*b);
380 /* Here we are using a few tricks to get the right sort.
381 * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
382 * we will actually calculate cunning cost/power (to make it more than 1).
383 * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
384 * Another thing is that both power and running costs should be doubled for multiheaded engines.
385 * Since it would be multiplying with 2 in both numerator and denominator, it will even themselves out and we skip checking for multiheaded. */
386 Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
387 Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
388 int r = ClampToI32(vb - va);
390 /* Use EngineID to sort instead since we want consistent sorting */
391 if (r == 0) return EngineNumberSorter(a, b);
392 return _engine_sort_direction ? -r : r;
395 /* Train sorting functions */
398 * Determines order of train engines by capacity
399 * @param *a first engine to compare
400 * @param *b second engine to compare
401 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
403 static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b)
405 const RailVehicleInfo *rvi_a = RailVehInfo(*a);
406 const RailVehicleInfo *rvi_b = RailVehInfo(*b);
408 int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
409 int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
410 int r = va - vb;
412 /* Use EngineID to sort instead since we want consistent sorting */
413 if (r == 0) return EngineNumberSorter(a, b);
414 return _engine_sort_direction ? -r : r;
418 * Determines order of train engines by engine / wagon
419 * @param *a first engine to compare
420 * @param *b second engine to compare
421 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
423 static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b)
425 int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
426 int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
427 int r = val_a - val_b;
429 /* Use EngineID to sort instead since we want consistent sorting */
430 if (r == 0) return EngineNumberSorter(a, b);
431 return _engine_sort_direction ? -r : r;
434 /* Road vehicle sorting functions */
437 * Determines order of road vehicles by capacity
438 * @param *a first engine to compare
439 * @param *b second engine to compare
440 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
442 static int CDECL RoadVehEngineCapacitySorter(const EngineID *a, const EngineID *b)
444 int va = GetTotalCapacityOfArticulatedParts(*a);
445 int vb = GetTotalCapacityOfArticulatedParts(*b);
446 int r = va - vb;
448 /* Use EngineID to sort instead since we want consistent sorting */
449 if (r == 0) return EngineNumberSorter(a, b);
450 return _engine_sort_direction ? -r : r;
453 /* Ship vehicle sorting functions */
456 * Determines order of ships by capacity
457 * @param *a first engine to compare
458 * @param *b second engine to compare
459 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
461 static int CDECL ShipEngineCapacitySorter(const EngineID *a, const EngineID *b)
463 const Engine *e_a = Engine::Get(*a);
464 const Engine *e_b = Engine::Get(*b);
466 int va = e_a->GetDisplayDefaultCapacity();
467 int vb = e_b->GetDisplayDefaultCapacity();
468 int r = va - vb;
470 /* Use EngineID to sort instead since we want consistent sorting */
471 if (r == 0) return EngineNumberSorter(a, b);
472 return _engine_sort_direction ? -r : r;
475 /* Aircraft sorting functions */
478 * Determines order of aircraft by cargo
479 * @param *a first engine to compare
480 * @param *b second engine to compare
481 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
483 static int CDECL AircraftEngineCargoSorter(const EngineID *a, const EngineID *b)
485 const Engine *e_a = Engine::Get(*a);
486 const Engine *e_b = Engine::Get(*b);
488 uint16 mail_a, mail_b;
489 int va = e_a->GetDisplayDefaultCapacity(&mail_a);
490 int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
491 int r = va - vb;
493 if (r == 0) {
494 /* The planes have the same passenger capacity. Check mail capacity instead */
495 r = mail_a - mail_b;
497 if (r == 0) {
498 /* Use EngineID to sort instead since we want consistent sorting */
499 return EngineNumberSorter(a, b);
502 return _engine_sort_direction ? -r : r;
506 * Determines order of aircraft by range.
507 * @param *a first engine to compare.
508 * @param *b second engine to compare.
509 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal.
511 static int CDECL AircraftRangeSorter(const EngineID *a, const EngineID *b)
513 uint16 r_a = Engine::Get(*a)->GetRange();
514 uint16 r_b = Engine::Get(*b)->GetRange();
516 int r = r_a - r_b;
518 /* Use EngineID to sort instead since we want consistent sorting */
519 if (r == 0) return EngineNumberSorter(a, b);
520 return _engine_sort_direction ? -r : r;
523 /* Locomotive sorting functions. */
525 * Determines order of locomotives by engineID
526 * @param *a first engine to compare
527 * @param *b second engine to compare
528 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
530 static int CDECL EngineNumberSorterLoco(const EngineID *a, const EngineID *b)
532 int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
534 return _internal_sort_order_loco ? -r : r;
538 * Determines order of locomotives by introduction date
539 * @param *a first engine to compare
540 * @param *b second engine to compare
541 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
543 static int CDECL EngineIntroDateSorterLoco(const EngineID *a, const EngineID *b)
545 const int va = Engine::Get(*a)->intro_date;
546 const int vb = Engine::Get(*b)->intro_date;
547 const int r = va - vb;
549 /* Use EngineID to sort instead since we want consistent sorting */
550 if (r == 0) return EngineNumberSorterLoco(a, b);
551 return _internal_sort_order_loco ? -r : r;
555 * Determines order of locomotives by name
556 * @param *a first engine to compare
557 * @param *b second engine to compare
558 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
560 static int CDECL EngineNameSorterLoco(const EngineID *a, const EngineID *b)
562 static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
563 static char last_name[2][64] = { "\0", "\0" };
565 const EngineID va = *a;
566 const EngineID vb = *b;
568 if (va != last_engine[0]) {
569 last_engine[0] = va;
570 SetDParam(0, va);
571 GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
574 if (vb != last_engine[1]) {
575 last_engine[1] = vb;
576 SetDParam(0, vb);
577 GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
580 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
582 /* Use EngineID to sort instead since we want consistent sorting */
583 if (r == 0) return EngineNumberSorterLoco(a, b);
584 return _internal_sort_order_loco ? -r : r;
588 * Determines order of locomotives by reliability
589 * @param *a first engine to compare
590 * @param *b second engine to compare
591 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
593 static int CDECL EngineReliabilitySorterLoco(const EngineID *a, const EngineID *b)
595 const int va = Engine::Get(*a)->reliability;
596 const int vb = Engine::Get(*b)->reliability;
597 const int r = va - vb;
599 /* Use EngineID to sort instead since we want consistent sorting */
600 if (r == 0) return EngineNumberSorterLoco(a, b);
601 return _internal_sort_order_loco ? -r : r;
605 * Determines order of locomotives by purchase cost
606 * @param *a first engine to compare
607 * @param *b second engine to compare
608 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
610 static int CDECL EngineCostSorterLoco(const EngineID *a, const EngineID *b)
612 Money va = Engine::Get(*a)->GetCost();
613 Money vb = Engine::Get(*b)->GetCost();
614 int r = ClampToI32(va - vb);
616 /* Use EngineID to sort instead since we want consistent sorting */
617 if (r == 0) return EngineNumberSorterLoco(a, b);
618 return _internal_sort_order_loco ? -r : r;
622 * Determines order of locomotives by speed
623 * @param *a first engine to compare
624 * @param *b second engine to compare
625 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
627 static int CDECL EngineSpeedSorterLoco(const EngineID *a, const EngineID *b)
629 int va = Engine::Get(*a)->GetDisplayMaxSpeed();
630 int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
631 int r = va - vb;
633 /* Use EngineID to sort instead since we want consistent sorting */
634 if (r == 0) return EngineNumberSorterLoco(a, b);
635 return _internal_sort_order_loco ? -r : r;
639 * Determines order of locomotives by power
640 * @param *a first engine to compare
641 * @param *b second engine to compare
642 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
644 static int CDECL EnginePowerSorterLoco(const EngineID *a, const EngineID *b)
646 int va = Engine::Get(*a)->GetPower();
647 int vb = Engine::Get(*b)->GetPower();
648 int r = va - vb;
650 /* Use EngineID to sort instead since we want consistent sorting */
651 if (r == 0) return EngineNumberSorterLoco(a, b);
652 return _internal_sort_order_loco ? -r : r;
656 * Determines order of locomotives by tractive effort
657 * @param *a first engine to compare
658 * @param *b second engine to compare
659 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
661 static int CDECL EngineTractiveEffortSorterLoco(const EngineID *a, const EngineID *b)
663 int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort();
664 int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort();
665 int r = va - vb;
667 /* Use EngineID to sort instead since we want consistent sorting */
668 if (r == 0) return EngineNumberSorterLoco(a, b);
669 return _internal_sort_order_loco ? -r : r;
673 * Determines order of locomotives by running costs
674 * @param *a first engine to compare
675 * @param *b second engine to compare
676 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
678 static int CDECL EngineRunningCostSorterLoco(const EngineID *a, const EngineID *b)
680 Money va = Engine::Get(*a)->GetRunningCost();
681 Money vb = Engine::Get(*b)->GetRunningCost();
682 int r = ClampToI32(va - vb);
684 /* Use EngineID to sort instead since we want consistent sorting */
685 if (r == 0) return EngineNumberSorterLoco(a, b);
686 return _internal_sort_order_loco ? -r : r;
690 * Determines order of locomotives by running costs
691 * @param *a first engine to compare
692 * @param *b second engine to compare
693 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
695 static int CDECL EnginePowerVsRunningCostSorterLoco(const EngineID *a, const EngineID *b)
697 const Engine *e_a = Engine::Get(*a);
698 const Engine *e_b = Engine::Get(*b);
700 /* Here we are using a few tricks to get the right sort.
701 * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
702 * we will actually calculate cunning cost/power (to make it more than 1).
703 * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
704 * Another thing is that both power and running costs should be doubled for multiheaded engines.
705 * Since it would be multiplying with 2 in both numerator and denominator, it will even themselves out and we skip checking for multiheaded. */
706 Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
707 Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
708 int r = ClampToI32(vb - va);
710 /* Use EngineID to sort instead since we want consistent sorting */
711 if (r == 0) return EngineNumberSorterLoco(a, b);
712 return _internal_sort_order_loco ? -r : r;
716 * Determines order of train locomotives by capacity
717 * @param *a first engine to compare
718 * @param *b second engine to compare
719 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
721 static int CDECL TrainEngineCapacitySorterLoco(const EngineID *a, const EngineID *b)
723 const RailVehicleInfo *rvi_a = RailVehInfo(*a);
724 const RailVehicleInfo *rvi_b = RailVehInfo(*b);
726 int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
727 int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
728 int r = va - vb;
730 /* Use EngineID to sort instead since we want consistent sorting */
731 if (r == 0) return EngineNumberSorterLoco(a, b);
732 return _internal_sort_order_loco ? -r : r;
735 /* Wagon sorting functions. */
738 * Determines order of wagons by engineID
739 * @param *a first engine to compare
740 * @param *b second engine to compare
741 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
743 static int CDECL EngineNumberSorterWagon(const EngineID *a, const EngineID *b)
745 int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
747 return _internal_sort_order_wagon ? -r : r;
751 * Determines order of wagons by introduction date
752 * @param *a first engine to compare
753 * @param *b second engine to compare
754 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
756 static int CDECL EngineIntroDateSorterWagon(const EngineID *a, const EngineID *b)
758 const int va = Engine::Get(*a)->intro_date;
759 const int vb = Engine::Get(*b)->intro_date;
760 const int r = va - vb;
762 /* Use EngineID to sort instead since we want consistent sorting */
763 if (r == 0) return EngineNumberSorterWagon(a, b);
764 return _internal_sort_order_wagon ? -r : r;
768 * Determines order of wagons by name
769 * @param *a first engine to compare
770 * @param *b second engine to compare
771 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
773 static int CDECL EngineNameSorterWagon(const EngineID *a, const EngineID *b)
775 static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
776 static char last_name[2][64] = { "\0", "\0" };
778 const EngineID va = *a;
779 const EngineID vb = *b;
781 if (va != last_engine[0]) {
782 last_engine[0] = va;
783 SetDParam(0, va);
784 GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
787 if (vb != last_engine[1]) {
788 last_engine[1] = vb;
789 SetDParam(0, vb);
790 GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
793 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
795 /* Use EngineID to sort instead since we want consistent sorting */
796 if (r == 0) return EngineNumberSorterWagon(a, b);
797 return _internal_sort_order_wagon ? -r : r;
801 * Determines order of wagons by purchase cost
802 * @param *a first engine to compare
803 * @param *b second engine to compare
804 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
806 static int CDECL EngineCostSorterWagon(const EngineID *a, const EngineID *b)
808 Money va = Engine::Get(*a)->GetCost();
809 Money vb = Engine::Get(*b)->GetCost();
810 int r = ClampToI32(va - vb);
812 /* Use EngineID to sort instead since we want consistent sorting */
813 if (r == 0) return EngineNumberSorterWagon(a, b);
814 return _internal_sort_order_wagon ? -r : r;
818 * Determines order of wagons by speed
819 * @param *a first engine to compare
820 * @param *b second engine to compare
821 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
823 static int CDECL EngineSpeedSorterWagon(const EngineID *a, const EngineID *b)
825 int va = Engine::Get(*a)->GetDisplayMaxSpeed();
826 int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
827 int r = va - vb;
829 /* Use EngineID to sort instead since we want consistent sorting */
830 if (r == 0) return EngineNumberSorterWagon(a, b);
831 return _internal_sort_order_wagon ? -r : r;
835 * Determines order of wagons by running costs
836 * @param *a first engine to compare
837 * @param *b second engine to compare
838 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
840 static int CDECL EngineRunningCostSorterWagon(const EngineID *a, const EngineID *b)
842 Money va = Engine::Get(*a)->GetRunningCost();
843 Money vb = Engine::Get(*b)->GetRunningCost();
844 int r = ClampToI32(va - vb);
846 /* Use EngineID to sort instead since we want consistent sorting */
847 if (r == 0) return EngineNumberSorterWagon(a, b);
848 return _internal_sort_order_wagon ? -r : r;
852 * Determines order of train wagons by capacity
853 * @param *a first engine to compare
854 * @param *b second engine to compare
855 * @return for descending order: returns < 0 if a < b and > 0 for a > b. Vice versa for ascending order and 0 for equal
857 static int CDECL TrainEngineCapacitySorterWagon(const EngineID *a, const EngineID *b)
859 const RailVehicleInfo *rvi_a = RailVehInfo(*a);
860 const RailVehicleInfo *rvi_b = RailVehInfo(*b);
862 int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
863 int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
864 int r = va - vb;
866 /* Use EngineID to sort instead since we want consistent sorting */
867 if (r == 0) return EngineNumberSorterWagon(a, b);
868 return _internal_sort_order_wagon ? -r : r;
871 EngList_SortTypeFunction * const _engine_sort_functions[][11] = { {
872 /* Trains */
873 &EngineNumberSorter,
874 &EngineCostSorter,
875 &EngineSpeedSorter,
876 &EnginePowerSorter,
877 &EngineTractiveEffortSorter,
878 &EngineIntroDateSorter,
879 &EngineNameSorter,
880 &EngineRunningCostSorter,
881 &EnginePowerVsRunningCostSorter,
882 &EngineReliabilitySorter,
883 &TrainEngineCapacitySorter,
884 }, {
885 /* Road vehicles */
886 &EngineNumberSorter,
887 &EngineCostSorter,
888 &EngineSpeedSorter,
889 &EnginePowerSorter,
890 &EngineTractiveEffortSorter,
891 &EngineIntroDateSorter,
892 &EngineNameSorter,
893 &EngineRunningCostSorter,
894 &EnginePowerVsRunningCostSorter,
895 &EngineReliabilitySorter,
896 &RoadVehEngineCapacitySorter,
897 }, {
898 /* Ships */
899 &EngineNumberSorter,
900 &EngineCostSorter,
901 &EngineSpeedSorter,
902 &EngineIntroDateSorter,
903 &EngineNameSorter,
904 &EngineRunningCostSorter,
905 &EngineReliabilitySorter,
906 &ShipEngineCapacitySorter,
907 }, {
908 /* Aircraft */
909 &EngineNumberSorter,
910 &EngineCostSorter,
911 &EngineSpeedSorter,
912 &EngineIntroDateSorter,
913 &EngineNameSorter,
914 &EngineRunningCostSorter,
915 &EngineReliabilitySorter,
916 &AircraftEngineCargoSorter,
917 &AircraftRangeSorter,
918 } };
920 /** Dropdown menu strings for the vehicle sort criteria. */
921 const StringID _engine_sort_listing[][12] = { {
922 /* Trains */
923 STR_SORT_BY_ENGINE_ID,
924 STR_SORT_BY_COST,
925 STR_SORT_BY_MAX_SPEED,
926 STR_SORT_BY_POWER,
927 STR_SORT_BY_TRACTIVE_EFFORT,
928 STR_SORT_BY_INTRO_DATE,
929 STR_SORT_BY_NAME,
930 STR_SORT_BY_RUNNING_COST,
931 STR_SORT_BY_POWER_VS_RUNNING_COST,
932 STR_SORT_BY_RELIABILITY,
933 STR_SORT_BY_CARGO_CAPACITY,
934 INVALID_STRING_ID
935 }, {
936 /* Road vehicles */
937 STR_SORT_BY_ENGINE_ID,
938 STR_SORT_BY_COST,
939 STR_SORT_BY_MAX_SPEED,
940 STR_SORT_BY_POWER,
941 STR_SORT_BY_TRACTIVE_EFFORT,
942 STR_SORT_BY_INTRO_DATE,
943 STR_SORT_BY_NAME,
944 STR_SORT_BY_RUNNING_COST,
945 STR_SORT_BY_POWER_VS_RUNNING_COST,
946 STR_SORT_BY_RELIABILITY,
947 STR_SORT_BY_CARGO_CAPACITY,
948 INVALID_STRING_ID
949 }, {
950 /* Ships */
951 STR_SORT_BY_ENGINE_ID,
952 STR_SORT_BY_COST,
953 STR_SORT_BY_MAX_SPEED,
954 STR_SORT_BY_INTRO_DATE,
955 STR_SORT_BY_NAME,
956 STR_SORT_BY_RUNNING_COST,
957 STR_SORT_BY_RELIABILITY,
958 STR_SORT_BY_CARGO_CAPACITY,
959 INVALID_STRING_ID
960 }, {
961 /* Aircraft */
962 STR_SORT_BY_ENGINE_ID,
963 STR_SORT_BY_COST,
964 STR_SORT_BY_MAX_SPEED,
965 STR_SORT_BY_INTRO_DATE,
966 STR_SORT_BY_NAME,
967 STR_SORT_BY_RUNNING_COST,
968 STR_SORT_BY_RELIABILITY,
969 STR_SORT_BY_CARGO_CAPACITY,
970 STR_SORT_BY_RANGE,
971 INVALID_STRING_ID
972 } };
974 static EngList_SortTypeFunction * const _sorter_loco[11] = {
975 /* Locomotives */
976 &EngineNumberSorterLoco,
977 &EngineCostSorterLoco,
978 &EngineSpeedSorterLoco,
979 &EnginePowerSorterLoco,
980 &EngineTractiveEffortSorterLoco,
981 &EngineIntroDateSorterLoco,
982 &EngineNameSorterLoco,
983 &EngineRunningCostSorterLoco,
984 &EnginePowerVsRunningCostSorterLoco,
985 &EngineReliabilitySorter,
986 &TrainEngineCapacitySorter
989 static EngList_SortTypeFunction * const _sorter_wagon[7] = {
990 /* Wagons */
991 &EngineNumberSorterWagon,
992 &EngineCostSorterWagon,
993 &EngineSpeedSorterWagon,
994 &EngineIntroDateSorterWagon,
995 &EngineNameSorterWagon,
996 &EngineRunningCostSorterWagon,
997 &TrainEngineCapacitySorterWagon
1000 static const StringID _sort_listing_loco[12] = {
1001 /* Locomotives */
1002 STR_SORT_BY_ENGINE_ID,
1003 STR_SORT_BY_COST,
1004 STR_SORT_BY_MAX_SPEED,
1005 STR_SORT_BY_POWER,
1006 STR_SORT_BY_TRACTIVE_EFFORT,
1007 STR_SORT_BY_INTRO_DATE,
1008 STR_SORT_BY_NAME,
1009 STR_SORT_BY_RUNNING_COST,
1010 STR_SORT_BY_POWER_VS_RUNNING_COST,
1011 STR_SORT_BY_RELIABILITY,
1012 STR_SORT_BY_CARGO_CAPACITY,
1013 INVALID_STRING_ID
1016 static const StringID _sort_listing_wagon[8] = {
1017 /* Wagons */
1018 STR_SORT_BY_ENGINE_ID,
1019 STR_SORT_BY_COST,
1020 STR_SORT_BY_MAX_SPEED,
1021 STR_SORT_BY_INTRO_DATE,
1022 STR_SORT_BY_NAME,
1023 STR_SORT_BY_RUNNING_COST,
1024 STR_SORT_BY_CARGO_CAPACITY,
1025 INVALID_STRING_ID
1028 /** Cargo filter functions */
1029 static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
1031 if (cid == CF_ANY) return true;
1032 uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
1033 return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
1036 static GUIEngineList::FilterFunction * const _filter_funcs[] = {
1037 &CargoFilter,
1040 static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine)
1042 CargoArray cap;
1043 uint32 refits;
1044 GetArticulatedVehicleCargoesAndRefits(engine, &cap, &refits);
1046 for (CargoID c = 0; c < NUM_CARGO; c++) {
1047 if (cap[c] == 0) continue;
1049 SetDParam(0, c);
1050 SetDParam(1, cap[c]);
1051 SetDParam(2, HasBit(refits, c) ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
1052 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
1053 y += FONT_HEIGHT_NORMAL;
1056 return y;
1059 /* Draw rail wagon specific details */
1060 static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
1062 const Engine *e = Engine::Get(engine_number);
1064 /* Purchase cost */
1065 SetDParam(0, e->GetCost());
1066 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
1067 y += FONT_HEIGHT_NORMAL;
1069 /* Wagon weight - (including cargo) */
1070 uint weight = e->GetDisplayWeight();
1071 SetDParam(0, weight);
1072 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
1073 SetDParam(1, cargo_weight + weight);
1074 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
1075 y += FONT_HEIGHT_NORMAL;
1077 /* Wagon speed limit, displayed if above zero */
1078 if (_settings_game.vehicle.wagon_speed_limits) {
1079 uint max_speed = e->GetDisplayMaxSpeed();
1080 if (max_speed > 0) {
1081 SetDParam(0, max_speed);
1082 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
1083 y += FONT_HEIGHT_NORMAL;
1087 /* Running cost */
1088 if (rvi->running_cost_class != INVALID_PRICE) {
1089 SetDParam(0, e->GetDisplayRunningCost());
1090 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
1091 y += FONT_HEIGHT_NORMAL;
1094 return y;
1097 /* Draw locomotive specific details */
1098 static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
1100 const Engine *e = Engine::Get(engine_number);
1102 /* Purchase Cost - Engine weight */
1103 SetDParam(0, e->GetCost());
1104 SetDParam(1, e->GetDisplayWeight());
1105 DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
1106 y += FONT_HEIGHT_NORMAL;
1108 /* Max speed - Engine power */
1109 SetDParam(0, e->GetDisplayMaxSpeed());
1110 SetDParam(1, e->GetPower());
1111 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
1112 y += FONT_HEIGHT_NORMAL;
1114 /* Max tractive effort - not applicable if old acceleration or maglev */
1115 if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
1116 SetDParam(0, e->GetDisplayMaxTractiveEffort());
1117 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
1118 y += FONT_HEIGHT_NORMAL;
1121 /* Running cost */
1122 if (rvi->running_cost_class != INVALID_PRICE) {
1123 SetDParam(0, e->GetDisplayRunningCost());
1124 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
1125 y += FONT_HEIGHT_NORMAL;
1128 /* Powered wagons power - Powered wagons extra weight */
1129 if (rvi->pow_wag_power != 0) {
1130 SetDParam(0, rvi->pow_wag_power);
1131 SetDParam(1, rvi->pow_wag_weight);
1132 DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
1133 y += FONT_HEIGHT_NORMAL;
1136 return y;
1139 /* Draw road vehicle specific details */
1140 static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number)
1142 const Engine *e = Engine::Get(engine_number);
1144 if (_settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL) {
1145 /* Purchase Cost */
1146 SetDParam(0, e->GetCost());
1147 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
1148 y += FONT_HEIGHT_NORMAL;
1150 /* Road vehicle weight - (including cargo) */
1151 int16 weight = e->GetDisplayWeight();
1152 SetDParam(0, weight);
1153 uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
1154 SetDParam(1, cargo_weight + weight);
1155 DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
1156 y += FONT_HEIGHT_NORMAL;
1158 /* Max speed - Engine power */
1159 SetDParam(0, e->GetDisplayMaxSpeed());
1160 SetDParam(1, e->GetPower());
1161 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
1162 y += FONT_HEIGHT_NORMAL;
1164 /* Max tractive effort */
1165 SetDParam(0, e->GetDisplayMaxTractiveEffort());
1166 DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
1167 y += FONT_HEIGHT_NORMAL;
1168 } else {
1169 /* Purchase cost - Max speed */
1170 SetDParam(0, e->GetCost());
1171 SetDParam(1, e->GetDisplayMaxSpeed());
1172 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
1173 y += FONT_HEIGHT_NORMAL;
1176 /* Running cost */
1177 SetDParam(0, e->GetDisplayRunningCost());
1178 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
1179 y += FONT_HEIGHT_NORMAL;
1181 return y;
1184 /* Draw ship specific details */
1185 static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
1187 const Engine *e = Engine::Get(engine_number);
1189 /* Purchase cost - Max speed */
1190 uint raw_speed = e->GetDisplayMaxSpeed();
1191 uint ocean_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, true);
1192 uint canal_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, false);
1194 SetDParam(0, e->GetCost());
1195 if (ocean_speed == canal_speed) {
1196 SetDParam(1, ocean_speed);
1197 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
1198 y += FONT_HEIGHT_NORMAL;
1199 } else {
1200 DrawString(left, right, y, STR_PURCHASE_INFO_COST);
1201 y += FONT_HEIGHT_NORMAL;
1203 SetDParam(0, ocean_speed);
1204 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_OCEAN);
1205 y += FONT_HEIGHT_NORMAL;
1207 SetDParam(0, canal_speed);
1208 DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_CANAL);
1209 y += FONT_HEIGHT_NORMAL;
1212 /* Cargo type + capacity */
1213 SetDParam(0, e->GetDefaultCargoType());
1214 SetDParam(1, e->GetDisplayDefaultCapacity());
1215 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
1216 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
1217 y += FONT_HEIGHT_NORMAL;
1219 /* Running cost */
1220 SetDParam(0, e->GetDisplayRunningCost());
1221 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
1222 y += FONT_HEIGHT_NORMAL;
1224 return y;
1228 * Draw aircraft specific details in the buy window.
1229 * @param left Left edge of the window to draw in.
1230 * @param right Right edge of the window to draw in.
1231 * @param y Top of the area to draw in.
1232 * @param engine_number Engine to display.
1233 * @param refittable If set, the aircraft can be refitted.
1234 * @return Bottom of the used area.
1236 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
1238 const Engine *e = Engine::Get(engine_number);
1239 CargoID cargo = e->GetDefaultCargoType();
1241 /* Purchase cost - Max speed */
1242 SetDParam(0, e->GetCost());
1243 SetDParam(1, e->GetDisplayMaxSpeed());
1244 DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
1245 y += FONT_HEIGHT_NORMAL;
1247 /* Cargo capacity */
1248 uint16 mail_capacity;
1249 uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
1250 if (mail_capacity > 0) {
1251 SetDParam(0, cargo);
1252 SetDParam(1, capacity);
1253 SetDParam(2, CT_MAIL);
1254 SetDParam(3, mail_capacity);
1255 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
1256 } else {
1257 /* Note, if the default capacity is selected by the refit capacity
1258 * callback, then the capacity shown is likely to be incorrect. */
1259 SetDParam(0, cargo);
1260 SetDParam(1, capacity);
1261 SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
1262 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
1264 y += FONT_HEIGHT_NORMAL;
1266 /* Running cost */
1267 SetDParam(0, e->GetDisplayRunningCost());
1268 DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
1269 y += FONT_HEIGHT_NORMAL;
1271 /* Aircraft type */
1272 SetDParam(0, e->GetAircraftTypeText());
1273 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_TYPE);
1274 y += FONT_HEIGHT_NORMAL;
1276 /* Aircraft range, if available. */
1277 uint16 range = e->GetRange();
1278 if (range != 0) {
1279 SetDParam(0, range);
1280 DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
1281 y += FONT_HEIGHT_NORMAL;
1284 return y;
1288 * Display additional text from NewGRF in the purchase information window
1289 * @param left Left border of text bounding box
1290 * @param right Right border of text bounding box
1291 * @param y Top border of text bounding box
1292 * @param engine Engine to query the additional purchase information for
1293 * @return Bottom border of text bounding box
1295 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
1297 uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, nullptr);
1298 if (callback == CALLBACK_FAILED || callback == 0x400) return y;
1299 const GRFFile *grffile = Engine::Get(engine)->GetGRF();
1300 if (callback > 0x400) {
1301 ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback);
1302 return y;
1305 StartTextRefStackUsage(grffile, 6);
1306 uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(grffile->grfid, 0xD000 + callback), TC_BLACK);
1307 StopTextRefStackUsage();
1308 return result;
1312 * Draw the purchase info details of a vehicle at a given location.
1313 * @param left,right,y location where to draw the info
1314 * @param engine_number the engine of which to draw the info of
1315 * @return y after drawing all the text
1317 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number)
1319 const Engine *e = Engine::Get(engine_number);
1320 YearMonthDay ymd;
1321 ConvertDateToYMD(e->intro_date, &ymd);
1322 bool refittable = IsArticulatedVehicleRefittable(engine_number);
1323 bool articulated_cargo = false;
1325 switch (e->type) {
1326 default: NOT_REACHED();
1327 case VEH_TRAIN:
1328 if (e->u.rail.railveh_type == RAILVEH_WAGON) {
1329 y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail);
1330 } else {
1331 y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail);
1333 articulated_cargo = true;
1334 break;
1336 case VEH_ROAD:
1337 y = DrawRoadVehPurchaseInfo(left, right, y, engine_number);
1338 articulated_cargo = true;
1339 break;
1341 case VEH_SHIP:
1342 y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable);
1343 break;
1345 case VEH_AIRCRAFT:
1346 y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable);
1347 break;
1350 if (articulated_cargo) {
1351 /* Cargo type + capacity, or N/A */
1352 int new_y = DrawCargoCapacityInfo(left, right, y, engine_number);
1354 if (new_y == y) {
1355 SetDParam(0, CT_INVALID);
1356 SetDParam(2, STR_EMPTY);
1357 DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
1358 y += FONT_HEIGHT_NORMAL;
1359 } else {
1360 y = new_y;
1364 /* Draw details that apply to all types except rail wagons. */
1365 if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
1366 /* Design date - Life length */
1367 SetDParam(0, ymd.year);
1368 SetDParam(1, e->GetLifeLengthInDays() / DAYS_IN_LEAP_YEAR);
1369 DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
1370 y += FONT_HEIGHT_NORMAL;
1372 /* Reliability */
1373 SetDParam(0, ToPercent16(e->reliability));
1374 DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
1375 y += FONT_HEIGHT_NORMAL;
1378 if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
1380 /* Additional text from NewGRF */
1381 y = ShowAdditionalText(left, right, y, engine_number);
1383 return y;
1387 * Engine drawing loop
1388 * @param type Type of vehicle (VEH_*)
1389 * @param l The left most location of the list
1390 * @param r The right most location of the list
1391 * @param y The top most location of the list
1392 * @param eng_list What engines to draw
1393 * @param min where to start in the list
1394 * @param max where in the list to end
1395 * @param selected_id what engine to highlight as selected, if any
1396 * @param show_count Whether to show the amount of engines or not
1397 * @param selected_group the group to list the engines of
1399 void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
1401 static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
1403 /* Obligatory sanity checks! */
1404 assert(max <= eng_list->Length());
1406 bool rtl = _current_text_dir == TD_RTL;
1407 int step_size = GetEngineListHeight(type);
1408 int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
1409 int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
1410 int sprite_width = sprite_left + sprite_right;
1412 int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
1413 int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
1415 Dimension replace_icon = {0, 0};
1416 int count_width = 0;
1417 if (show_count) {
1418 replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
1419 SetDParamMaxDigits(0, 3, FS_SMALL);
1420 count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
1423 int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
1424 int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
1425 int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
1426 int count_left = l;
1427 int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
1429 int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
1430 int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
1431 int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
1433 for (; min < max; min++, y += step_size) {
1434 const EngineID engine = (*eng_list)[min];
1435 /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
1436 const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
1438 const Engine *e = Engine::Get(engine);
1439 bool hidden = HasBit(e->company_hidden, _local_company);
1440 StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
1441 TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
1443 SetDParam(0, engine);
1444 DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
1445 DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
1446 if (show_count) {
1447 SetDParam(0, num_engines);
1448 DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
1449 if (EngineHasReplacementForCompany(Company::Get(_local_company), engine, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, replace_icon_left, y + replace_icon_y_offset);
1455 * Display the dropdown for the vehicle sort criteria.
1456 * @param w Parent window (holds the dropdown button).
1457 * @param vehicle_type %Vehicle type being sorted.
1458 * @param selected Currently selected sort criterium.
1459 * @param button Widget button.
1461 void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button)
1463 uint32 hidden_mask = 0;
1464 /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
1465 if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
1466 SetBit(hidden_mask, 3); // power
1467 SetBit(hidden_mask, 4); // tractive effort
1468 SetBit(hidden_mask, 8); // power by running costs
1470 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
1471 if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
1472 SetBit(hidden_mask, 4); // tractive effort
1474 ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
1477 /** GUI for building vehicles. */
1478 struct BuildVehicleWindow : Window {
1479 VehicleType vehicle_type; ///< Type of vehicles shown in the window.
1480 union {
1481 RailTypeByte railtype; ///< Rail type to show, or #RAILTYPE_END.
1482 RoadTypes roadtypes; ///< Road type to show, or #ROADTYPES_ALL.
1483 } filter; ///< Filter to apply.
1484 bool descending_sort_order; ///< Sort direction, @see _engine_sort_direction
1485 byte sort_criteria; ///< Current sort criterium.
1486 bool show_hidden_engines; ///< State of the 'show hidden engines' button.
1487 bool listview_mode; ///< If set, only display the available vehicles and do not show a 'build' button.
1488 EngineID sel_engine; ///< Currently selected engine, or #INVALID_ENGINE
1489 EngineID rename_engine; ///< Engine being renamed.
1490 GUIEngineList eng_list;
1491 CargoID cargo_filter[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
1492 StringID cargo_filter_texts[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
1493 byte cargo_filter_criteria; ///< Selected cargo filter
1494 int details_height; ///< Minimal needed height of the details panels (found so far).
1495 Scrollbar *vscroll;
1497 BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
1499 this->vehicle_type = type;
1500 this->window_number = tile == INVALID_TILE ? (int)type : tile;
1502 this->sel_engine = INVALID_ENGINE;
1504 this->sort_criteria = _engine_sort_last_criteria[type];
1505 this->descending_sort_order = _engine_sort_last_order[type];
1506 this->show_hidden_engines = _engine_sort_show_hidden_engines[type];
1508 switch (type) {
1509 default: NOT_REACHED();
1510 case VEH_TRAIN:
1511 this->filter.railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
1512 break;
1513 case VEH_ROAD:
1514 this->filter.roadtypes = (tile == INVALID_TILE) ? ROADTYPES_ALL : GetRoadTypes(tile);
1515 case VEH_SHIP:
1516 case VEH_AIRCRAFT:
1517 break;
1520 this->listview_mode = (this->window_number <= VEH_END);
1522 this->CreateNestedTree();
1524 this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
1526 /* If we are just viewing the list of vehicles, we do not need the Build button.
1527 * So we just hide it, and enlarge the Rename button by the now vacant place. */
1528 if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
1530 NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
1531 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
1533 widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE);
1534 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
1536 widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
1537 widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + type;
1538 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + type;
1540 widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
1541 widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
1542 widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
1544 widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_ENGINES);
1545 widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
1546 widget->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
1547 widget->SetLowered(this->show_hidden_engines);
1549 this->details_height = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
1551 this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
1553 this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
1555 this->eng_list.ForceRebuild();
1556 this->GenerateBuildList(); // generate the list, since we need it in the next line
1557 /* Select the first engine in the list as default when opening the window */
1558 if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
1561 /** Populate the filter list and set the cargo filter criteria. */
1562 void SetCargoFilterArray()
1564 uint filter_items = 0;
1566 /* Add item for disabling filtering. */
1567 this->cargo_filter[filter_items] = CF_ANY;
1568 this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1569 filter_items++;
1571 /* Add item for vehicles not carrying anything, e.g. train engines.
1572 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1573 if (this->vehicle_type == VEH_TRAIN) {
1574 this->cargo_filter[filter_items] = CF_NONE;
1575 this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
1576 filter_items++;
1579 /* Collect available cargo types for filtering. */
1580 const CargoSpec *cs;
1581 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) {
1582 this->cargo_filter[filter_items] = cs->Index();
1583 this->cargo_filter_texts[filter_items] = cs->name;
1584 filter_items++;
1587 /* Terminate the filter list. */
1588 this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1590 /* If not found, the cargo criteria will be set to all cargoes. */
1591 this->cargo_filter_criteria = 0;
1593 /* Find the last cargo filter criteria. */
1594 for (uint i = 0; i < filter_items; i++) {
1595 if (this->cargo_filter[i] == _engine_sort_last_cargo_criteria[this->vehicle_type]) {
1596 this->cargo_filter_criteria = i;
1597 break;
1601 this->eng_list.SetFilterFuncs(_filter_funcs);
1602 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1605 void OnInit()
1607 this->SetCargoFilterArray();
1610 /** Filter the engine list against the currently selected cargo filter */
1611 void FilterEngineList()
1613 this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1614 if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
1615 this->sel_engine = INVALID_ENGINE;
1616 } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
1617 this->sel_engine = this->eng_list[0];
1621 /** Filter a single engine */
1622 bool FilterSingleEngine(EngineID eid)
1624 CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1625 return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
1628 /* Figure out what train EngineIDs to put in the list */
1629 void GenerateBuildTrainList()
1631 EngineID sel_id = INVALID_ENGINE;
1632 int num_engines = 0;
1633 int num_wagons = 0;
1635 this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number);
1637 this->eng_list.Clear();
1639 /* Make list of all available train engines and wagons.
1640 * Also check to see if the previously selected engine is still available,
1641 * and if not, reset selection to INVALID_ENGINE. This could be the case
1642 * when engines become obsolete and are removed */
1643 const Engine *e;
1644 FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) {
1645 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1646 EngineID eid = e->index;
1647 const RailVehicleInfo *rvi = &e->u.rail;
1649 if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1650 if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1652 /* Filter now! So num_engines and num_wagons is valid */
1653 if (!FilterSingleEngine(eid)) continue;
1655 *this->eng_list.Append() = eid;
1657 if (rvi->railveh_type != RAILVEH_WAGON) {
1658 num_engines++;
1659 } else {
1660 num_wagons++;
1663 if (eid == this->sel_engine) sel_id = eid;
1666 this->sel_engine = sel_id;
1668 /* make engines first, and then wagons, sorted by selected sort_criteria */
1669 _engine_sort_direction = false;
1670 EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1672 /* and then sort engines */
1673 _engine_sort_direction = this->descending_sort_order;
1674 EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
1676 /* and finally sort wagons */
1677 EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
1680 /* Figure out what road vehicle EngineIDs to put in the list */
1681 void GenerateBuildRoadVehList()
1683 EngineID sel_id = INVALID_ENGINE;
1685 this->eng_list.Clear();
1687 const Engine *e;
1688 FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) {
1689 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1690 EngineID eid = e->index;
1691 if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1692 if (!HasBit(this->filter.roadtypes, HasBit(EngInfo(eid)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD)) continue;
1693 *this->eng_list.Append() = eid;
1695 if (eid == this->sel_engine) sel_id = eid;
1697 this->sel_engine = sel_id;
1700 /* Figure out what ship EngineIDs to put in the list */
1701 void GenerateBuildShipList()
1703 EngineID sel_id = INVALID_ENGINE;
1704 this->eng_list.Clear();
1706 const Engine *e;
1707 FOR_ALL_ENGINES_OF_TYPE(e, VEH_SHIP) {
1708 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1709 EngineID eid = e->index;
1710 if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1711 *this->eng_list.Append() = eid;
1713 if (eid == this->sel_engine) sel_id = eid;
1715 this->sel_engine = sel_id;
1718 /* Figure out what aircraft EngineIDs to put in the list */
1719 void GenerateBuildAircraftList()
1721 EngineID sel_id = INVALID_ENGINE;
1723 this->eng_list.Clear();
1725 const Station *st = this->listview_mode ? nullptr : Station::GetByTile(this->window_number);
1727 /* Make list of all available planes.
1728 * Also check to see if the previously selected plane is still available,
1729 * and if not, reset selection to INVALID_ENGINE. This could be the case
1730 * when planes become obsolete and are removed */
1731 const Engine *e;
1732 FOR_ALL_ENGINES_OF_TYPE(e, VEH_AIRCRAFT) {
1733 if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1734 EngineID eid = e->index;
1735 if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1736 /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1737 if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1739 *this->eng_list.Append() = eid;
1740 if (eid == this->sel_engine) sel_id = eid;
1743 this->sel_engine = sel_id;
1746 /* Generate the list of vehicles */
1747 void GenerateBuildList()
1749 if (!this->eng_list.NeedRebuild()) return;
1750 switch (this->vehicle_type) {
1751 default: NOT_REACHED();
1752 case VEH_TRAIN:
1753 this->GenerateBuildTrainList();
1754 this->eng_list.Compact();
1755 this->eng_list.RebuildDone();
1756 return; // trains should not reach the last sorting
1757 case VEH_ROAD:
1758 this->GenerateBuildRoadVehList();
1759 break;
1760 case VEH_SHIP:
1761 this->GenerateBuildShipList();
1762 break;
1763 case VEH_AIRCRAFT:
1764 this->GenerateBuildAircraftList();
1765 break;
1768 this->FilterEngineList();
1770 _engine_sort_direction = this->descending_sort_order;
1771 EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
1773 this->eng_list.Compact();
1774 this->eng_list.RebuildDone();
1777 void OnClick(Point pt, int widget, int click_count)
1779 switch (widget) {
1780 case WID_BV_SORT_ASCENDING_DESCENDING:
1781 this->descending_sort_order ^= true;
1782 _engine_sort_last_order[this->vehicle_type] = this->descending_sort_order;
1783 this->eng_list.ForceRebuild();
1784 this->SetDirty();
1785 break;
1787 case WID_BV_SHOW_HIDDEN_ENGINES:
1788 this->show_hidden_engines ^= true;
1789 _engine_sort_show_hidden_engines[this->vehicle_type] = this->show_hidden_engines;
1790 this->eng_list.ForceRebuild();
1791 this->SetWidgetLoweredState(widget, this->show_hidden_engines);
1792 this->SetDirty();
1793 break;
1795 case WID_BV_LIST: {
1796 uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1797 size_t num_items = this->eng_list.Length();
1798 this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE;
1799 this->SetDirty();
1800 if (_ctrl_pressed) {
1801 this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
1802 } else if (click_count > 1 && !this->listview_mode) {
1803 this->OnClick(pt, WID_BV_BUILD, 1);
1805 break;
1808 case WID_BV_SORT_DROPDOWN: // Select sorting criteria dropdown menu
1809 DisplayVehicleSortDropDown(this, this->vehicle_type, this->sort_criteria, WID_BV_SORT_DROPDOWN);
1810 break;
1812 case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1813 ShowDropDownMenu(this, this->cargo_filter_texts, this->cargo_filter_criteria, WID_BV_CARGO_FILTER_DROPDOWN, 0, 0);
1814 break;
1816 case WID_BV_SHOW_HIDE: {
1817 const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::GetIfValid(this->sel_engine);
1818 if (e != nullptr) {
1819 DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
1821 break;
1824 case WID_BV_BUILD: {
1825 EngineID sel_eng = this->sel_engine;
1826 if (sel_eng != INVALID_ENGINE) {
1827 CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1828 DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
1830 break;
1833 case WID_BV_RENAME: {
1834 EngineID sel_eng = this->sel_engine;
1835 if (sel_eng != INVALID_ENGINE) {
1836 this->rename_engine = sel_eng;
1837 SetDParam(0, sel_eng);
1838 ShowQueryString(STR_ENGINE_NAME, STR_QUERY_RENAME_TRAIN_TYPE_CAPTION + this->vehicle_type, MAX_LENGTH_ENGINE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
1840 break;
1846 * Some data on this window has become invalid.
1847 * @param data Information about the changed data.
1848 * @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.
1850 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1852 if (!gui_scope) return;
1853 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1854 if (this->vehicle_type == VEH_ROAD &&
1855 _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL &&
1856 this->sort_criteria > 7) {
1857 this->sort_criteria = 0;
1858 _engine_sort_last_criteria[VEH_ROAD] = 0;
1860 this->eng_list.ForceRebuild();
1863 virtual void SetStringParameters(int widget) const
1865 switch (widget) {
1866 case WID_BV_CAPTION:
1867 if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1868 const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1869 SetDParam(0, rti->strings.build_caption);
1870 } else {
1871 SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1873 break;
1875 case WID_BV_SORT_DROPDOWN:
1876 SetDParam(0, _engine_sort_listing[this->vehicle_type][this->sort_criteria]);
1877 break;
1879 case WID_BV_CARGO_FILTER_DROPDOWN:
1880 SetDParam(0, this->cargo_filter_texts[this->cargo_filter_criteria]);
1881 break;
1883 case WID_BV_SHOW_HIDE: {
1884 const Engine *e = (this->sel_engine == INVALID_ENGINE) ? nullptr : Engine::Get(this->sel_engine);
1885 if (e != nullptr && e->IsHidden(_local_company)) {
1886 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
1887 } else {
1888 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1890 break;
1895 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1897 switch (widget) {
1898 case WID_BV_LIST:
1899 resize->height = GetEngineListHeight(this->vehicle_type);
1900 size->height = 3 * resize->height;
1901 size->width = max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
1902 break;
1904 case WID_BV_PANEL:
1905 size->height = this->details_height;
1906 break;
1908 case WID_BV_SORT_ASCENDING_DESCENDING: {
1909 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1910 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1911 d.height += padding.height;
1912 *size = maxdim(*size, d);
1913 break;
1916 case WID_BV_SHOW_HIDE:
1917 *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1918 *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
1919 size->width += padding.width;
1920 size->height += padding.height;
1921 break;
1925 virtual void DrawWidget(const Rect &r, int widget) const
1927 switch (widget) {
1928 case WID_BV_LIST:
1929 DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, &this->eng_list, this->vscroll->GetPosition(), min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->eng_list.Length()), this->sel_engine, false, DEFAULT_GROUP);
1930 break;
1932 case WID_BV_SORT_ASCENDING_DESCENDING:
1933 this->DrawSortButtonState(WID_BV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
1934 break;
1938 virtual void OnPaint()
1940 this->GenerateBuildList();
1941 this->vscroll->SetCount(this->eng_list.Length());
1943 this->SetWidgetDisabledState(WID_BV_SHOW_HIDE, this->sel_engine == INVALID_ENGINE);
1945 /* disable renaming engines in network games if you are not the server */
1946 this->SetWidgetDisabledState(WID_BV_RENAME, (this->sel_engine == INVALID_ENGINE) || (_networking && !_network_server));
1947 this->SetWidgetDisabledState(WID_BV_BUILD, this->sel_engine == INVALID_ENGINE);
1949 this->DrawWidgets();
1951 if (!this->IsShaded()) {
1952 int needed_height = this->details_height;
1953 /* Draw details panels. */
1954 if (this->sel_engine != INVALID_ENGINE) {
1955 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1956 int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
1957 nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine);
1958 needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
1960 if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1961 int resize = needed_height - this->details_height;
1962 this->details_height = needed_height;
1963 this->ReInit(0, resize);
1964 return;
1969 virtual void OnQueryTextFinished(char *str)
1971 if (str == nullptr) return;
1973 DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
1976 virtual void OnDropdownSelect(int widget, int index)
1978 switch (widget) {
1979 case WID_BV_SORT_DROPDOWN:
1980 if (this->sort_criteria != index) {
1981 this->sort_criteria = index;
1982 _engine_sort_last_criteria[this->vehicle_type] = this->sort_criteria;
1983 this->eng_list.ForceRebuild();
1985 break;
1987 case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1988 if (this->cargo_filter_criteria != index) {
1989 this->cargo_filter_criteria = index;
1990 _engine_sort_last_cargo_criteria[this->vehicle_type] = this->cargo_filter[this->cargo_filter_criteria];
1991 /* deactivate filter if criteria is 'Show All', activate it otherwise */
1992 this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1993 this->eng_list.ForceRebuild();
1995 break;
1997 this->SetDirty();
2000 virtual void OnResize()
2002 this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
2006 /* Advanced window for trains. It is divided into two parts, one for locomotives and one for wagons. */
2008 struct BuildVehicleWindowTrainAdvanced : Window {
2010 /* Locomotives and wagons */
2012 VehicleType vehicle_type;
2013 RailTypeByte railtype;
2014 bool listview_mode;
2017 /* Locomotives */
2019 bool descending_sort_order_loco;
2020 byte sort_criteria_loco;
2021 EngineID sel_engine_loco;
2022 EngineID rename_engine_loco;
2023 GUIEngineList eng_list_loco;
2024 Scrollbar *vscroll_loco;
2025 byte cargo_filter_criteria_loco; ///< Selected cargo filter
2026 bool show_hidden_locos; ///< State of the 'show hidden locomotives' button.
2027 int details_height_loco; ///< Minimal needed height of the details panels (found so far).
2028 CargoID cargo_filter_loco[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
2029 StringID cargo_filter_texts_loco[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
2032 /* Wagons */
2034 bool descending_sort_order_wagon;
2035 byte sort_criteria_wagon;
2036 EngineID sel_engine_wagon;
2037 EngineID rename_engine_wagon;
2038 GUIEngineList eng_list_wagon;
2039 Scrollbar *vscroll_wagon;
2040 byte cargo_filter_criteria_wagon; ///< Selected cargo filter
2041 bool show_hidden_wagons; ///< State of the 'show hidden wagons' button.
2042 int details_height_wagon; ///< Minimal needed height of the details panels (found so far).
2043 CargoID cargo_filter_wagon[NUM_CARGO + 2]; ///< Available cargo filters; CargoID or CF_ANY or CF_NONE
2044 StringID cargo_filter_texts_wagon[NUM_CARGO + 3]; ///< Texts for filter_cargo, terminated by INVALID_STRING_ID
2047 bool virtual_train_mode; ///< Are we building a virtual train?
2048 Train **virtual_train_out; ///< Virtual train ptr
2050 BuildVehicleWindowTrainAdvanced(WindowDesc *desc, TileIndex tile, VehicleType type, Train **virtual_train_out) : Window(desc)
2053 this->vehicle_type = type;
2054 this->window_number = tile == INVALID_TILE ? (int)type : tile;
2056 this->virtual_train_out = virtual_train_out;
2057 this->virtual_train_mode = (virtual_train_out != nullptr);
2058 if (this->virtual_train_mode) this->window_number = 0;
2060 this->sel_engine_loco = INVALID_ENGINE;
2061 this->sort_criteria_loco = _last_sort_criteria_loco;
2062 this->descending_sort_order_loco = _last_sort_order_loco;
2063 this->show_hidden_wagons = _engine_sort_show_hidden_wagons;
2065 this->sel_engine_wagon = INVALID_ENGINE;
2066 this->sort_criteria_wagon = _last_sort_criteria_wagon;
2067 this->descending_sort_order_wagon = _last_sort_order_wagon;
2068 this->show_hidden_locos = _engine_sort_show_hidden_locos;
2070 this->railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
2071 this->listview_mode = !(this->virtual_train_mode) && (this->window_number <= VEH_END);
2073 this->CreateNestedTree();
2075 this->vscroll_loco = this->GetScrollbar(WID_BV_SCROLLBAR_LOCO);
2076 this->vscroll_wagon = this->GetScrollbar(WID_BV_SCROLLBAR_WAGON);
2078 /* If we are just viewing the list of vehicles, we do not need the Build button.
2079 * So we just hide it, and enlarge the Rename button by the now vacant place. */
2080 if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL_LOCO)->SetDisplayedPlane(SZSP_NONE);
2081 if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL_WAGON)->SetDisplayedPlane(SZSP_NONE);
2083 /* Locomotives */
2085 NWidgetCore *widget_loco = this->GetWidget<NWidgetCore>(WID_BV_LIST_LOCO);
2086 widget_loco->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
2088 widget_loco = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE_LOCO);
2089 widget_loco->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
2091 widget_loco = this->GetWidget<NWidgetCore>(WID_BV_BUILD_LOCO);
2092 if (this->virtual_train_mode) {
2093 widget_loco->widget_data = STR_TMPL_CONFIRM;
2094 widget_loco->tool_tip = STR_TMPL_CONFIRM;
2096 else {
2097 widget_loco->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_LOCOMOTIVE_BUTTON;
2098 widget_loco->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_LOCOMOTIVE_TOOLTIP;
2101 widget_loco = this->GetWidget<NWidgetCore>(WID_BV_RENAME_LOCO);
2102 widget_loco->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_LOCOMOTIVE_BUTTON;
2103 widget_loco->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_LOCOMOTIVE_TOOLTIP;
2105 widget_loco = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_LOCOS);
2106 widget_loco->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
2107 widget_loco->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
2108 widget_loco->SetLowered(this->show_hidden_locos);
2111 /* Wagons */
2113 NWidgetCore *widget_wagon = this->GetWidget<NWidgetCore>(WID_BV_LIST_WAGON);
2114 widget_wagon->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
2116 widget_wagon = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE_WAGON);
2117 widget_wagon->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
2119 widget_wagon = this->GetWidget<NWidgetCore>(WID_BV_BUILD_WAGON);
2120 if (this->virtual_train_mode) {
2121 widget_wagon->widget_data = STR_TMPL_CONFIRM;
2122 widget_wagon->tool_tip = STR_TMPL_CONFIRM;
2123 } else {
2124 widget_wagon->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_WAGON_BUTTON;
2125 widget_wagon->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_WAGON_TOOLTIP;
2128 widget_wagon = this->GetWidget<NWidgetCore>(WID_BV_RENAME_WAGON);
2129 widget_wagon->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_WAGON_BUTTON;
2130 widget_wagon->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_WAGON_TOOLTIP;
2132 widget_wagon = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_WAGONS);
2133 widget_wagon->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
2134 widget_wagon->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
2135 widget_wagon->SetLowered(this->show_hidden_wagons);
2138 this->details_height_loco = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
2139 this->details_height_wagon = ((this->vehicle_type == VEH_TRAIN) ? 10 : 9) * FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
2141 this->FinishInitNested(this->window_number);
2143 this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
2145 this->eng_list_loco.ForceRebuild();
2146 this->eng_list_wagon.ForceRebuild();
2148 this->GenerateBuildList(); // generate the list, since we need it in the next line
2149 /* Select the first engine in the list as default when opening the window */
2151 if (this->eng_list_loco.Length() > 0) this->sel_engine_loco = this->eng_list_loco[0];
2152 if (this->eng_list_wagon.Length() > 0) this->sel_engine_wagon = this->eng_list_wagon[0];
2156 /** Populate the filter list and set the cargo filter criteria. */
2157 void SetCargoFilterArray()
2159 /* Locomotives */
2161 uint filter_items_loco = 0;
2163 /* Add item for disabling filtering. */
2164 this->cargo_filter_loco[filter_items_loco] = CF_ANY;
2165 this->cargo_filter_texts_loco[filter_items_loco] = STR_PURCHASE_INFO_ALL_TYPES;
2166 filter_items_loco++;
2168 /* Add item for vehicles not carrying anything, e.g. train engines. */
2169 this->cargo_filter_loco[filter_items_loco] = CF_NONE;
2170 this->cargo_filter_texts_loco[filter_items_loco] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
2171 filter_items_loco++;
2173 /* Collect available cargo types for filtering. */
2174 const CargoSpec *cs_loco;
2175 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs_loco) {
2176 this->cargo_filter_loco[filter_items_loco] = cs_loco->Index();
2177 this->cargo_filter_texts_loco[filter_items_loco] = cs_loco->name;
2178 filter_items_loco++;
2181 /* Terminate the filter list. */
2182 this->cargo_filter_texts_loco[filter_items_loco] = INVALID_STRING_ID;
2184 /* If not found, the cargo criteria will be set to all cargoes. */
2185 this->cargo_filter_criteria_loco = 0;
2187 /* Find the last cargo filter criteria. */
2188 for (uint i = 0; i < filter_items_loco; i++) {
2189 if (this->cargo_filter_loco[i] == _last_filter_criteria_loco) {
2190 this->cargo_filter_criteria_loco = i;
2191 break;
2195 this->eng_list_loco.SetFilterFuncs(_filter_funcs);
2196 this->eng_list_loco.SetFilterState(this->cargo_filter_loco[this->cargo_filter_criteria_loco] != CF_ANY);
2199 /* Wagons */
2201 uint filter_items_wagon = 0;
2203 /* Add item for disabling filtering. */
2204 this->cargo_filter_wagon[filter_items_wagon] = CF_ANY;
2205 this->cargo_filter_texts_wagon[filter_items_wagon] = STR_PURCHASE_INFO_ALL_TYPES;
2206 filter_items_wagon++;
2208 /* Add item for vehicles not carrying anything, e.g. train engines.
2209 * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
2211 this->cargo_filter_wagon[filter_items_wagon] = CF_NONE;
2212 this->cargo_filter_texts_wagon[filter_items_wagon] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
2213 filter_items_wagon++;
2216 /* Collect available cargo types for filtering. */
2218 const CargoSpec *cs_wagon;
2220 FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs_wagon) {
2221 this->cargo_filter_wagon[filter_items_wagon] = cs_wagon->Index();
2222 this->cargo_filter_texts_wagon[filter_items_wagon] = cs_wagon->name;
2223 filter_items_wagon++;
2226 /* Terminate the filter list. */
2227 this->cargo_filter_texts_wagon[filter_items_wagon] = INVALID_STRING_ID;
2229 /* If not found, the cargo criteria will be set to all cargoes. */
2230 this->cargo_filter_criteria_wagon = 0;
2232 /* Find the last cargo filter criteria. */
2233 for (uint i = 0; i < filter_items_wagon; i++) {
2234 if (this->cargo_filter_wagon[i] == _last_filter_criteria_wagon) {
2235 this->cargo_filter_criteria_wagon = i;
2236 break;
2239 this->eng_list_wagon.SetFilterFuncs(_filter_funcs);
2240 this->eng_list_wagon.SetFilterState(this->cargo_filter_wagon[this->cargo_filter_criteria_wagon] != CF_ANY);
2243 void OnInit()
2245 this->SetCargoFilterArray();
2248 /** Filter the engine list against the currently selected cargo filter */
2249 void FilterEngineList()
2251 this->eng_list_loco.Filter(this->cargo_filter_loco[this->cargo_filter_criteria_loco]);
2252 if (0 == this->eng_list_loco.Length()) { // no engine passed through the filter, invalidate the previously selected engine
2253 this->sel_engine_loco = INVALID_ENGINE;
2254 } else if (!this->eng_list_loco.Contains(this->sel_engine_loco)) { // previously selected engine didn't pass the filter, select the first engine of the list
2255 this->sel_engine_loco = this->eng_list_loco[0];
2257 this->eng_list_wagon.Filter(this->cargo_filter_wagon[this->cargo_filter_criteria_wagon]);
2258 if (0 == this->eng_list_wagon.Length()) { // no engine passed through the filter, invalidate the previously selected engine
2259 this->sel_engine_wagon = INVALID_ENGINE;
2260 } else if (!this->eng_list_wagon.Contains(this->sel_engine_wagon)) { // previously selected engine didn't pass the filter, select the first engine of the list
2261 this->sel_engine_wagon = this->eng_list_wagon[0];
2265 /* Filter a single locomotive */
2266 bool FilterSingleEngineLoco(EngineID eid)
2268 CargoID filter_type = this->cargo_filter_loco[this->cargo_filter_criteria_loco];
2269 return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
2272 /* Filter a single wagon */
2273 bool FilterSingleEngineWagon(EngineID eid)
2275 CargoID filter_type = this->cargo_filter_wagon[this->cargo_filter_criteria_wagon];
2276 return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
2279 /* Figure out what train EngineIDs to put in the list */
2280 void GenerateBuildTrainList()
2282 this->railtype = (this->listview_mode || this->virtual_train_mode) ? RAILTYPE_END : GetRailType(this->window_number);
2284 /* Locomotives */
2286 EngineID sel_id_loco = INVALID_ENGINE;
2288 int num_engines_loco = 0;
2289 int num_wagons_loco = 0;
2291 this->eng_list_loco.Clear();
2293 /* Make list of all available train engines and wagons.
2294 * Also check to see if the previously selected engine is still available,
2295 * and if not, reset selection to INVALID_ENGINE. This could be the case
2296 * when engines become obsolete and are removed */
2297 const Engine *e_loco;
2299 FOR_ALL_ENGINES_OF_TYPE(e_loco, VEH_TRAIN) {
2300 if (!this->show_hidden_locos && e_loco->IsHidden(_local_company)) continue;
2301 EngineID eid = e_loco->index;
2302 const RailVehicleInfo *rvi = &e_loco->u.rail;
2304 if (this->railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->railtype)) continue;
2305 if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
2307 /* Filter now! So num_engines and num_wagons is valid */
2308 if (!FilterSingleEngineLoco(eid)) continue;
2310 if (rvi->railveh_type != RAILVEH_WAGON) {
2311 num_engines_loco++;
2312 *this->eng_list_loco.Append() = eid;
2315 if (eid == this->sel_engine_loco) sel_id_loco = eid;
2318 this->sel_engine_loco = sel_id_loco;
2321 /* Wagons */
2323 EngineID sel_id_wagon = INVALID_ENGINE;
2325 int num_engines_wagon = 0;
2326 int num_wagons_wagon = 0;
2328 this->eng_list_wagon.Clear();
2330 /* Make list of all available train engines and wagons.
2331 * Also check to see if the previously selected engine is still available,
2332 * and if not, reset selection to INVALID_ENGINE. This could be the case
2333 * when engines become obsolete and are removed */
2334 const Engine *e_wagon;
2336 FOR_ALL_ENGINES_OF_TYPE(e_wagon, VEH_TRAIN) {
2337 if (!this->show_hidden_wagons && e_wagon->IsHidden(_local_company)) continue;
2338 EngineID eid = e_wagon->index;
2339 const RailVehicleInfo *rvi = &e_wagon->u.rail;
2341 if (this->railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->railtype)) continue;
2342 if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
2344 /* Filter now! So num_engines and num_wagons is valid */
2345 if (!FilterSingleEngineWagon(eid)) continue;
2348 if (rvi->railveh_type == RAILVEH_WAGON) {
2349 *this->eng_list_wagon.Append() = eid;
2350 num_wagons_wagon++;
2353 if (eid == this->sel_engine_wagon) sel_id_wagon = eid;
2356 this->sel_engine_wagon = sel_id_wagon;
2358 /* Sort locomotives */
2359 _internal_sort_order_loco = this->descending_sort_order_loco;
2360 EngList_SortPartial(&this->eng_list_loco, _sorter_loco[this->sort_criteria_loco], 0, num_engines_loco);
2362 /* Sort wagons */
2363 _internal_sort_order_wagon = this->descending_sort_order_wagon;
2364 EngList_SortPartial(&this->eng_list_wagon, _sorter_wagon[this->sort_criteria_wagon], num_engines_wagon, num_wagons_wagon);
2368 /* Generate the list of vehicles */
2369 void GenerateBuildList()
2371 if (!this->eng_list_loco.NeedRebuild() && !this->eng_list_wagon.NeedRebuild()) return;
2373 this->GenerateBuildTrainList();
2374 this->eng_list_loco.Compact();
2375 this->eng_list_loco.RebuildDone();
2376 this->eng_list_wagon.Compact();
2377 this->eng_list_wagon.RebuildDone();
2381 void OnClick(Point pt, int widget, int click_count)
2383 switch (widget) {
2385 /* Locomotives */
2388 case WID_BV_SORT_ASSENDING_DESCENDING_LOCO: {
2389 this->descending_sort_order_loco ^= true;
2390 _last_sort_order_loco = this->descending_sort_order_loco;
2391 this->eng_list_loco.ForceRebuild();
2392 this->SetDirty();
2393 break;
2396 case WID_BV_SHOW_HIDDEN_LOCOS: {
2397 this->show_hidden_locos ^= true;
2398 _engine_sort_show_hidden_locos = this->show_hidden_locos;
2399 this->eng_list_loco.ForceRebuild();
2400 this->SetWidgetLoweredState(widget, this->show_hidden_locos);
2401 this->SetDirty();
2402 break;
2405 case WID_BV_LIST_LOCO: {
2406 uint i = this->vscroll_loco->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST_LOCO);
2407 size_t num_items = this->eng_list_loco.Length();
2408 this->sel_engine_loco = (i < num_items) ? this->eng_list_loco[i] : INVALID_ENGINE;
2409 this->SetDirty();
2411 if (_ctrl_pressed) {
2412 this->OnClick(pt, WID_BV_SHOW_HIDE_LOCO, 1);
2414 else if (click_count > 1 && !this->listview_mode) {
2415 this->OnClick(pt, WID_BV_BUILD_LOCO, 1);
2417 break;
2420 case WID_BV_SORT_DROPDOWN_LOCO: { // Select sorting criteria dropdown menu
2421 uint32 hidden_mask = 0;
2422 /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
2423 if (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
2424 SetBit(hidden_mask, 4); // tractive effort
2426 ShowDropDownMenu(this, _sort_listing_loco, this->sort_criteria_loco, WID_BV_SORT_DROPDOWN_LOCO, 0, hidden_mask);
2427 break;
2430 case WID_BV_CARGO_FILTER_DROPDOWN_LOCO: { // Select cargo filtering criteria dropdown menu
2431 ShowDropDownMenu(this, this->cargo_filter_texts_loco, this->cargo_filter_criteria_loco, WID_BV_CARGO_FILTER_DROPDOWN_LOCO, 0, 0);
2432 break;
2435 case WID_BV_SHOW_HIDE_LOCO: {
2436 const Engine *e = (this->sel_engine_loco == INVALID_ENGINE) ? nullptr : Engine::GetIfValid(this->sel_engine_loco);
2437 if (e != nullptr) {
2438 DoCommandP(0, 0, this->sel_engine_loco | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
2440 break;
2443 case WID_BV_BUILD_LOCO: {
2444 EngineID sel_eng = this->sel_engine_loco;
2445 if (sel_eng != INVALID_ENGINE) {
2446 if (this->virtual_train_mode) {
2447 DoCommandP(0, sel_eng, 0, CMD_BUILD_VIRTUAL_RAIL_VEHICLE, CcAddVirtualEngine);
2449 else {
2450 CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
2451 DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
2454 break;
2457 case WID_BV_RENAME_LOCO: {
2458 EngineID sel_eng = this->sel_engine_loco;
2459 if (sel_eng != INVALID_ENGINE) {
2460 this->rename_engine_loco = sel_eng;
2461 this->rename_engine_wagon = INVALID_ENGINE;
2462 SetDParam(0, sel_eng);
2463 ShowQueryString(STR_ENGINE_NAME, STR_QUERY_RENAME_TRAIN_TYPE_LOCOMOTIVE_CAPTION + this->vehicle_type, MAX_LENGTH_ENGINE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
2465 break;
2469 /* Wagons */
2471 case WID_BV_SORT_ASSENDING_DESCENDING_WAGON: {
2472 this->descending_sort_order_wagon ^= true;
2473 _last_sort_order_wagon = this->descending_sort_order_wagon;
2474 this->eng_list_wagon.ForceRebuild();
2475 this->SetDirty();
2476 break;
2479 case WID_BV_SHOW_HIDDEN_WAGONS: {
2480 this->show_hidden_wagons ^= true;
2481 _engine_sort_show_hidden_wagons = this->show_hidden_wagons;
2482 this->eng_list_wagon.ForceRebuild();
2483 this->SetWidgetLoweredState(widget, this->show_hidden_wagons);
2484 this->SetDirty();
2485 break;
2488 case WID_BV_LIST_WAGON: {
2489 uint i = this->vscroll_wagon->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST_WAGON);
2490 size_t num_items = this->eng_list_wagon.Length();
2491 this->sel_engine_wagon = (i < num_items) ? this->eng_list_wagon[i] : INVALID_ENGINE;
2492 this->SetDirty();
2494 if (_ctrl_pressed) {
2495 this->OnClick(pt, WID_BV_SHOW_HIDE_WAGON, 1);
2497 else if (click_count > 1 && !this->listview_mode) {
2498 this->OnClick(pt, WID_BV_BUILD_WAGON, 1);
2500 break;
2503 case WID_BV_SORT_DROPDOWN_WAGON: { // Select sorting criteria dropdown menu
2504 uint32 hidden_mask = 0;
2505 /* Disable sorting by maximum speed when wagon speed is disabled. */
2506 if (!_settings_game.vehicle.wagon_speed_limits) {
2507 SetBit(hidden_mask, 2); // maximum speed
2509 ShowDropDownMenu(this, _sort_listing_wagon, this->sort_criteria_wagon, WID_BV_SORT_DROPDOWN_WAGON, 0, hidden_mask);
2510 break;
2513 case WID_BV_CARGO_FILTER_DROPDOWN_WAGON: { // Select cargo filtering criteria dropdown menu
2514 ShowDropDownMenu(this, this->cargo_filter_texts_wagon, this->cargo_filter_criteria_wagon, WID_BV_CARGO_FILTER_DROPDOWN_WAGON, 0, 0);
2515 break;
2518 case WID_BV_SHOW_HIDE_WAGON: {
2519 const Engine *e = (this->sel_engine_wagon == INVALID_ENGINE) ? nullptr : Engine::GetIfValid(this->sel_engine_wagon);
2520 if (e != nullptr) {
2521 DoCommandP(0, 0, this->sel_engine_wagon | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
2523 break;
2526 case WID_BV_BUILD_WAGON: {
2527 EngineID sel_eng = this->sel_engine_wagon;
2528 if (sel_eng != INVALID_ENGINE) {
2529 if (this->virtual_train_mode) {
2530 DoCommandP(0, sel_eng, 0, CMD_BUILD_VIRTUAL_RAIL_VEHICLE, CcAddVirtualEngine);
2532 else {
2533 CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
2534 DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
2537 break;
2540 case WID_BV_RENAME_WAGON: {
2541 EngineID sel_eng = this->sel_engine_wagon;
2542 if (sel_eng != INVALID_ENGINE) {
2543 this->rename_engine_loco = INVALID_ENGINE;
2544 this->rename_engine_wagon = sel_eng;
2545 SetDParam(0, sel_eng);
2546 ShowQueryString(STR_ENGINE_NAME, STR_QUERY_RENAME_TRAIN_TYPE_WAGON_CAPTION + this->vehicle_type, MAX_LENGTH_ENGINE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
2548 break;
2554 * Some data on this window has become invalid.
2555 * @param data Information about the changed data.
2556 * @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.
2558 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2560 if (!gui_scope) return;
2562 /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
2563 this->eng_list_loco.ForceRebuild();
2564 this->eng_list_wagon.ForceRebuild();
2567 virtual void SetStringParameters(int widget) const
2569 switch (widget) {
2570 case WID_BV_CAPTION: {
2571 if (this->vehicle_type == VEH_TRAIN && !this->listview_mode && !this->virtual_train_mode) {
2572 const RailtypeInfo *rti = GetRailTypeInfo(this->railtype);
2573 SetDParam(0, rti->strings.build_caption);
2574 } else {
2575 SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
2577 break;
2580 case WID_BV_CAPTION_LOCO: {
2581 SetDParam(0, STR_BUY_VEHICLE_TRAIN_LOCOMOTIVES);
2582 break;
2585 case WID_BV_SHOW_HIDE_LOCO: {
2586 const Engine *e = (this->sel_engine_loco == INVALID_ENGINE) ? nullptr : Engine::GetIfValid(this->sel_engine_loco);
2587 if (e != nullptr && e->IsHidden(_local_company)) {
2588 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
2590 else {
2591 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
2593 break;
2596 case WID_BV_CAPTION_WAGON: {
2597 SetDParam(0, STR_BUY_VEHICLE_TRAIN_WAGONS);
2598 break;
2601 case WID_BV_SORT_DROPDOWN_LOCO: {
2602 SetDParam(0, _sort_listing_loco[this->sort_criteria_loco]);
2603 break;
2606 case WID_BV_CARGO_FILTER_DROPDOWN_LOCO: {
2607 SetDParam(0, this->cargo_filter_texts_loco[this->cargo_filter_criteria_loco]);
2608 break;
2611 case WID_BV_SORT_DROPDOWN_WAGON: {
2612 SetDParam(0, _sort_listing_wagon[this->sort_criteria_wagon]);
2613 break;
2616 case WID_BV_CARGO_FILTER_DROPDOWN_WAGON: {
2617 SetDParam(0, this->cargo_filter_texts_wagon[this->cargo_filter_criteria_wagon]);
2618 break;
2621 case WID_BV_SHOW_HIDE_WAGON: {
2622 const Engine *e = (this->sel_engine_wagon == INVALID_ENGINE) ? nullptr : Engine::GetIfValid(this->sel_engine_wagon);
2623 if (e != nullptr && e->IsHidden(_local_company)) {
2624 SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
2626 else {
2627 SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
2629 break;
2634 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2636 switch (widget) {
2637 case WID_BV_LIST_LOCO: {
2638 resize->height = GetEngineListHeight(this->vehicle_type);
2639 size->height = 3 * resize->height;
2640 break;
2643 case WID_BV_PANEL_LOCO: {
2644 size->height = this->details_height_loco;
2645 break;
2648 case WID_BV_SORT_ASSENDING_DESCENDING_LOCO: {
2649 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
2650 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
2651 d.height += padding.height;
2652 *size = maxdim(*size, d);
2653 break;
2656 case WID_BV_LIST_WAGON: {
2657 resize->height = GetEngineListHeight(this->vehicle_type);
2658 size->height = 3 * resize->height;
2659 break;
2662 case WID_BV_PANEL_WAGON: {
2663 size->height = this->details_height_wagon;
2664 break;
2667 case WID_BV_SORT_ASSENDING_DESCENDING_WAGON: {
2668 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
2669 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
2670 d.height += padding.height;
2671 *size = maxdim(*size, d);
2672 break;
2675 case WID_BV_SHOW_HIDE_LOCO: {
2676 *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
2677 *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
2678 size->width += padding.width;
2679 size->height += padding.height;
2680 break;
2683 case WID_BV_SHOW_HIDE_WAGON: {
2684 *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
2685 *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
2686 size->width += padding.width;
2687 size->height += padding.height;
2688 break;
2693 virtual void DrawWidget(const Rect &r, int widget) const
2695 switch (widget) {
2696 case WID_BV_LIST_LOCO: {
2697 DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, &this->eng_list_loco, this->vscroll_loco->GetPosition(), min(this->vscroll_loco->GetPosition() + this->vscroll_loco->GetCapacity(), this->eng_list_loco.Length()), this->sel_engine_loco, false, DEFAULT_GROUP);
2698 break;
2701 case WID_BV_SORT_ASSENDING_DESCENDING_LOCO: {
2702 this->DrawSortButtonState(WID_BV_SORT_ASSENDING_DESCENDING_LOCO, this->descending_sort_order_loco ? SBS_DOWN : SBS_UP);
2703 break;
2706 case WID_BV_LIST_WAGON: {
2707 DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, &this->eng_list_wagon, this->vscroll_wagon->GetPosition(), min(this->vscroll_wagon->GetPosition() + this->vscroll_wagon->GetCapacity(), this->eng_list_wagon.Length()), this->sel_engine_wagon, false, DEFAULT_GROUP);
2708 break;
2711 case WID_BV_SORT_ASSENDING_DESCENDING_WAGON: {
2712 this->DrawSortButtonState(WID_BV_SORT_ASSENDING_DESCENDING_WAGON, this->descending_sort_order_wagon ? SBS_DOWN : SBS_UP);
2713 break;
2718 virtual void OnPaint()
2720 this->GenerateBuildList();
2721 this->vscroll_loco->SetCount(this->eng_list_loco.Length());
2722 this->vscroll_wagon->SetCount(this->eng_list_wagon.Length());
2724 this->SetWidgetDisabledState(WID_BV_SHOW_HIDE_LOCO, this->sel_engine_loco == INVALID_ENGINE);
2725 this->SetWidgetDisabledState(WID_BV_SHOW_HIDE_WAGON, this->sel_engine_wagon == INVALID_ENGINE);
2727 /* disable renaming engines in network games if you are not the server */
2728 this->SetWidgetDisabledState(WID_BV_RENAME_LOCO, (this->sel_engine_loco == INVALID_ENGINE) || (_networking && !_network_server));
2729 this->SetWidgetDisabledState(WID_BV_BUILD_LOCO, this->sel_engine_loco == INVALID_ENGINE);
2731 /* disable renaming engines in network games if you are not the server */
2732 this->SetWidgetDisabledState(WID_BV_RENAME_WAGON, (this->sel_engine_wagon == INVALID_ENGINE) || (_networking && !_network_server));
2733 this->SetWidgetDisabledState(WID_BV_BUILD_WAGON, this->sel_engine_wagon == INVALID_ENGINE);
2735 this->DrawWidgets();
2737 if (!this->IsShaded()) {
2738 int needed_height_loco = this->details_height_loco;
2739 /* Draw details panels. */
2740 if (this->sel_engine_loco != INVALID_ENGINE) {
2741 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL_LOCO);
2742 int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
2743 nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine_loco);
2744 needed_height_loco = max(needed_height_loco, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
2746 if (needed_height_loco != this->details_height_loco) { // Details window are not high enough, enlarge them.
2747 int resize = needed_height_loco - this->details_height_loco;
2748 this->details_height_loco = needed_height_loco;
2749 this->ReInit(0, resize);
2750 return;
2753 int needed_height_wagon = this->details_height_wagon;
2754 if (this->sel_engine_wagon != INVALID_ENGINE) {
2755 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL_WAGON);
2756 int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
2757 nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine_wagon);
2758 needed_height_wagon = max(needed_height_wagon, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
2760 if (needed_height_wagon != this->details_height_wagon) { // Details window are not high enough, enlarge them.
2761 int resize = needed_height_wagon - this->details_height_wagon;
2762 this->details_height_wagon = needed_height_wagon;
2763 this->ReInit(0, resize);
2764 return;
2769 virtual void OnQueryTextFinished(char *str)
2771 if (str == nullptr) return;
2772 if(this->rename_engine_loco != INVALID_ENGINE)
2774 DoCommandP(0, this->rename_engine_loco, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
2776 else
2778 DoCommandP(0, this->rename_engine_wagon, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), nullptr, str);
2782 virtual void OnDropdownSelect(int widget, int index)
2784 switch (widget) {
2785 case WID_BV_SORT_DROPDOWN_LOCO: {
2786 if (this->sort_criteria_loco != index) {
2787 this->sort_criteria_loco = index;
2788 _last_sort_criteria_loco = this->sort_criteria_loco;
2789 this->eng_list_loco.ForceRebuild();
2791 break;
2794 case WID_BV_CARGO_FILTER_DROPDOWN_LOCO: { // Select a cargo filter criteria
2795 if (this->cargo_filter_criteria_loco != index) {
2796 this->cargo_filter_criteria_loco = index;
2797 _last_filter_criteria_loco = this->cargo_filter_loco[this->cargo_filter_criteria_loco];
2798 /* deactivate filter if criteria is 'Show All', activate it otherwise */
2799 this->eng_list_loco.SetFilterState(this->cargo_filter_loco[this->cargo_filter_criteria_loco] != CF_ANY);
2800 this->eng_list_loco.ForceRebuild();
2802 break;
2805 case WID_BV_SORT_DROPDOWN_WAGON: {
2806 if (this->sort_criteria_wagon != index) {
2807 this->sort_criteria_wagon = index;
2808 _last_sort_criteria_wagon = this->sort_criteria_wagon;
2809 this->eng_list_wagon.ForceRebuild();
2811 break;
2814 case WID_BV_CARGO_FILTER_DROPDOWN_WAGON: { // Select a cargo filter criteria
2815 if (this->cargo_filter_criteria_wagon != index) {
2816 this->cargo_filter_criteria_wagon = index;
2817 _last_filter_criteria_wagon = this->cargo_filter_wagon[this->cargo_filter_criteria_wagon];
2818 /* deactivate filter if criteria is 'Show All', activate it otherwise */
2819 this->eng_list_wagon.SetFilterState(this->cargo_filter_wagon[this->cargo_filter_criteria_wagon] != CF_ANY);
2820 this->eng_list_wagon.ForceRebuild();
2822 break;
2826 this->SetDirty();
2829 virtual void OnResize()
2831 this->vscroll_loco->SetCapacityFromWidget(this, WID_BV_LIST_LOCO);
2832 this->vscroll_wagon->SetCapacityFromWidget(this, WID_BV_LIST_WAGON);
2835 void AddVirtualEngine(Train *toadd)
2837 if (this->virtual_train_out == nullptr) return;
2839 if (*(this->virtual_train_out) == nullptr) {
2840 *(this->virtual_train_out) = toadd;
2841 } else {
2842 VehicleID target = (*(this->virtual_train_out))->GetLastUnit()->index;
2844 DoCommandP(0, (1 << 21) | toadd->index, target, CMD_MOVE_RAIL_VEHICLE);
2846 InvalidateWindowClassesData(WC_CREATE_TEMPLATE);
2847 InvalidateWindowClassesData(WC_TEMPLATEGUI_MAIN);
2851 void CcAddVirtualEngine(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2853 if (result.Failed()) return;
2855 Window* window = FindWindowById(WC_BUILD_VIRTUAL_TRAIN, 0);
2856 if (window) {
2857 Train* train = Train::From(Vehicle::Get(_new_vehicle_id));
2858 ((BuildVehicleWindowTrainAdvanced*) window)->AddVirtualEngine(train);
2862 static WindowDesc _build_vehicle_desc(
2863 WDP_AUTO, "build_vehicle", 240, 268,
2864 WC_BUILD_VEHICLE, WC_NONE,
2865 WDF_CONSTRUCTION,
2866 _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
2869 static WindowDesc _build_vehicle_desc_train_advanced(
2870 WDP_AUTO, "build_vehicle", 480, 268,
2871 WC_BUILD_VEHICLE, WC_NONE,
2872 WDF_CONSTRUCTION,
2873 _nested_build_vehicle_widgets_train_advanced, lengthof(_nested_build_vehicle_widgets_train_advanced)
2876 static WindowDesc _build_template_vehicle_desc(
2877 WDP_AUTO, "build_vehicle", 240, 268,
2878 WC_BUILD_VIRTUAL_TRAIN, WC_CREATE_TEMPLATE,
2879 WDF_CONSTRUCTION,
2880 _nested_build_vehicle_widgets_train_advanced, lengthof(_nested_build_vehicle_widgets_train_advanced)
2883 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
2885 /* We want to be able to open both Available Train as Available Ships,
2886 * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
2887 * As it always is a low value, it won't collide with any real tile
2888 * number. */
2889 uint num = (tile == INVALID_TILE) ? (int)type : tile;
2891 assert(IsCompanyBuildableVehicleType(type));
2893 DeleteWindowById(WC_BUILD_VEHICLE, num);
2895 if(type == VEH_TRAIN && _settings_client.gui.advanced_train_purchase_window)
2897 new BuildVehicleWindowTrainAdvanced(&_build_vehicle_desc_train_advanced, tile, type, nullptr);
2899 else
2901 new BuildVehicleWindow(&_build_vehicle_desc, tile, type);
2905 void ShowTemplateTrainBuildVehicleWindow(Train **virtual_train)
2907 assert(IsCompanyBuildableVehicleType(VEH_TRAIN));
2909 DeleteWindowById(WC_BUILD_VIRTUAL_TRAIN, 0);
2911 new BuildVehicleWindowTrainAdvanced(&_build_template_vehicle_desc, INVALID_TILE, VEH_TRAIN, virtual_train);