1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
8 /** @file autoreplace_gui.cpp GUI for autoreplace handling. */
9 
10 #include "stdafx.h"
11 #include "command_func.h"
12 #include "vehicle_gui.h"
13 #include "newgrf_engine.h"
14 #include "rail.h"
15 #include "road.h"
16 #include "strings_func.h"
17 #include "window_func.h"
18 #include "autoreplace_func.h"
19 #include "company_func.h"
20 #include "engine_base.h"
21 #include "window_gui.h"
22 #include "engine_gui.h"
23 #include "settings_func.h"
24 #include "core/geometry_func.hpp"
25 #include "rail_gui.h"
26 #include "road_gui.h"
27 #include "widgets/dropdown_func.h"
28 
29 #include "widgets/autoreplace_widget.h"
30 
31 #include "safeguards.h"
32 
33 void DrawEngineList(VehicleType type, int x, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group);
34 
EngineNumberSorter(const EngineID & a,const EngineID & b)35 static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
36 {
37 	return Engine::Get(a)->list_position < Engine::Get(b)->list_position;
38 }
39 
40 /**
41  * Rebuild the left autoreplace list if an engine is removed or added
42  * @param e Engine to check if it is removed or added
43  * @param id_g The group the engine belongs to
44  *  Note: this function only works if it is called either
45  *   - when a new vehicle is build, but before it's counted in num_engines
46  *   - when a vehicle is deleted and after it's subtracted from num_engines
47  *   - when not changing the count (used when changing replace orders)
48  */
InvalidateAutoreplaceWindow(EngineID e,GroupID id_g)49 void InvalidateAutoreplaceWindow(EngineID e, GroupID id_g)
50 {
51 	if (GetGroupNumEngines(_local_company, id_g, e) == 0 || GetGroupNumEngines(_local_company, ALL_GROUP, e) == 0) {
52 		/* We don't have any of this engine type.
53 		 * Either we just sold the last one, we build a new one or we stopped replacing it.
54 		 * In all cases, we need to update the left list */
55 		InvalidateWindowData(WC_REPLACE_VEHICLE, Engine::Get(e)->type, 1);
56 	}
57 }
58 
59 /**
60  * When an engine is made buildable or is removed from being buildable, add/remove it from the build/autoreplace lists
61  * @param type The type of engine
62  */
AddRemoveEngineFromAutoreplaceAndBuildWindows(VehicleType type)63 void AddRemoveEngineFromAutoreplaceAndBuildWindows(VehicleType type)
64 {
65 	InvalidateWindowData(WC_REPLACE_VEHICLE, type, 0); // Update the autoreplace window
66 	InvalidateWindowClassesData(WC_BUILD_VEHICLE); // The build windows needs updating as well
67 }
68 
69 static const StringID _start_replace_dropdown[] = {
70 	STR_REPLACE_VEHICLES_NOW,
71 	STR_REPLACE_VEHICLES_WHEN_OLD,
72 	INVALID_STRING_ID
73 };
74 
75 /**
76  * Window for the autoreplacing of vehicles.
77  */
78 class ReplaceVehicleWindow : public Window {
79 	EngineID sel_engine[2];       ///< Selected engine left and right.
80 	GUIEngineList engines[2];     ///< Left and right list of engines.
81 	bool replace_engines;         ///< If \c true, engines are replaced, if \c false, wagons are replaced (only for trains).
82 	bool reset_sel_engine;        ///< Also reset #sel_engine while updating left and/or right and no valid engine selected.
83 	GroupID sel_group;            ///< Group selected to replace.
84 	int details_height;           ///< Minimal needed height of the details panels, in text lines (found so far).
85 	byte sort_criteria;           ///< Criteria of sorting vehicles.
86 	bool descending_sort_order;   ///< Order of sorting vehicles.
87 	bool show_hidden_engines;     ///< Whether to show the hidden engines.
88 	RailType sel_railtype;        ///< Type of rail tracks selected. #INVALID_RAILTYPE to show all.
89 	RoadType sel_roadtype;        ///< Type of road selected. #INVALID_ROADTYPE to show all.
90 	Scrollbar *vscroll[2];
91 
92 	/**
93 	 * Figure out if an engine should be added to a list.
94 	 * @param e            The EngineID.
95 	 * @param draw_left    If \c true, the left list is drawn (the engines specific to the railtype you selected).
96 	 * @param show_engines If \c true, the locomotives are drawn, else the wagons are drawn (never both).
97 	 * @return \c true if the engine should be in the list (based on this check), else \c false.
98 	 */
GenerateReplaceRailList(EngineID e,bool draw_left,bool show_engines)99 	bool GenerateReplaceRailList(EngineID e, bool draw_left, bool show_engines)
100 	{
101 		const RailVehicleInfo *rvi = RailVehInfo(e);
102 
103 		/* Ensure that the wagon/engine selection fits the engine. */
104 		if ((rvi->railveh_type == RAILVEH_WAGON) == show_engines) return false;
105 
106 		if (draw_left && this->sel_railtype != INVALID_RAILTYPE) {
107 			/* Ensure that the railtype is specific to the selected one */
108 			if (rvi->railtype != this->sel_railtype) return false;
109 		}
110 		return true;
111 	}
112 
113 
114 	/**
115 	 * Generate an engines list
116 	 * @param draw_left true if generating the left list, otherwise false
117 	 */
GenerateReplaceVehList(bool draw_left)118 	void GenerateReplaceVehList(bool draw_left)
119 	{
120 		EngineID selected_engine = INVALID_ENGINE;
121 		VehicleType type = (VehicleType)this->window_number;
122 		byte side = draw_left ? 0 : 1;
123 
124 		GUIEngineList *list = &this->engines[side];
125 		list->clear();
126 
127 		for (const Engine *e : Engine::IterateType(type)) {
128 			if (!draw_left && !this->show_hidden_engines && e->IsHidden(_local_company)) continue;
129 			EngineID eid = e->index;
130 			switch (type) {
131 				case VEH_TRAIN:
132 					if (!this->GenerateReplaceRailList(eid, draw_left, this->replace_engines)) continue; // special rules for trains
133 					break;
134 
135 				case VEH_ROAD:
136 					if (draw_left && this->sel_roadtype != INVALID_ROADTYPE) {
137 						/* Ensure that the roadtype is specific to the selected one */
138 						if (e->u.road.roadtype != this->sel_roadtype) continue;
139 					}
140 					break;
141 
142 				default:
143 					break;
144 			}
145 
146 			if (draw_left) {
147 				const uint num_engines = GetGroupNumEngines(_local_company, this->sel_group, eid);
148 
149 				/* Skip drawing the engines we don't have any of and haven't set for replacement */
150 				if (num_engines == 0 && EngineReplacementForCompany(Company::Get(_local_company), eid, this->sel_group) == INVALID_ENGINE) continue;
151 			} else {
152 				if (!CheckAutoreplaceValidity(this->sel_engine[0], eid, _local_company)) continue;
153 			}
154 
155 			list->push_back(eid);
156 			if (eid == this->sel_engine[side]) selected_engine = eid; // The selected engine is still in the list
157 		}
158 		this->sel_engine[side] = selected_engine; // update which engine we selected (the same or none, if it's not in the list anymore)
159 		if (draw_left) {
160 			EngList_Sort(list, &EngineNumberSorter);
161 		} else {
162 			_engine_sort_direction = this->descending_sort_order;
163 			EngList_Sort(list, _engine_sort_functions[this->window_number][this->sort_criteria]);
164 		}
165 	}
166 
167 	/** Generate the lists */
GenerateLists()168 	void GenerateLists()
169 	{
170 		EngineID e = this->sel_engine[0];
171 
172 		if (this->engines[0].NeedRebuild()) {
173 			/* We need to rebuild the left engines list */
174 			this->GenerateReplaceVehList(true);
175 			this->vscroll[0]->SetCount((uint)this->engines[0].size());
176 			if (this->reset_sel_engine && this->sel_engine[0] == INVALID_ENGINE && this->engines[0].size() != 0) {
177 				this->sel_engine[0] = this->engines[0][0];
178 			}
179 		}
180 
181 		if (this->engines[1].NeedRebuild() || e != this->sel_engine[0]) {
182 			/* Either we got a request to rebuild the right engines list, or the left engines list selected a different engine */
183 			if (this->sel_engine[0] == INVALID_ENGINE) {
184 				/* Always empty the right engines list when nothing is selected in the left engines list */
185 				this->engines[1].clear();
186 				this->sel_engine[1] = INVALID_ENGINE;
187 			} else {
188 				if (this->reset_sel_engine && this->sel_engine[0] != INVALID_ENGINE) {
189 					/* Select the current replacement for sel_engine[0]. */
190 					const Company *c = Company::Get(_local_company);
191 					this->sel_engine[1] = EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group);
192 				}
193 				/* Regenerate the list on the right. Note: This resets sel_engine[1] to INVALID_ENGINE, if it is no longer available. */
194 				this->GenerateReplaceVehList(false);
195 				this->vscroll[1]->SetCount((uint)this->engines[1].size());
196 				if (this->reset_sel_engine && this->sel_engine[1] != INVALID_ENGINE) {
197 					int position = 0;
198 					for (EngineID &eid : this->engines[1]) {
199 						if (eid == this->sel_engine[1]) break;
200 						++position;
201 					}
202 					this->vscroll[1]->ScrollTowards(position);
203 				}
204 			}
205 		}
206 		/* Reset the flags about needed updates */
207 		this->engines[0].RebuildDone();
208 		this->engines[1].RebuildDone();
209 		this->reset_sel_engine = false;
210 	}
211 
212 	/**
213 	 * Handle click on the start replace button.
214 	 * @param replace_when_old Replace now or only when old?
215 	 */
ReplaceClick_StartReplace(bool replace_when_old)216 	void ReplaceClick_StartReplace(bool replace_when_old)
217 	{
218 		EngineID veh_from = this->sel_engine[0];
219 		EngineID veh_to = this->sel_engine[1];
220 		DoCommandP(0, (replace_when_old ? 1 : 0) | (this->sel_group << 16), veh_from + (veh_to << 16), CMD_SET_AUTOREPLACE);
221 	}
222 
223 public:
ReplaceVehicleWindow(WindowDesc * desc,VehicleType vehicletype,GroupID id_g)224 	ReplaceVehicleWindow(WindowDesc *desc, VehicleType vehicletype, GroupID id_g) : Window(desc)
225 	{
226 		this->sel_railtype = INVALID_RAILTYPE;
227 		this->sel_roadtype = INVALID_ROADTYPE;
228 		this->replace_engines  = true; // start with locomotives (all other vehicles will not read this bool)
229 		this->engines[0].ForceRebuild();
230 		this->engines[1].ForceRebuild();
231 		this->reset_sel_engine = true;
232 		this->details_height   = ((vehicletype == VEH_TRAIN) ? 10 : 9);
233 		this->sel_engine[0] = INVALID_ENGINE;
234 		this->sel_engine[1] = INVALID_ENGINE;
235 		this->show_hidden_engines = _engine_sort_show_hidden_engines[vehicletype];
236 
237 		this->CreateNestedTree();
238 		this->vscroll[0] = this->GetScrollbar(WID_RV_LEFT_SCROLLBAR);
239 		this->vscroll[1] = this->GetScrollbar(WID_RV_RIGHT_SCROLLBAR);
240 
241 		NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_RV_SHOW_HIDDEN_ENGINES);
242 		widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + vehicletype;
243 		widget->tool_tip    = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + vehicletype;
244 		widget->SetLowered(this->show_hidden_engines);
245 		this->FinishInitNested(vehicletype);
246 
247 		if (vehicletype == VEH_TRAIN || vehicletype == VEH_ROAD) {
248 			widget = this->GetWidget<NWidgetCore>(WID_RV_RAIL_ROAD_TYPE_DROPDOWN);
249 			widget->tool_tip = STR_REPLACE_HELP_RAILTYPE + vehicletype;
250 		}
251 
252 		this->sort_criteria = _engine_sort_last_criteria[vehicletype];
253 		this->descending_sort_order = _engine_sort_last_order[vehicletype];
254 		this->owner = _local_company;
255 		this->sel_group = id_g;
256 	}
257 
UpdateWidgetSize(int widget,Dimension * size,const Dimension & padding,Dimension * fill,Dimension * resize)258 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
259 	{
260 		switch (widget) {
261 			case WID_RV_SORT_ASCENDING_DESCENDING: {
262 				Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
263 				d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
264 				d.height += padding.height;
265 				*size = maxdim(*size, d);
266 				break;
267 			}
268 
269 			case WID_RV_LEFT_MATRIX:
270 			case WID_RV_RIGHT_MATRIX:
271 				resize->height = GetEngineListHeight((VehicleType)this->window_number);
272 				size->height = (this->window_number <= VEH_ROAD ? 8 : 4) * resize->height;
273 				break;
274 
275 			case WID_RV_LEFT_DETAILS:
276 			case WID_RV_RIGHT_DETAILS:
277 				size->height = FONT_HEIGHT_NORMAL * this->details_height + padding.height;
278 				break;
279 
280 			case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: {
281 				StringID str = this->GetWidget<NWidgetCore>(widget)->widget_data;
282 				SetDParam(0, STR_CONFIG_SETTING_ON);
283 				Dimension d = GetStringBoundingBox(str);
284 				SetDParam(0, STR_CONFIG_SETTING_OFF);
285 				d = maxdim(d, GetStringBoundingBox(str));
286 				d.width += padding.width;
287 				d.height += padding.height;
288 				*size = maxdim(*size, d);
289 				break;
290 			}
291 
292 			case WID_RV_TRAIN_ENGINEWAGON_DROPDOWN: {
293 				Dimension d = GetStringBoundingBox(STR_REPLACE_ENGINES);
294 				d = maxdim(d, GetStringBoundingBox(STR_REPLACE_WAGONS));
295 				d.width += padding.width;
296 				d.height += padding.height;
297 				*size = maxdim(*size, d);
298 				break;
299 			}
300 
301 			case WID_RV_INFO_TAB: {
302 				Dimension d = GetStringBoundingBox(STR_REPLACE_NOT_REPLACING);
303 				d = maxdim(d, GetStringBoundingBox(STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED));
304 				d.width += WD_FRAMETEXT_LEFT +  WD_FRAMETEXT_RIGHT;
305 				d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
306 				*size = maxdim(*size, d);
307 				break;
308 			}
309 
310 			case WID_RV_RAIL_ROAD_TYPE_DROPDOWN: {
311 				Dimension d = {0, 0};
312 				switch (this->window_number) {
313 					case VEH_TRAIN:
314 						for (RailType rt = RAILTYPE_BEGIN; rt != RAILTYPE_END; rt++) {
315 							const RailtypeInfo *rti = GetRailTypeInfo(rt);
316 							/* Skip rail type if it has no label */
317 							if (rti->label == 0) continue;
318 							d = maxdim(d, GetStringBoundingBox(rti->strings.replace_text));
319 						}
320 						break;
321 
322 					case VEH_ROAD:
323 						for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) {
324 							const RoadTypeInfo *rti = GetRoadTypeInfo(rt);
325 							/* Skip road type if it has no label */
326 							if (rti->label == 0) continue;
327 							d = maxdim(d, GetStringBoundingBox(rti->strings.replace_text));
328 						}
329 						break;
330 
331 					default: NOT_REACHED();
332 				}
333 				d.width += padding.width;
334 				d.height += padding.height;
335 				*size = maxdim(*size, d);
336 				break;
337 			}
338 
339 			case WID_RV_START_REPLACE: {
340 				Dimension d = GetStringBoundingBox(STR_REPLACE_VEHICLES_START);
341 				for (int i = 0; _start_replace_dropdown[i] != INVALID_STRING_ID; i++) {
342 					d = maxdim(d, GetStringBoundingBox(_start_replace_dropdown[i]));
343 				}
344 				d.width += padding.width;
345 				d.height += padding.height;
346 				*size = maxdim(*size, d);
347 				break;
348 			}
349 		}
350 	}
351 
SetStringParameters(int widget) const352 	void SetStringParameters(int widget) const override
353 	{
354 		switch (widget) {
355 			case WID_RV_CAPTION:
356 				SetDParam(0, STR_REPLACE_VEHICLE_TRAIN + this->window_number);
357 				switch (this->sel_group) {
358 					case ALL_GROUP:
359 						SetDParam(1, STR_GROUP_ALL_TRAINS + this->window_number);
360 						break;
361 
362 					case DEFAULT_GROUP:
363 						SetDParam(1, STR_GROUP_DEFAULT_TRAINS + this->window_number);
364 						break;
365 
366 					default:
367 						SetDParam(1, STR_GROUP_NAME);
368 						SetDParam(2, sel_group);
369 						break;
370 				}
371 				break;
372 
373 			case WID_RV_SORT_DROPDOWN:
374 				SetDParam(0, _engine_sort_listing[this->window_number][this->sort_criteria]);
375 				break;
376 
377 			case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: {
378 				bool remove_wagon;
379 				const Group *g = Group::GetIfValid(this->sel_group);
380 				if (g != nullptr) {
381 					remove_wagon = HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL);
382 					SetDParam(0, STR_GROUP_NAME);
383 					SetDParam(1, sel_group);
384 				} else {
385 					const Company *c = Company::Get(_local_company);
386 					remove_wagon = c->settings.renew_keep_length;
387 					SetDParam(0, STR_GROUP_DEFAULT_TRAINS + this->window_number);
388 				}
389 				SetDParam(2, remove_wagon ? STR_CONFIG_SETTING_ON : STR_CONFIG_SETTING_OFF);
390 				break;
391 			}
392 
393 			case WID_RV_TRAIN_ENGINEWAGON_DROPDOWN:
394 				SetDParam(0, this->replace_engines ? STR_REPLACE_ENGINES : STR_REPLACE_WAGONS);
395 				break;
396 		}
397 	}
398 
DrawWidget(const Rect & r,int widget) const399 	void DrawWidget(const Rect &r, int widget) const override
400 	{
401 		switch (widget) {
402 			case WID_RV_SORT_ASCENDING_DESCENDING:
403 				this->DrawSortButtonState(WID_RV_SORT_ASCENDING_DESCENDING, this->descending_sort_order ? SBS_DOWN : SBS_UP);
404 				break;
405 
406 			case WID_RV_INFO_TAB: {
407 				const Company *c = Company::Get(_local_company);
408 				StringID str;
409 				if (this->sel_engine[0] != INVALID_ENGINE) {
410 					if (!EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group)) {
411 						str = STR_REPLACE_NOT_REPLACING;
412 					} else {
413 						bool when_old = false;
414 						EngineID e = EngineReplacementForCompany(c, this->sel_engine[0], this->sel_group, &when_old);
415 						str = when_old ? STR_REPLACE_REPLACING_WHEN_OLD : STR_ENGINE_NAME;
416 						SetDParam(0, e);
417 					}
418 				} else {
419 					str = STR_REPLACE_NOT_REPLACING_VEHICLE_SELECTED;
420 				}
421 
422 				DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_BLACK, SA_HOR_CENTER);
423 				break;
424 			}
425 
426 			case WID_RV_LEFT_MATRIX:
427 			case WID_RV_RIGHT_MATRIX: {
428 				int side = (widget == WID_RV_LEFT_MATRIX) ? 0 : 1;
429 				EngineID start  = static_cast<EngineID>(this->vscroll[side]->GetPosition()); // what is the offset for the start (scrolling)
430 				EngineID end    = static_cast<EngineID>(std::min<size_t>(this->vscroll[side]->GetCapacity() + start, this->engines[side].size()));
431 
432 				/* Do the actual drawing */
433 				DrawEngineList((VehicleType)this->window_number, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP,
434 						&this->engines[side], start, end, this->sel_engine[side], side == 0, this->sel_group);
435 				break;
436 			}
437 		}
438 	}
439 
OnPaint()440 	void OnPaint() override
441 	{
442 		if (this->engines[0].NeedRebuild() || this->engines[1].NeedRebuild()) this->GenerateLists();
443 
444 		Company *c = Company::Get(_local_company);
445 
446 		/* Disable the "Start Replacing" button if:
447 		 *    Either engines list is empty
448 		 * or The selected replacement engine has a replacement (to prevent loops). */
449 		this->SetWidgetDisabledState(WID_RV_START_REPLACE,
450 				this->sel_engine[0] == INVALID_ENGINE || this->sel_engine[1] == INVALID_ENGINE || EngineReplacementForCompany(c, this->sel_engine[1], this->sel_group) != INVALID_ENGINE);
451 
452 		/* Disable the "Stop Replacing" button if:
453 		 *   The left engines list (existing vehicle) is empty
454 		 *   or The selected vehicle has no replacement set up */
455 		this->SetWidgetDisabledState(WID_RV_STOP_REPLACE, this->sel_engine[0] == INVALID_ENGINE || !EngineHasReplacementForCompany(c, this->sel_engine[0], this->sel_group));
456 
457 		switch (this->window_number) {
458 			case VEH_TRAIN:
459 				/* Show the selected railtype in the pulldown menu */
460 				this->GetWidget<NWidgetCore>(WID_RV_RAIL_ROAD_TYPE_DROPDOWN)->widget_data = sel_railtype == INVALID_RAILTYPE ? STR_REPLACE_ALL_RAILTYPE : GetRailTypeInfo(sel_railtype)->strings.replace_text;
461 				break;
462 
463 			case VEH_ROAD:
464 				/* Show the selected roadtype in the pulldown menu */
465 				this->GetWidget<NWidgetCore>(WID_RV_RAIL_ROAD_TYPE_DROPDOWN)->widget_data = sel_roadtype == INVALID_ROADTYPE ? STR_REPLACE_ALL_ROADTYPE : GetRoadTypeInfo(sel_roadtype)->strings.replace_text;
466 				break;
467 
468 			default: break;
469 		}
470 
471 		this->DrawWidgets();
472 
473 		if (!this->IsShaded()) {
474 			int needed_height = this->details_height;
475 			/* Draw details panels. */
476 			for (int side = 0; side < 2; side++) {
477 				if (this->sel_engine[side] != INVALID_ENGINE) {
478 					/* Use default engine details without refitting */
479 					const Engine *e = Engine::Get(this->sel_engine[side]);
480 					TestedEngineDetails ted;
481 					ted.cost = 0;
482 					ted.cargo = e->GetDefaultCargoType();
483 					ted.capacity = e->GetDisplayDefaultCapacity(&ted.mail_capacity);
484 
485 					NWidgetBase *nwi = this->GetWidget<NWidgetBase>(side == 0 ? WID_RV_LEFT_DETAILS : WID_RV_RIGHT_DETAILS);
486 					int text_end = DrawVehiclePurchaseInfo(nwi->pos_x + WD_FRAMETEXT_LEFT, nwi->pos_x + nwi->current_x - WD_FRAMETEXT_RIGHT,
487 							nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine[side], ted);
488 					needed_height = std::max(needed_height, (text_end - (int)nwi->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL);
489 				}
490 			}
491 			if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
492 				this->details_height = needed_height;
493 				this->ReInit();
494 				return;
495 			}
496 		}
497 	}
498 
OnClick(Point pt,int widget,int click_count)499 	void OnClick(Point pt, int widget, int click_count) override
500 	{
501 		switch (widget) {
502 			case WID_RV_SORT_ASCENDING_DESCENDING:
503 				this->descending_sort_order ^= true;
504 				_engine_sort_last_order[this->window_number] = this->descending_sort_order;
505 				this->engines[1].ForceRebuild();
506 				this->SetDirty();
507 				break;
508 
509 			case WID_RV_SHOW_HIDDEN_ENGINES:
510 				this->show_hidden_engines ^= true;
511 				_engine_sort_show_hidden_engines[this->window_number] = this->show_hidden_engines;
512 				this->engines[1].ForceRebuild();
513 				this->SetWidgetLoweredState(widget, this->show_hidden_engines);
514 				this->SetDirty();
515 				break;
516 
517 			case WID_RV_SORT_DROPDOWN:
518 				DisplayVehicleSortDropDown(this, static_cast<VehicleType>(this->window_number), this->sort_criteria, WID_RV_SORT_DROPDOWN);
519 				break;
520 
521 			case WID_RV_TRAIN_ENGINEWAGON_DROPDOWN: {
522 				DropDownList list;
523 				list.emplace_back(new DropDownListStringItem(STR_REPLACE_ENGINES, 1, false));
524 				list.emplace_back(new DropDownListStringItem(STR_REPLACE_WAGONS, 0, false));
525 				ShowDropDownList(this, std::move(list), this->replace_engines ? 1 : 0, WID_RV_TRAIN_ENGINEWAGON_DROPDOWN);
526 				break;
527 			}
528 
529 			case WID_RV_RAIL_ROAD_TYPE_DROPDOWN: // Rail/roadtype selection dropdown menu
530 				switch (this->window_number) {
531 					case VEH_TRAIN:
532 						ShowDropDownList(this, GetRailTypeDropDownList(true, true), sel_railtype, WID_RV_RAIL_ROAD_TYPE_DROPDOWN);
533 						break;
534 
535 					case VEH_ROAD:
536 						ShowDropDownList(this, GetRoadTypeDropDownList(RTTB_ROAD | RTTB_TRAM, true, true), sel_roadtype, WID_RV_RAIL_ROAD_TYPE_DROPDOWN);
537 						break;
538 				}
539 				break;
540 
541 			case WID_RV_TRAIN_WAGONREMOVE_TOGGLE: {
542 				const Group *g = Group::GetIfValid(this->sel_group);
543 				if (g != nullptr) {
544 					DoCommandP(0, this->sel_group | (GroupFlags::GF_REPLACE_WAGON_REMOVAL << 16), (HasBit(g->flags, GroupFlags::GF_REPLACE_WAGON_REMOVAL) ? 0 : 1) | (_ctrl_pressed << 1), CMD_SET_GROUP_FLAG);
545 				} else {
546 					// toggle renew_keep_length
547 					DoCommandP(0, 0, Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1, CMD_CHANGE_COMPANY_SETTING, nullptr, "company.renew_keep_length");
548 				}
549 				break;
550 			}
551 
552 			case WID_RV_START_REPLACE: { // Start replacing
553 				if (this->GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
554 					this->HandleButtonClick(WID_RV_START_REPLACE);
555 					ReplaceClick_StartReplace(false);
556 				} else {
557 					bool replacment_when_old = EngineHasReplacementWhenOldForCompany(Company::Get(_local_company), this->sel_engine[0], this->sel_group);
558 					ShowDropDownMenu(this, _start_replace_dropdown, replacment_when_old ? 1 : 0, WID_RV_START_REPLACE, !this->replace_engines ? 1 << 1 : 0, 0);
559 				}
560 				break;
561 			}
562 
563 			case WID_RV_STOP_REPLACE: { // Stop replacing
564 				EngineID veh_from = this->sel_engine[0];
565 				DoCommandP(0, this->sel_group << 16, veh_from + (INVALID_ENGINE << 16), CMD_SET_AUTOREPLACE);
566 				break;
567 			}
568 
569 			case WID_RV_LEFT_MATRIX:
570 			case WID_RV_RIGHT_MATRIX: {
571 				byte click_side;
572 				if (widget == WID_RV_LEFT_MATRIX) {
573 					click_side = 0;
574 				} else {
575 					click_side = 1;
576 				}
577 				uint i = this->vscroll[click_side]->GetScrolledRowFromWidget(pt.y, this, widget);
578 				size_t engine_count = this->engines[click_side].size();
579 
580 				EngineID e = engine_count > i ? this->engines[click_side][i] : INVALID_ENGINE;
581 				if (e == this->sel_engine[click_side]) break; // we clicked the one we already selected
582 				this->sel_engine[click_side] = e;
583 				if (click_side == 0) {
584 					this->engines[1].ForceRebuild();
585 					this->reset_sel_engine = true;
586 				}
587 				this->SetDirty();
588 				break;
589 			}
590 		}
591 	}
592 
OnDropdownSelect(int widget,int index)593 	void OnDropdownSelect(int widget, int index) override
594 	{
595 		switch (widget) {
596 			case WID_RV_SORT_DROPDOWN:
597 				if (this->sort_criteria != index) {
598 					this->sort_criteria = index;
599 					_engine_sort_last_criteria[this->window_number] = this->sort_criteria;
600 					this->engines[1].ForceRebuild();
601 					this->SetDirty();
602 				}
603 				break;
604 
605 			case WID_RV_RAIL_ROAD_TYPE_DROPDOWN:
606 				switch (this->window_number) {
607 					case VEH_TRAIN: {
608 						RailType temp = (RailType)index;
609 						if (temp == sel_railtype) return; // we didn't select a new one. No need to change anything
610 						sel_railtype = temp;
611 						break;
612 					}
613 
614 					case VEH_ROAD: {
615 						RoadType temp = (RoadType)index;
616 						if (temp == sel_roadtype) return; // we didn't select a new one. No need to change anything
617 						sel_roadtype = temp;
618 						break;
619 					}
620 
621 					default: NOT_REACHED();
622 				}
623 
624 				/* Reset scrollbar positions */
625 				this->vscroll[0]->SetPosition(0);
626 				this->vscroll[1]->SetPosition(0);
627 				/* Rebuild the lists */
628 				this->engines[0].ForceRebuild();
629 				this->engines[1].ForceRebuild();
630 				this->reset_sel_engine = true;
631 				this->SetDirty();
632 				break;
633 
634 			case WID_RV_TRAIN_ENGINEWAGON_DROPDOWN: {
635 				this->replace_engines = index != 0;
636 				this->engines[0].ForceRebuild();
637 				this->reset_sel_engine = true;
638 				this->SetDirty();
639 				break;
640 			}
641 
642 			case WID_RV_START_REPLACE:
643 				this->ReplaceClick_StartReplace(index != 0);
644 				break;
645 		}
646 	}
647 
OnTooltip(Point pt,int widget,TooltipCloseCondition close_cond)648 	bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override
649 	{
650 		if (widget != WID_RV_TRAIN_WAGONREMOVE_TOGGLE) return false;
651 
652 		if (Group::IsValidID(this->sel_group)) {
653 			uint64 params[1];
654 			params[0] = STR_REPLACE_REMOVE_WAGON_HELP;
655 			GuiShowTooltips(this, STR_REPLACE_REMOVE_WAGON_GROUP_HELP, 1, params, close_cond);
656 		} else {
657 			GuiShowTooltips(this, STR_REPLACE_REMOVE_WAGON_HELP, 0, nullptr, close_cond);
658 		}
659 		return true;
660 	}
661 
OnResize()662 	void OnResize() override
663 	{
664 		this->vscroll[0]->SetCapacityFromWidget(this, WID_RV_LEFT_MATRIX);
665 		this->vscroll[1]->SetCapacityFromWidget(this, WID_RV_RIGHT_MATRIX);
666 	}
667 
668 	/**
669 	 * Some data on this window has become invalid.
670 	 * @param data Information about the changed data.
671 	 * @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.
672 	 */
OnInvalidateData(int data=0,bool gui_scope=true)673 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
674 	{
675 		if (data != 0) {
676 			/* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
677 			this->engines[0].ForceRebuild();
678 		} else {
679 			this->engines[1].ForceRebuild();
680 		}
681 	}
682 };
683 
684 static const NWidgetPart _nested_replace_rail_vehicle_widgets[] = {
685 	NWidget(NWID_HORIZONTAL),
686 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
687 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_RV_CAPTION), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
688 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
689 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
690 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
691 	EndContainer(),
692 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
693 		NWidget(WWT_PANEL, COLOUR_GREY),
694 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_VEHICLES_IN_USE, STR_REPLACE_VEHICLE_VEHICLES_IN_USE_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
695 		EndContainer(),
696 		NWidget(WWT_PANEL, COLOUR_GREY),
697 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES, STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
698 		EndContainer(),
699 	EndContainer(),
700 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
701 		NWidget(NWID_VERTICAL),
702 			NWidget(NWID_HORIZONTAL),
703 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_RAIL_ROAD_TYPE_DROPDOWN), SetMinimalSize(136, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetFill(1, 0), SetResize(1, 0),
704 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_TRAIN_ENGINEWAGON_DROPDOWN), SetDataTip(STR_BLACK_STRING, STR_REPLACE_ENGINE_WAGON_SELECT_HELP),
705 			EndContainer(),
706 			NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), EndContainer(),
707 		EndContainer(),
708 		NWidget(NWID_VERTICAL),
709 			NWidget(NWID_HORIZONTAL),
710 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 1),
711 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
712 			EndContainer(),
713 			NWidget(NWID_HORIZONTAL),
714 				NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_RV_SHOW_HIDDEN_ENGINES), SetDataTip(STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN, STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP),
715 				NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
716 			EndContainer(),
717 		EndContainer(),
718 	EndContainer(),
719 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
720 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_LEFT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_LEFT_SCROLLBAR),
721 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_LEFT_SCROLLBAR),
722 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_RIGHT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_RIGHT_SCROLLBAR),
723 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_RIGHT_SCROLLBAR),
724 	EndContainer(),
725 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
726 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
727 		NWidget(NWID_VERTICAL),
728 			NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
729 			NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_TRAIN_WAGONREMOVE_TOGGLE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_REMOVE_WAGON, STR_REPLACE_REMOVE_WAGON_HELP), SetFill(1, 0), SetResize(1, 0),
730 		EndContainer(),
731 	EndContainer(),
732 	NWidget(NWID_HORIZONTAL),
733 		NWidget(NWID_PUSHBUTTON_DROPDOWN, COLOUR_GREY, WID_RV_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON),
734 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0),
735 		EndContainer(),
736 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_STOP_REPLACE), SetMinimalSize(150, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON),
737 		NWidget(WWT_RESIZEBOX, COLOUR_GREY),
738 	EndContainer(),
739 };
740 
741 static WindowDesc _replace_rail_vehicle_desc(
742 	WDP_AUTO, "replace_vehicle_train", 500, 140,
743 	WC_REPLACE_VEHICLE, WC_NONE,
744 	WDF_CONSTRUCTION,
745 	_nested_replace_rail_vehicle_widgets, lengthof(_nested_replace_rail_vehicle_widgets)
746 );
747 
748 static const NWidgetPart _nested_replace_road_vehicle_widgets[] = {
749 	NWidget(NWID_HORIZONTAL),
750 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
751 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_RV_CAPTION), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
752 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
753 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
754 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
755 	EndContainer(),
756 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
757 		NWidget(WWT_PANEL, COLOUR_GREY),
758 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_VEHICLES_IN_USE, STR_REPLACE_VEHICLE_VEHICLES_IN_USE_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
759 		EndContainer(),
760 		NWidget(WWT_PANEL, COLOUR_GREY),
761 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES, STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
762 		EndContainer(),
763 	EndContainer(),
764 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
765 		NWidget(NWID_VERTICAL),
766 			NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_RAIL_ROAD_TYPE_DROPDOWN), SetMinimalSize(136, 12), SetDataTip(0x0, STR_REPLACE_HELP_RAILTYPE), SetFill(1, 0), SetResize(1, 0),
767 			NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), EndContainer(),
768 		EndContainer(),
769 		NWidget(NWID_VERTICAL),
770 			NWidget(NWID_HORIZONTAL),
771 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), SetFill(1, 1),
772 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
773 			EndContainer(),
774 			NWidget(NWID_HORIZONTAL),
775 				NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_RV_SHOW_HIDDEN_ENGINES), SetDataTip(STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN, STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP),
776 				NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
777 			EndContainer(),
778 		EndContainer(),
779 	EndContainer(),
780 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
781 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_LEFT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_LEFT_SCROLLBAR),
782 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_LEFT_SCROLLBAR),
783 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_RIGHT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_RIGHT_SCROLLBAR),
784 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_RIGHT_SCROLLBAR),
785 	EndContainer(),
786 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
787 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
788 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
789 	EndContainer(),
790 	NWidget(NWID_HORIZONTAL),
791 		NWidget(NWID_PUSHBUTTON_DROPDOWN, COLOUR_GREY, WID_RV_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON),
792 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0),
793 		EndContainer(),
794 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_STOP_REPLACE), SetMinimalSize(150, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON),
795 		NWidget(WWT_RESIZEBOX, COLOUR_GREY),
796 	EndContainer(),
797 };
798 
799 static WindowDesc _replace_road_vehicle_desc(
800 	WDP_AUTO, "replace_vehicle_road", 500, 140,
801 	WC_REPLACE_VEHICLE, WC_NONE,
802 	WDF_CONSTRUCTION,
803 	_nested_replace_road_vehicle_widgets, lengthof(_nested_replace_road_vehicle_widgets)
804 );
805 
806 static const NWidgetPart _nested_replace_vehicle_widgets[] = {
807 	NWidget(NWID_HORIZONTAL),
808 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
809 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_RV_CAPTION), SetMinimalSize(433, 14), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
810 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
811 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
812 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
813 	EndContainer(),
814 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
815 		NWidget(WWT_PANEL, COLOUR_GREY),
816 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_VEHICLES_IN_USE, STR_REPLACE_VEHICLE_VEHICLES_IN_USE_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
817 		EndContainer(),
818 		NWidget(WWT_PANEL, COLOUR_GREY),
819 			NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES, STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
820 		EndContainer(),
821 	EndContainer(),
822 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
823 		NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), EndContainer(),
824 		NWidget(NWID_VERTICAL),
825 			NWidget(NWID_HORIZONTAL),
826 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
827 				NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
828 			EndContainer(),
829 			NWidget(NWID_HORIZONTAL),
830 				NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_RV_SHOW_HIDDEN_ENGINES), SetDataTip(STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN, STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP),
831 				NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
832 			EndContainer(),
833 		EndContainer(),
834 	EndContainer(),
835 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
836 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_LEFT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_LEFT_SCROLLBAR),
837 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_LEFT_SCROLLBAR),
838 		NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_RIGHT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_RIGHT_SCROLLBAR),
839 		NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_RIGHT_SCROLLBAR),
840 	EndContainer(),
841 	NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
842 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(),
843 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(),
844 	EndContainer(),
845 	NWidget(NWID_HORIZONTAL),
846 		NWidget(NWID_PUSHBUTTON_DROPDOWN, COLOUR_GREY, WID_RV_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON),
847 		NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), EndContainer(),
848 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_STOP_REPLACE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON),
849 		NWidget(WWT_RESIZEBOX, COLOUR_GREY),
850 	EndContainer(),
851 };
852 
853 static WindowDesc _replace_vehicle_desc(
854 	WDP_AUTO, "replace_vehicle", 456, 118,
855 	WC_REPLACE_VEHICLE, WC_NONE,
856 	WDF_CONSTRUCTION,
857 	_nested_replace_vehicle_widgets, lengthof(_nested_replace_vehicle_widgets)
858 );
859 
860 /**
861  * Show the autoreplace configuration window for a particular group.
862  * @param id_g The group to replace the vehicles for.
863  * @param vehicletype The type of vehicles in the group.
864  */
ShowReplaceGroupVehicleWindow(GroupID id_g,VehicleType vehicletype)865 void ShowReplaceGroupVehicleWindow(GroupID id_g, VehicleType vehicletype)
866 {
867 	CloseWindowById(WC_REPLACE_VEHICLE, vehicletype);
868 	WindowDesc *desc;
869 	switch (vehicletype) {
870 		case VEH_TRAIN: desc = &_replace_rail_vehicle_desc; break;
871 		case VEH_ROAD:  desc = &_replace_road_vehicle_desc; break;
872 		default:        desc = &_replace_vehicle_desc;      break;
873 	}
874 	new ReplaceVehicleWindow(desc, vehicletype, id_g);
875 }
876