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 rail_gui.cpp %File for dealing with rail construction user interface */
9 
10 #include "stdafx.h"
11 #include "gui.h"
12 #include "window_gui.h"
13 #include "station_gui.h"
14 #include "terraform_gui.h"
15 #include "viewport_func.h"
16 #include "command_func.h"
17 #include "waypoint_func.h"
18 #include "newgrf_station.h"
19 #include "company_base.h"
20 #include "strings_func.h"
21 #include "window_func.h"
22 #include "date_func.h"
23 #include "sound_func.h"
24 #include "company_func.h"
25 #include "widgets/dropdown_type.h"
26 #include "tunnelbridge.h"
27 #include "tilehighlight_func.h"
28 #include "spritecache.h"
29 #include "core/geometry_func.hpp"
30 #include "hotkeys.h"
31 #include "engine_base.h"
32 #include "vehicle_func.h"
33 #include "zoom_func.h"
34 #include "rail_gui.h"
35 #include "querystring_gui.h"
36 #include "sortlist_type.h"
37 #include "stringfilter_type.h"
38 #include "string_func.h"
39 
40 #include "station_map.h"
41 #include "tunnelbridge_map.h"
42 
43 #include "widgets/rail_widget.h"
44 
45 #include "safeguards.h"
46 
47 
48 static RailType _cur_railtype;               ///< Rail type of the current build-rail toolbar.
49 static bool _remove_button_clicked;          ///< Flag whether 'remove' toggle-button is currently enabled
50 static DiagDirection _build_depot_direction; ///< Currently selected depot direction
51 static byte _waypoint_count = 1;             ///< Number of waypoint types
52 static byte _cur_waypoint_type;              ///< Currently selected waypoint type
53 static bool _convert_signal_button;          ///< convert signal button in the signal GUI pressed
54 static SignalVariant _cur_signal_variant;    ///< set the signal variant (for signal GUI)
55 static SignalType _cur_signal_type;          ///< set the signal type (for signal GUI)
56 
57 struct RailStationGUISettings {
58 	Axis orientation;                 ///< Currently selected rail station orientation
59 
60 	bool newstations;                 ///< Are custom station definitions available?
61 	StationClassID station_class;     ///< Currently selected custom station class (if newstations is \c true )
62 	byte station_type;                ///< %Station type within the currently selected custom station class (if newstations is \c true )
63 	byte station_count;               ///< Number of custom stations (if newstations is \c true )
64 };
65 static RailStationGUISettings _railstation; ///< Settings of the station builder GUI
66 
67 
68 static void HandleStationPlacement(TileIndex start, TileIndex end);
69 static void ShowBuildTrainDepotPicker(Window *parent);
70 static void ShowBuildWaypointPicker(Window *parent);
71 static Window *ShowStationBuilder(Window *parent);
72 static void ShowSignalBuilder(Window *parent);
73 
74 /**
75  * Check whether a station type can be build.
76  * @return true if building is allowed.
77  */
IsStationAvailable(const StationSpec * statspec)78 static bool IsStationAvailable(const StationSpec *statspec)
79 {
80 	if (statspec == nullptr || !HasBit(statspec->callback_mask, CBM_STATION_AVAIL)) return true;
81 
82 	uint16 cb_res = GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, nullptr, INVALID_TILE);
83 	if (cb_res == CALLBACK_FAILED) return true;
84 
85 	return Convert8bitBooleanCallback(statspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res);
86 }
87 
CcPlaySound_CONSTRUCTION_RAIL(const CommandCost & result,TileIndex tile,uint32 p1,uint32 p2,uint32 cmd)88 void CcPlaySound_CONSTRUCTION_RAIL(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
89 {
90 	if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
91 }
92 
GenericPlaceRail(TileIndex tile,int cmd)93 static void GenericPlaceRail(TileIndex tile, int cmd)
94 {
95 	DoCommandP(tile, _cur_railtype, cmd | (_settings_client.gui.auto_remove_signals << 3),
96 			_remove_button_clicked ?
97 			CMD_REMOVE_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
98 			CMD_BUILD_SINGLE_RAIL | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK),
99 			CcPlaySound_CONSTRUCTION_RAIL);
100 }
101 
102 /**
103  * Try to add an additional rail-track at the entrance of a depot
104  * @param tile  Tile to use for adding the rail-track
105  * @param dir   Direction to check for already present tracks
106  * @param track Track to add
107  * @see CcRailDepot()
108  */
PlaceExtraDepotRail(TileIndex tile,DiagDirection dir,Track track)109 static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track)
110 {
111 	if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return;
112 	if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return;
113 	if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return;
114 
115 	DoCommandP(tile, _cur_railtype, track | (_settings_client.gui.auto_remove_signals << 3), CMD_BUILD_SINGLE_RAIL);
116 }
117 
118 /** Additional pieces of track to add at the entrance of a depot. */
119 static const Track _place_depot_extra_track[12] = {
120 	TRACK_LEFT,  TRACK_UPPER, TRACK_UPPER, TRACK_RIGHT, // First additional track for directions 0..3
121 	TRACK_X,     TRACK_Y,     TRACK_X,     TRACK_Y,     // Second additional track
122 	TRACK_LOWER, TRACK_LEFT,  TRACK_RIGHT, TRACK_LOWER, // Third additional track
123 };
124 
125 /** Direction to check for existing track pieces. */
126 static const DiagDirection _place_depot_extra_dir[12] = {
127 	DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE, DIAGDIR_SW,
128 	DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SE,
129 	DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE,
130 };
131 
CcRailDepot(const CommandCost & result,TileIndex tile,uint32 p1,uint32 p2,uint32 cmd)132 void CcRailDepot(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
133 {
134 	if (result.Failed()) return;
135 
136 	DiagDirection dir = (DiagDirection)p2;
137 
138 	if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
139 	if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
140 
141 	tile += TileOffsByDiagDir(dir);
142 
143 	if (IsTileType(tile, MP_RAILWAY)) {
144 		PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]);
145 		PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]);
146 		PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]);
147 	}
148 }
149 
150 /**
151  * Place a rail waypoint.
152  * @param tile Position to start dragging a waypoint.
153  */
PlaceRail_Waypoint(TileIndex tile)154 static void PlaceRail_Waypoint(TileIndex tile)
155 {
156 	if (_remove_button_clicked) {
157 		VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_STATION);
158 		return;
159 	}
160 
161 	Axis axis = GetAxisForNewWaypoint(tile);
162 	if (IsValidAxis(axis)) {
163 		/* Valid tile for waypoints */
164 		VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_STATION);
165 		VpSetPlaceSizingLimit(_settings_game.station.station_spread);
166 	} else {
167 		/* Tile where we can't build rail waypoints. This is always going to fail,
168 		 * but provides the user with a proper error message. */
169 		DoCommandP(tile, 1 << 8 | 1 << 16, STAT_CLASS_WAYP | INVALID_STATION << 16, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT));
170 	}
171 }
172 
CcStation(const CommandCost & result,TileIndex tile,uint32 p1,uint32 p2,uint32 cmd)173 void CcStation(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
174 {
175 	if (result.Failed()) return;
176 
177 	if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
178 	/* Only close the station builder window if the default station and non persistent building is chosen. */
179 	if (_railstation.station_class == STAT_CLASS_DFLT && _railstation.station_type == 0 && !_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
180 }
181 
182 /**
183  * Place a rail station.
184  * @param tile Position to place or start dragging a station.
185  */
PlaceRail_Station(TileIndex tile)186 static void PlaceRail_Station(TileIndex tile)
187 {
188 	if (_remove_button_clicked) {
189 		VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_REMOVE_STATION);
190 		VpSetPlaceSizingLimit(-1);
191 	} else if (_settings_client.gui.station_dragdrop) {
192 		VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_STATION);
193 		VpSetPlaceSizingLimit(_settings_game.station.station_spread);
194 	} else {
195 		uint32 p1 = _cur_railtype | _railstation.orientation << 6 | _settings_client.gui.station_numtracks << 8 | _settings_client.gui.station_platlength << 16 | _ctrl_pressed << 24;
196 		uint32 p2 = _railstation.station_class | _railstation.station_type << 8 | INVALID_STATION << 16;
197 
198 		int w = _settings_client.gui.station_numtracks;
199 		int h = _settings_client.gui.station_platlength;
200 		if (!_railstation.orientation) Swap(w, h);
201 
202 		CommandContainer cmdcont = { tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
203 		ShowSelectStationIfNeeded(cmdcont, TileArea(tile, w, h));
204 	}
205 }
206 
207 /**
208  * Build a new signal or edit/remove a present signal, use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp
209  *
210  * @param tile The tile where the signal will build or edit
211  */
GenericPlaceSignals(TileIndex tile)212 static void GenericPlaceSignals(TileIndex tile)
213 {
214 	TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0));
215 
216 	if (trackbits & TRACK_BIT_VERT) { // N-S direction
217 		trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
218 	}
219 
220 	if (trackbits & TRACK_BIT_HORZ) { // E-W direction
221 		trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
222 	}
223 
224 	Track track = FindFirstTrack(trackbits);
225 
226 	if (_remove_button_clicked) {
227 		DoCommandP(tile, track, 0, CMD_REMOVE_SIGNALS | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM), CcPlaySound_CONSTRUCTION_RAIL);
228 	} else {
229 		const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
230 
231 		/* various bitstuffed elements for CmdBuildSingleSignal() */
232 		uint32 p1 = track;
233 
234 		/* Which signals should we cycle through? */
235 		uint8 cycle_types;
236 
237 		if (_settings_client.gui.cycle_signal_types == SIGNAL_CYCLE_ALL && _settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL) {
238 			cycle_types = SIGTYPE_NORMAL | (SIGTYPE_LAST << 3);
239 		} else {
240 			cycle_types = SIGTYPE_PBS | (SIGTYPE_LAST << 3);
241 		}
242 
243 		if (w != nullptr) {
244 			/* signal GUI is used */
245 			SB(p1, 3, 1, _ctrl_pressed);
246 			SB(p1, 4, 1, _cur_signal_variant);
247 			SB(p1, 5, 3, _cur_signal_type);
248 			SB(p1, 8, 1, _convert_signal_button);
249 			SB(p1, 9, 6, cycle_types);
250 		} else {
251 			SB(p1, 3, 1, _ctrl_pressed);
252 			SB(p1, 4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
253 			SB(p1, 5, 3, SIGTYPE_PBS_ONEWAY);
254 			SB(p1, 8, 1, 0);
255 			SB(p1, 9, 6, cycle_types);
256 		}
257 
258 		DoCommandP(tile, p1, 0, CMD_BUILD_SIGNALS |
259 				CMD_MSG((w != nullptr && _convert_signal_button) ? STR_ERROR_SIGNAL_CAN_T_CONVERT_SIGNALS_HERE : STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
260 				CcPlaySound_CONSTRUCTION_RAIL);
261 	}
262 }
263 
264 /**
265  * Start placing a rail bridge.
266  * @param tile Position of the first tile of the bridge.
267  * @param w    Rail toolbar window.
268  */
PlaceRail_Bridge(TileIndex tile,Window * w)269 static void PlaceRail_Bridge(TileIndex tile, Window *w)
270 {
271 	if (IsBridgeTile(tile)) {
272 		TileIndex other_tile = GetOtherTunnelBridgeEnd(tile);
273 		Point pt = {0, 0};
274 		w->OnPlaceMouseUp(VPM_X_OR_Y, DDSP_BUILD_BRIDGE, pt, other_tile, tile);
275 	} else {
276 		VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_BRIDGE);
277 	}
278 }
279 
280 /** Command callback for building a tunnel */
CcBuildRailTunnel(const CommandCost & result,TileIndex tile,uint32 p1,uint32 p2,uint32 cmd)281 void CcBuildRailTunnel(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd)
282 {
283 	if (result.Succeeded()) {
284 		if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
285 		if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
286 	} else {
287 		SetRedErrorSquare(_build_tunnel_endtile);
288 	}
289 }
290 
291 /**
292  * Toggles state of the Remove button of Build rail toolbar
293  * @param w window the button belongs to
294  */
ToggleRailButton_Remove(Window * w)295 static void ToggleRailButton_Remove(Window *w)
296 {
297 	CloseWindowById(WC_SELECT_STATION, 0);
298 	w->ToggleWidgetLoweredState(WID_RAT_REMOVE);
299 	w->SetWidgetDirty(WID_RAT_REMOVE);
300 	_remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE);
301 	SetSelectionRed(_remove_button_clicked);
302 }
303 
304 /**
305  * Updates the Remove button because of Ctrl state change
306  * @param w window the button belongs to
307  * @return true iff the remove button was changed
308  */
RailToolbar_CtrlChanged(Window * w)309 static bool RailToolbar_CtrlChanged(Window *w)
310 {
311 	if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return false;
312 
313 	/* allow ctrl to switch remove mode only for these widgets */
314 	for (uint i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) {
315 		if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) {
316 			ToggleRailButton_Remove(w);
317 			return true;
318 		}
319 	}
320 
321 	return false;
322 }
323 
324 
325 /**
326  * The "remove"-button click proc of the build-rail toolbar.
327  * @param w Build-rail toolbar window
328  * @see BuildRailToolbarWindow::OnClick()
329  */
BuildRailClick_Remove(Window * w)330 static void BuildRailClick_Remove(Window *w)
331 {
332 	if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return;
333 	ToggleRailButton_Remove(w);
334 	if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
335 
336 	/* handle station builder */
337 	if (w->IsWidgetLowered(WID_RAT_BUILD_STATION)) {
338 		if (_remove_button_clicked) {
339 			/* starting drag & drop remove */
340 			if (!_settings_client.gui.station_dragdrop) {
341 				SetTileSelectSize(1, 1);
342 			} else {
343 				VpSetPlaceSizingLimit(-1);
344 			}
345 		} else {
346 			/* starting station build mode */
347 			if (!_settings_client.gui.station_dragdrop) {
348 				int x = _settings_client.gui.station_numtracks;
349 				int y = _settings_client.gui.station_platlength;
350 				if (_railstation.orientation == 0) Swap(x, y);
351 				SetTileSelectSize(x, y);
352 			} else {
353 				VpSetPlaceSizingLimit(_settings_game.station.station_spread);
354 			}
355 		}
356 	}
357 }
358 
DoRailroadTrack(int mode)359 static void DoRailroadTrack(int mode)
360 {
361 	uint32 p2 = _cur_railtype | (mode << 6) | (_settings_client.gui.auto_remove_signals << 11);
362 	DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), p2,
363 			_remove_button_clicked ?
364 			CMD_REMOVE_RAILROAD_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK) :
365 			CMD_BUILD_RAILROAD_TRACK  | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK),
366 			CcPlaySound_CONSTRUCTION_RAIL);
367 }
368 
HandleAutodirPlacement()369 static void HandleAutodirPlacement()
370 {
371 	int trackstat = _thd.drawstyle & HT_DIR_MASK; // 0..5
372 
373 	if (_thd.drawstyle & HT_RAIL) { // one tile case
374 		GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat);
375 		return;
376 	}
377 
378 	DoRailroadTrack(trackstat);
379 }
380 
381 /**
382  * Build new signals or remove signals or (if only one tile marked) edit a signal.
383  *
384  * If one tile marked abort and use GenericPlaceSignals()
385  * else use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp to build many signals
386  */
HandleAutoSignalPlacement()387 static void HandleAutoSignalPlacement()
388 {
389 	uint32 p2 = GB(_thd.drawstyle, 0, 3); // 0..5
390 
391 	if ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT) { // one tile case
392 		GenericPlaceSignals(TileVirtXY(_thd.selend.x, _thd.selend.y));
393 		return;
394 	}
395 
396 	const Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
397 
398 	if (w != nullptr) {
399 		/* signal GUI is used */
400 		SB(p2,  3, 1, 0);
401 		SB(p2,  4, 1, _cur_signal_variant);
402 		SB(p2,  6, 1, _ctrl_pressed);
403 		SB(p2,  7, 3, _cur_signal_type);
404 		SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
405 		SB(p2, 10, 1, !_settings_client.gui.drag_signals_fixed_distance);
406 	} else {
407 		SB(p2,  3, 1, 0);
408 		SB(p2,  4, 1, (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC));
409 		SB(p2,  6, 1, _ctrl_pressed);
410 		SB(p2,  7, 3, SIGTYPE_PBS_ONEWAY);
411 		SB(p2, 24, 8, _settings_client.gui.drag_signals_density);
412 		SB(p2, 10, 1, !_settings_client.gui.drag_signals_fixed_distance);
413 	}
414 
415 	/* _settings_client.gui.drag_signals_density is given as a parameter such that each user
416 	 * in a network game can specify their own signal density */
417 	DoCommandP(TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), p2,
418 			_remove_button_clicked ?
419 			CMD_REMOVE_SIGNAL_TRACK | CMD_MSG(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM) :
420 			CMD_BUILD_SIGNAL_TRACK  | CMD_MSG(STR_ERROR_CAN_T_BUILD_SIGNALS_HERE),
421 			CcPlaySound_CONSTRUCTION_RAIL);
422 }
423 
424 
425 /** Rail toolbar management class. */
426 struct BuildRailToolbarWindow : Window {
427 	RailType railtype;    ///< Rail type to build.
428 	int last_user_action; ///< Last started user action.
429 
BuildRailToolbarWindowBuildRailToolbarWindow430 	BuildRailToolbarWindow(WindowDesc *desc, RailType railtype) : Window(desc)
431 	{
432 		this->InitNested(TRANSPORT_RAIL);
433 		this->SetupRailToolbar(railtype);
434 		this->DisableWidget(WID_RAT_REMOVE);
435 		this->last_user_action = WIDGET_LIST_END;
436 
437 		if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
438 	}
439 
CloseBuildRailToolbarWindow440 	void Close() override
441 	{
442 		if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true);
443 		if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false);
444 		this->Window::Close();
445 	}
446 
447 	/**
448 	 * Configures the rail toolbar for railtype given
449 	 * @param railtype the railtype to display
450 	 */
SetupRailToolbarBuildRailToolbarWindow451 	void SetupRailToolbar(RailType railtype)
452 	{
453 		this->railtype = railtype;
454 		const RailtypeInfo *rti = GetRailTypeInfo(railtype);
455 
456 		assert(railtype < RAILTYPE_END);
457 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_NS)->widget_data     = rti->gui_sprites.build_ns_rail;
458 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_X)->widget_data      = rti->gui_sprites.build_x_rail;
459 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_EW)->widget_data     = rti->gui_sprites.build_ew_rail;
460 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_Y)->widget_data      = rti->gui_sprites.build_y_rail;
461 		this->GetWidget<NWidgetCore>(WID_RAT_AUTORAIL)->widget_data     = rti->gui_sprites.auto_rail;
462 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_DEPOT)->widget_data  = rti->gui_sprites.build_depot;
463 		this->GetWidget<NWidgetCore>(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail;
464 		this->GetWidget<NWidgetCore>(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel;
465 	}
466 
467 	/**
468 	 * Switch to another rail type.
469 	 * @param railtype New rail type.
470 	 */
ModifyRailTypeBuildRailToolbarWindow471 	void ModifyRailType(RailType railtype)
472 	{
473 		this->SetupRailToolbar(railtype);
474 		this->ReInit();
475 	}
476 
UpdateRemoveWidgetStatusBuildRailToolbarWindow477 	void UpdateRemoveWidgetStatus(int clicked_widget)
478 	{
479 		switch (clicked_widget) {
480 			case WID_RAT_REMOVE:
481 				/* If it is the removal button that has been clicked, do nothing,
482 				 * as it is up to the other buttons to drive removal status */
483 				return;
484 
485 			case WID_RAT_BUILD_NS:
486 			case WID_RAT_BUILD_X:
487 			case WID_RAT_BUILD_EW:
488 			case WID_RAT_BUILD_Y:
489 			case WID_RAT_AUTORAIL:
490 			case WID_RAT_BUILD_WAYPOINT:
491 			case WID_RAT_BUILD_STATION:
492 			case WID_RAT_BUILD_SIGNALS:
493 				/* Removal button is enabled only if the rail/signal/waypoint/station
494 				 * button is still lowered.  Once raised, it has to be disabled */
495 				this->SetWidgetDisabledState(WID_RAT_REMOVE, !this->IsWidgetLowered(clicked_widget));
496 				break;
497 
498 			default:
499 				/* When any other buttons than rail/signal/waypoint/station, raise and
500 				 * disable the removal button */
501 				this->DisableWidget(WID_RAT_REMOVE);
502 				this->RaiseWidget(WID_RAT_REMOVE);
503 				break;
504 		}
505 	}
506 
SetStringParametersBuildRailToolbarWindow507 	void SetStringParameters(int widget) const override
508 	{
509 		if (widget == WID_RAT_CAPTION) {
510 			const RailtypeInfo *rti = GetRailTypeInfo(this->railtype);
511 			if (rti->max_speed > 0) {
512 				SetDParam(0, STR_TOOLBAR_RAILTYPE_VELOCITY);
513 				SetDParam(1, rti->strings.toolbar_caption);
514 				SetDParam(2, rti->max_speed);
515 			} else {
516 				SetDParam(0, rti->strings.toolbar_caption);
517 			}
518 		}
519 	}
520 
OnClickBuildRailToolbarWindow521 	void OnClick(Point pt, int widget, int click_count) override
522 	{
523 		if (widget < WID_RAT_BUILD_NS) return;
524 
525 		_remove_button_clicked = false;
526 		switch (widget) {
527 			case WID_RAT_BUILD_NS:
528 				HandlePlacePushButton(this, WID_RAT_BUILD_NS, GetRailTypeInfo(_cur_railtype)->cursor.rail_ns, HT_LINE | HT_DIR_VL);
529 				this->last_user_action = widget;
530 				break;
531 
532 			case WID_RAT_BUILD_X:
533 				HandlePlacePushButton(this, WID_RAT_BUILD_X, GetRailTypeInfo(_cur_railtype)->cursor.rail_swne, HT_LINE | HT_DIR_X);
534 				this->last_user_action = widget;
535 				break;
536 
537 			case WID_RAT_BUILD_EW:
538 				HandlePlacePushButton(this, WID_RAT_BUILD_EW, GetRailTypeInfo(_cur_railtype)->cursor.rail_ew, HT_LINE | HT_DIR_HL);
539 				this->last_user_action = widget;
540 				break;
541 
542 			case WID_RAT_BUILD_Y:
543 				HandlePlacePushButton(this, WID_RAT_BUILD_Y, GetRailTypeInfo(_cur_railtype)->cursor.rail_nwse, HT_LINE | HT_DIR_Y);
544 				this->last_user_action = widget;
545 				break;
546 
547 			case WID_RAT_AUTORAIL:
548 				HandlePlacePushButton(this, WID_RAT_AUTORAIL, GetRailTypeInfo(_cur_railtype)->cursor.autorail, HT_RAIL);
549 				this->last_user_action = widget;
550 				break;
551 
552 			case WID_RAT_DEMOLISH:
553 				HandlePlacePushButton(this, WID_RAT_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT | HT_DIAGONAL);
554 				this->last_user_action = widget;
555 				break;
556 
557 			case WID_RAT_BUILD_DEPOT:
558 				if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) {
559 					ShowBuildTrainDepotPicker(this);
560 					this->last_user_action = widget;
561 				}
562 				break;
563 
564 			case WID_RAT_BUILD_WAYPOINT:
565 				this->last_user_action = widget;
566 				_waypoint_count = StationClass::Get(STAT_CLASS_WAYP)->GetSpecCount();
567 				if (HandlePlacePushButton(this, WID_RAT_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT) && _waypoint_count > 1) {
568 					ShowBuildWaypointPicker(this);
569 				}
570 				break;
571 
572 			case WID_RAT_BUILD_STATION:
573 				if (HandlePlacePushButton(this, WID_RAT_BUILD_STATION, SPR_CURSOR_RAIL_STATION, HT_RECT)) {
574 					ShowStationBuilder(this);
575 					this->last_user_action = widget;
576 				}
577 				break;
578 
579 			case WID_RAT_BUILD_SIGNALS: {
580 				this->last_user_action = widget;
581 				bool started = HandlePlacePushButton(this, WID_RAT_BUILD_SIGNALS, ANIMCURSOR_BUILDSIGNALS, HT_RECT);
582 				if (started != _ctrl_pressed) {
583 					ShowSignalBuilder(this);
584 				}
585 				break;
586 			}
587 
588 			case WID_RAT_BUILD_BRIDGE:
589 				HandlePlacePushButton(this, WID_RAT_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT);
590 				this->last_user_action = widget;
591 				break;
592 
593 			case WID_RAT_BUILD_TUNNEL:
594 				HandlePlacePushButton(this, WID_RAT_BUILD_TUNNEL, GetRailTypeInfo(_cur_railtype)->cursor.tunnel, HT_SPECIAL);
595 				this->last_user_action = widget;
596 				break;
597 
598 			case WID_RAT_REMOVE:
599 				BuildRailClick_Remove(this);
600 				break;
601 
602 			case WID_RAT_CONVERT_RAIL:
603 				HandlePlacePushButton(this, WID_RAT_CONVERT_RAIL, GetRailTypeInfo(_cur_railtype)->cursor.convert, HT_RECT | HT_DIAGONAL);
604 				this->last_user_action = widget;
605 				break;
606 
607 			default: NOT_REACHED();
608 		}
609 		this->UpdateRemoveWidgetStatus(widget);
610 		if (_ctrl_pressed) RailToolbar_CtrlChanged(this);
611 	}
612 
OnHotkeyBuildRailToolbarWindow613 	EventState OnHotkey(int hotkey) override
614 	{
615 		MarkTileDirtyByTile(TileVirtXY(_thd.pos.x, _thd.pos.y)); // redraw tile selection
616 		return Window::OnHotkey(hotkey);
617 	}
618 
OnPlaceObjectBuildRailToolbarWindow619 	void OnPlaceObject(Point pt, TileIndex tile) override
620 	{
621 		switch (this->last_user_action) {
622 			case WID_RAT_BUILD_NS:
623 				VpStartPlaceSizing(tile, VPM_FIX_VERTICAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
624 				break;
625 
626 			case WID_RAT_BUILD_X:
627 				VpStartPlaceSizing(tile, VPM_FIX_Y | VPM_RAILDIRS, DDSP_PLACE_RAIL);
628 				break;
629 
630 			case WID_RAT_BUILD_EW:
631 				VpStartPlaceSizing(tile, VPM_FIX_HORIZONTAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
632 				break;
633 
634 			case WID_RAT_BUILD_Y:
635 				VpStartPlaceSizing(tile, VPM_FIX_X | VPM_RAILDIRS, DDSP_PLACE_RAIL);
636 				break;
637 
638 			case WID_RAT_AUTORAIL:
639 				VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL);
640 				break;
641 
642 			case WID_RAT_DEMOLISH:
643 				PlaceProc_DemolishArea(tile);
644 				break;
645 
646 			case WID_RAT_BUILD_DEPOT:
647 				DoCommandP(tile, _cur_railtype, _build_depot_direction,
648 						CMD_BUILD_TRAIN_DEPOT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT),
649 						CcRailDepot);
650 				break;
651 
652 			case WID_RAT_BUILD_WAYPOINT:
653 				PlaceRail_Waypoint(tile);
654 				break;
655 
656 			case WID_RAT_BUILD_STATION:
657 				PlaceRail_Station(tile);
658 				break;
659 
660 			case WID_RAT_BUILD_SIGNALS:
661 				VpStartPlaceSizing(tile, VPM_SIGNALDIRS, DDSP_BUILD_SIGNALS);
662 				break;
663 
664 			case WID_RAT_BUILD_BRIDGE:
665 				PlaceRail_Bridge(tile, this);
666 				break;
667 
668 			case WID_RAT_BUILD_TUNNEL:
669 				DoCommandP(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, CMD_BUILD_TUNNEL | CMD_MSG(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE), CcBuildRailTunnel);
670 				break;
671 
672 			case WID_RAT_CONVERT_RAIL:
673 				VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CONVERT_RAIL);
674 				break;
675 
676 			default: NOT_REACHED();
677 		}
678 	}
679 
OnPlaceDragBuildRailToolbarWindow680 	void OnPlaceDrag(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt) override
681 	{
682 		/* no dragging if you have pressed the convert button */
683 		if (FindWindowById(WC_BUILD_SIGNAL, 0) != nullptr && _convert_signal_button && this->IsWidgetLowered(WID_RAT_BUILD_SIGNALS)) return;
684 
685 		VpSelectTilesWithMethod(pt.x, pt.y, select_method);
686 	}
687 
OnPlaceMouseUpBuildRailToolbarWindow688 	void OnPlaceMouseUp(ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, Point pt, TileIndex start_tile, TileIndex end_tile) override
689 	{
690 		if (pt.x != -1) {
691 			switch (select_proc) {
692 				default: NOT_REACHED();
693 				case DDSP_BUILD_BRIDGE:
694 					if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
695 					ShowBuildBridgeWindow(start_tile, end_tile, TRANSPORT_RAIL, _cur_railtype);
696 					break;
697 
698 				case DDSP_PLACE_RAIL:
699 					HandleAutodirPlacement();
700 					break;
701 
702 				case DDSP_BUILD_SIGNALS:
703 					HandleAutoSignalPlacement();
704 					break;
705 
706 				case DDSP_DEMOLISH_AREA:
707 					GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
708 					break;
709 
710 				case DDSP_CONVERT_RAIL:
711 					DoCommandP(end_tile, start_tile, _cur_railtype | (_ctrl_pressed ? 1 << 6 : 0), CMD_CONVERT_RAIL | CMD_MSG(STR_ERROR_CAN_T_CONVERT_RAIL), CcPlaySound_CONSTRUCTION_RAIL);
712 					break;
713 
714 				case DDSP_REMOVE_STATION:
715 				case DDSP_BUILD_STATION:
716 					if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) {
717 						/* Station */
718 						if (_remove_button_clicked) {
719 							DoCommandP(end_tile, start_tile, _ctrl_pressed ? 0 : 1, CMD_REMOVE_FROM_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_REMOVE_PART_OF_STATION), CcPlaySound_CONSTRUCTION_RAIL);
720 						} else {
721 							HandleStationPlacement(start_tile, end_tile);
722 						}
723 					} else {
724 						/* Waypoint */
725 						if (_remove_button_clicked) {
726 							DoCommandP(end_tile, start_tile, _ctrl_pressed ? 0 : 1, CMD_REMOVE_FROM_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_REMOVE_TRAIN_WAYPOINT), CcPlaySound_CONSTRUCTION_RAIL);
727 						} else {
728 							TileArea ta(start_tile, end_tile);
729 							uint32 p1 = _cur_railtype | (select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y) << 6 | ta.w << 8 | ta.h << 16 | _ctrl_pressed << 24;
730 							uint32 p2 = STAT_CLASS_WAYP | _cur_waypoint_type << 8 | INVALID_STATION << 16;
731 
732 							CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_WAYPOINT | CMD_MSG(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT), CcPlaySound_CONSTRUCTION_RAIL, "" };
733 							ShowSelectWaypointIfNeeded(cmdcont, ta);
734 						}
735 					}
736 					break;
737 			}
738 		}
739 	}
740 
OnPlaceObjectAbortBuildRailToolbarWindow741 	void OnPlaceObjectAbort() override
742 	{
743 		if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true);
744 
745 		this->RaiseButtons();
746 		this->DisableWidget(WID_RAT_REMOVE);
747 		this->SetWidgetDirty(WID_RAT_REMOVE);
748 
749 		CloseWindowById(WC_BUILD_SIGNAL, TRANSPORT_RAIL);
750 		CloseWindowById(WC_BUILD_STATION, TRANSPORT_RAIL);
751 		CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
752 		CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL);
753 		CloseWindowById(WC_SELECT_STATION, 0);
754 		CloseWindowByClass(WC_BUILD_BRIDGE);
755 	}
756 
OnPlacePresizeBuildRailToolbarWindow757 	void OnPlacePresize(Point pt, TileIndex tile) override
758 	{
759 		DoCommand(tile, _cur_railtype | (TRANSPORT_RAIL << 8), 0, DC_AUTO, CMD_BUILD_TUNNEL);
760 		VpSetPresizeRange(tile, _build_tunnel_endtile == 0 ? tile : _build_tunnel_endtile);
761 	}
762 
OnCTRLStateChangeBuildRailToolbarWindow763 	EventState OnCTRLStateChange() override
764 	{
765 		/* do not toggle Remove button by Ctrl when placing station */
766 		if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED;
767 		return ES_NOT_HANDLED;
768 	}
769 
770 	static HotkeyList hotkeys;
771 };
772 
773 /**
774  * Handler for global hotkeys of the BuildRailToolbarWindow.
775  * @param hotkey Hotkey
776  * @return ES_HANDLED if hotkey was accepted.
777  */
RailToolbarGlobalHotkeys(int hotkey)778 static EventState RailToolbarGlobalHotkeys(int hotkey)
779 {
780 	if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED;
781 	extern RailType _last_built_railtype;
782 	Window *w = ShowBuildRailToolbar(_last_built_railtype);
783 	if (w == nullptr) return ES_NOT_HANDLED;
784 	return w->OnHotkey(hotkey);
785 }
786 
787 const uint16 _railtoolbar_autorail_keys[] = {'5', 'A' | WKC_GLOBAL_HOTKEY, 0};
788 
789 static Hotkey railtoolbar_hotkeys[] = {
790 	Hotkey('1', "build_ns", WID_RAT_BUILD_NS),
791 	Hotkey('2', "build_x", WID_RAT_BUILD_X),
792 	Hotkey('3', "build_ew", WID_RAT_BUILD_EW),
793 	Hotkey('4', "build_y", WID_RAT_BUILD_Y),
794 	Hotkey(_railtoolbar_autorail_keys, "autorail", WID_RAT_AUTORAIL),
795 	Hotkey('6', "demolish", WID_RAT_DEMOLISH),
796 	Hotkey('7', "depot", WID_RAT_BUILD_DEPOT),
797 	Hotkey('8', "waypoint", WID_RAT_BUILD_WAYPOINT),
798 	Hotkey('9', "station", WID_RAT_BUILD_STATION),
799 	Hotkey('S', "signal", WID_RAT_BUILD_SIGNALS),
800 	Hotkey('B', "bridge", WID_RAT_BUILD_BRIDGE),
801 	Hotkey('T', "tunnel", WID_RAT_BUILD_TUNNEL),
802 	Hotkey('R', "remove", WID_RAT_REMOVE),
803 	Hotkey('C', "convert", WID_RAT_CONVERT_RAIL),
804 	HOTKEY_LIST_END
805 };
806 HotkeyList BuildRailToolbarWindow::hotkeys("railtoolbar", railtoolbar_hotkeys, RailToolbarGlobalHotkeys);
807 
808 static const NWidgetPart _nested_build_rail_widgets[] = {
809 	NWidget(NWID_HORIZONTAL),
810 		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
811 		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_RAT_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
812 		NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
813 	EndContainer(),
814 	NWidget(NWID_HORIZONTAL),
815 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_NS),
816 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
817 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_X),
818 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
819 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_EW),
820 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_EW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
821 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_Y),
822 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
823 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_AUTORAIL),
824 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL),
825 
826 		NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), SetDataTip(0x0, STR_NULL), EndContainer(),
827 
828 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_DEMOLISH),
829 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
830 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT),
831 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING),
832 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_WAYPOINT),
833 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT),
834 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_STATION),
835 						SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION),
836 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_SIGNALS),
837 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_SIGNALS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS),
838 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_BRIDGE),
839 						SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_BRIDGE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_BRIDGE),
840 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_TUNNEL),
841 						SetFill(0, 1), SetMinimalSize(20, 22), SetDataTip(SPR_IMG_TUNNEL_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TUNNEL),
842 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_REMOVE),
843 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_RAIL_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR),
844 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_CONVERT_RAIL),
845 						SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_CONVERT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL),
846 	EndContainer(),
847 };
848 
849 static WindowDesc _build_rail_desc(
850 	WDP_ALIGN_TOOLBAR, "toolbar_rail", 0, 0,
851 	WC_BUILD_TOOLBAR, WC_NONE,
852 	WDF_CONSTRUCTION,
853 	_nested_build_rail_widgets, lengthof(_nested_build_rail_widgets),
854 	&BuildRailToolbarWindow::hotkeys
855 );
856 
857 
858 /**
859  * Open the build rail toolbar window for a specific rail type.
860  *
861  * If the terraform toolbar is linked to the toolbar, that window is also opened.
862  *
863  * @param railtype Rail type to open the window for
864  * @return newly opened rail toolbar, or nullptr if the toolbar could not be opened.
865  */
ShowBuildRailToolbar(RailType railtype)866 Window *ShowBuildRailToolbar(RailType railtype)
867 {
868 	if (!Company::IsValidID(_local_company)) return nullptr;
869 	if (!ValParamRailtype(railtype)) return nullptr;
870 
871 	CloseWindowByClass(WC_BUILD_TOOLBAR);
872 	_cur_railtype = railtype;
873 	_remove_button_clicked = false;
874 	return new BuildRailToolbarWindow(&_build_rail_desc, railtype);
875 }
876 
877 /* TODO: For custom stations, respect their allowed platforms/lengths bitmasks!
878  * --pasky */
879 
HandleStationPlacement(TileIndex start,TileIndex end)880 static void HandleStationPlacement(TileIndex start, TileIndex end)
881 {
882 	TileArea ta(start, end);
883 	uint numtracks = ta.w;
884 	uint platlength = ta.h;
885 
886 	if (_railstation.orientation == AXIS_X) Swap(numtracks, platlength);
887 
888 	uint32 p1 = _cur_railtype | _railstation.orientation << 6 | numtracks << 8 | platlength << 16 | _ctrl_pressed << 24;
889 	uint32 p2 = _railstation.station_class | _railstation.station_type << 8 | INVALID_STATION << 16;
890 
891 	CommandContainer cmdcont = { ta.tile, p1, p2, CMD_BUILD_RAIL_STATION | CMD_MSG(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION), CcStation, "" };
892 	ShowSelectStationIfNeeded(cmdcont, ta);
893 }
894 
895 /** Enum referring to the Hotkeys in the build rail station window */
896 enum BuildRalStationHotkeys {
897 	BRASHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
898 };
899 
900 struct BuildRailStationWindow : public PickerWindowBase {
901 private:
902 	uint line_height;     ///< Height of a single line in the newstation selection matrix (#WID_BRAS_NEWST_LIST widget).
903 	uint coverage_height; ///< Height of the coverage texts.
904 	Scrollbar *vscroll;   ///< Vertical scrollbar of the new station list.
905 	Scrollbar *vscroll2;  ///< Vertical scrollbar of the matrix with new stations.
906 
907 	typedef GUIList<StationClassID, StringFilter &> GUIStationClassList; ///< Type definition for the list to hold available station classes.
908 
909 	static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box.
910 
911 	static Listing   last_sorting;           ///< Default sorting of #GUIStationClassList.
912 	static Filtering last_filtering;         ///< Default filtering of #GUIStationClassList.
913 	static GUIStationClassList::SortFunction * const sorter_funcs[];   ///< Sort functions of the #GUIStationClassList.
914 	static GUIStationClassList::FilterFunction * const filter_funcs[]; ///< Filter functions of the #GUIStationClassList.
915 	GUIStationClassList station_classes;     ///< Available station classes.
916 	StringFilter string_filter;              ///< Filter for available station classes.
917 	QueryString filter_editbox;              ///< Filter editbox.
918 
919 	/**
920 	 * Scrolls #WID_BRAS_NEWST_SCROLL so that the selected station class is visible.
921 	 *
922 	 * Note that this method should be called shortly after SelectClassAndStation() which will ensure
923 	 * an actual existing station class is selected, or the one at position 0 which will always be
924 	 * the default TTD rail station.
925 	 */
EnsureSelectedStationClassIsVisibleBuildRailStationWindow926 	void EnsureSelectedStationClassIsVisible()
927 	{
928 		uint pos = 0;
929 		for (auto station_class : this->station_classes) {
930 			if (station_class == _railstation.station_class) break;
931 			pos++;
932 		}
933 		this->vscroll->SetCount((int)this->station_classes.size());
934 		this->vscroll->ScrollTowards(pos);
935 	}
936 
937 	/**
938 	 * Verify whether the currently selected station size is allowed after selecting a new station class/type.
939 	 * If not, change the station size variables ( _settings_client.gui.station_numtracks and _settings_client.gui.station_platlength ).
940 	 * @param statspec Specification of the new station class/type
941 	 */
CheckSelectedSizeBuildRailStationWindow942 	void CheckSelectedSize(const StationSpec *statspec)
943 	{
944 		if (statspec == nullptr || _settings_client.gui.station_dragdrop) return;
945 
946 		/* If current number of tracks is not allowed, make it as big as possible */
947 		if (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
948 			this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
949 			_settings_client.gui.station_numtracks = 1;
950 			if (statspec->disallowed_platforms != UINT8_MAX) {
951 				while (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
952 					_settings_client.gui.station_numtracks++;
953 				}
954 				this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
955 			}
956 		}
957 
958 		if (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
959 			this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
960 			_settings_client.gui.station_platlength = 1;
961 			if (statspec->disallowed_lengths != UINT8_MAX) {
962 				while (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
963 					_settings_client.gui.station_platlength++;
964 				}
965 				this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
966 			}
967 		}
968 	}
969 
970 public:
BuildRailStationWindowBuildRailStationWindow971 	BuildRailStationWindow(WindowDesc *desc, Window *parent, bool newstation) : PickerWindowBase(desc, parent), filter_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE)
972 	{
973 		this->coverage_height = 2 * FONT_HEIGHT_NORMAL + 3 * WD_PAR_VSEP_NORMAL;
974 		this->vscroll = nullptr;
975 		_railstation.newstations = newstation;
976 
977 		this->CreateNestedTree();
978 		NWidgetStacked *newst_additions = this->GetWidget<NWidgetStacked>(WID_BRAS_SHOW_NEWST_ADDITIONS);
979 		newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
980 		newst_additions = this->GetWidget<NWidgetStacked>(WID_BRAS_SHOW_NEWST_MATRIX);
981 		newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
982 		newst_additions = this->GetWidget<NWidgetStacked>(WID_BRAS_SHOW_NEWST_DEFSIZE);
983 		newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
984 		newst_additions = this->GetWidget<NWidgetStacked>(WID_BRAS_SHOW_NEWST_RESIZE);
985 		newst_additions->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
986 		/* Hide the station class filter if no stations other than the default one are available. */
987 		this->GetWidget<NWidgetStacked>(WID_BRAS_FILTER_CONTAINER)->SetDisplayedPlane(newstation ? 0 : SZSP_NONE);
988 		if (newstation) {
989 			this->vscroll = this->GetScrollbar(WID_BRAS_NEWST_SCROLL);
990 			this->vscroll2 = this->GetScrollbar(WID_BRAS_MATRIX_SCROLL);
991 
992 			this->querystrings[WID_BRAS_FILTER_EDITBOX] = &this->filter_editbox;
993 			this->station_classes.SetListing(this->last_sorting);
994 			this->station_classes.SetFiltering(this->last_filtering);
995 			this->station_classes.SetSortFuncs(this->sorter_funcs);
996 			this->station_classes.SetFilterFuncs(this->filter_funcs);
997 		}
998 
999 		this->station_classes.ForceRebuild();
1000 
1001 		BuildStationClassesAvailable();
1002 		SelectClassAndStation();
1003 
1004 		this->FinishInitNested(TRANSPORT_RAIL);
1005 
1006 		this->LowerWidget(_railstation.orientation + WID_BRAS_PLATFORM_DIR_X);
1007 		if (_settings_client.gui.station_dragdrop) {
1008 			this->LowerWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1009 		} else {
1010 			this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1011 			this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1012 		}
1013 		this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
1014 		this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
1015 
1016 		if (!newstation) {
1017 			_railstation.station_class = StationClassID::STAT_CLASS_DFLT;
1018 			_railstation.station_type = 0;
1019 			this->vscroll2 = nullptr;
1020 		} else {
1021 			_railstation.station_count = StationClass::Get(_railstation.station_class)->GetSpecCount();
1022 			_railstation.station_type = std::min<int>(_railstation.station_type, _railstation.station_count - 1);
1023 
1024 			NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX);
1025 			matrix->SetScrollbar(this->vscroll2);
1026 			matrix->SetCount(_railstation.station_count);
1027 			matrix->SetClicked(_railstation.station_type);
1028 
1029 			EnsureSelectedStationClassIsVisible();
1030 		}
1031 
1032 		this->InvalidateData();
1033 	}
1034 
CloseBuildRailStationWindow1035 	void Close() override
1036 	{
1037 		CloseWindowById(WC_SELECT_STATION, 0);
1038 		this->PickerWindowBase::Close();
1039 	}
1040 
1041 	/** Sort station classes by StationClassID. */
StationClassIDSorterBuildRailStationWindow1042 	static bool StationClassIDSorter(StationClassID const &a, StationClassID const &b)
1043 	{
1044 		return a < b;
1045 	}
1046 
1047 	/** Filter station classes by class name. */
TagNameFilterBuildRailStationWindow1048 	static bool CDECL TagNameFilter(StationClassID const * sc, StringFilter &filter)
1049 	{
1050 		char buffer[DRAW_STRING_BUFFER];
1051 		GetString(buffer, StationClass::Get(*sc)->name, lastof(buffer));
1052 
1053 		filter.ResetState();
1054 		filter.AddLine(buffer);
1055 		return filter.GetState();
1056 	}
1057 
1058 	/** Builds the filter list of available station classes. */
BuildStationClassesAvailableBuildRailStationWindow1059 	void BuildStationClassesAvailable()
1060 	{
1061 		if (!this->station_classes.NeedRebuild()) return;
1062 
1063 		this->station_classes.clear();
1064 
1065 		for (uint i = 0; i < StationClass::GetClassCount(); i++) {
1066 			StationClassID station_class_id = (StationClassID)i;
1067 			if (station_class_id == StationClassID::STAT_CLASS_WAYP) {
1068 				// Skip waypoints.
1069 				continue;
1070 			}
1071 			StationClass *station_class = StationClass::Get(station_class_id);
1072 			if (station_class->GetUISpecCount() == 0) continue;
1073 			station_classes.push_back(station_class_id);
1074 		}
1075 
1076 		if (_railstation.newstations) {
1077 			this->station_classes.Filter(this->string_filter);
1078 			this->station_classes.shrink_to_fit();
1079 			this->station_classes.RebuildDone();
1080 			this->station_classes.Sort();
1081 
1082 			this->vscroll->SetCount((uint)this->station_classes.size());
1083 		}
1084 	}
1085 
1086 	/**
1087 	 * Checks if the previously selected current station class and station
1088 	 * can be shown as selected to the user when the dialog is opened.
1089 	 */
SelectClassAndStationBuildRailStationWindow1090 	void SelectClassAndStation()
1091 	{
1092 		if (_railstation.station_class == StationClassID::STAT_CLASS_DFLT) {
1093 			/* This happens during the first time the window is open during the game life cycle. */
1094 			this->SelectOtherClass(StationClassID::STAT_CLASS_DFLT);
1095 		} else {
1096 			/* Check if the previously selected station class is not available anymore as a
1097 			 * result of starting a new game without the corresponding NewGRF. */
1098 			bool available = false;
1099 			for (uint i = 0; i < StationClass::GetClassCount(); ++i) {
1100 				if ((StationClassID)i == _railstation.station_class) {
1101 					available = true;
1102 					break;
1103 				}
1104 			}
1105 
1106 			this->SelectOtherClass(available ? _railstation.station_class : StationClassID::STAT_CLASS_DFLT);
1107 		}
1108 	}
1109 
1110 	/**
1111 	 * Select the specified station class.
1112 	 * @param station_class Station class select.
1113 	 */
SelectOtherClassBuildRailStationWindow1114 	void SelectOtherClass(StationClassID station_class)
1115 	{
1116 		_railstation.station_class = station_class;
1117 	}
1118 
OnInvalidateDataBuildRailStationWindow1119 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
1120 	{
1121 		if (!gui_scope) return;
1122 
1123 		this->BuildStationClassesAvailable();
1124 	}
1125 
OnHotkeyBuildRailStationWindow1126 	EventState OnHotkey(int hotkey) override
1127 	{
1128 		switch (hotkey) {
1129 			case BRASHK_FOCUS_FILTER_BOX:
1130 				this->SetFocusedWidget(WID_BRAS_FILTER_EDITBOX);
1131 				SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
1132 				break;
1133 
1134 			default:
1135 				return ES_NOT_HANDLED;
1136 		}
1137 
1138 		return ES_HANDLED;
1139 	}
1140 
OnEditboxChangedBuildRailStationWindow1141 	void OnEditboxChanged(int wid) override
1142 	{
1143 		string_filter.SetFilterTerm(this->filter_editbox.text.buf);
1144 		this->station_classes.SetFilterState(!string_filter.IsEmpty());
1145 		this->station_classes.ForceRebuild();
1146 		this->InvalidateData();
1147 	}
1148 
OnPaintBuildRailStationWindow1149 	void OnPaint() override
1150 	{
1151 		bool newstations = _railstation.newstations;
1152 		const StationSpec *statspec = newstations ? StationClass::Get(_railstation.station_class)->GetSpec(_railstation.station_type) : nullptr;
1153 
1154 		if (_settings_client.gui.station_dragdrop) {
1155 			SetTileSelectSize(1, 1);
1156 		} else {
1157 			int x = _settings_client.gui.station_numtracks;
1158 			int y = _settings_client.gui.station_platlength;
1159 			if (_railstation.orientation == AXIS_X) Swap(x, y);
1160 			if (!_remove_button_clicked) {
1161 				SetTileSelectSize(x, y);
1162 			}
1163 		}
1164 
1165 		int rad = (_settings_game.station.modified_catchment) ? CA_TRAIN : CA_UNMODIFIED;
1166 
1167 		if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
1168 
1169 		for (uint bits = 0; bits < 7; bits++) {
1170 			bool disable = bits >= _settings_game.station.station_spread;
1171 			if (statspec == nullptr) {
1172 				this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_NUM_1, disable);
1173 				this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_LEN_1, disable);
1174 			} else {
1175 				this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_NUM_1, HasBit(statspec->disallowed_platforms, bits) || disable);
1176 				this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_LEN_1, HasBit(statspec->disallowed_lengths,   bits) || disable);
1177 			}
1178 		}
1179 
1180 		this->DrawWidgets();
1181 
1182 		if (this->IsShaded()) return;
1183 		/* 'Accepts' and 'Supplies' texts. */
1184 		NWidgetBase *cov = this->GetWidget<NWidgetBase>(WID_BRAS_COVERAGE_TEXTS);
1185 		int top = cov->pos_y + WD_PAR_VSEP_NORMAL;
1186 		int left = cov->pos_x + WD_FRAMERECT_LEFT;
1187 		int right = cov->pos_x + cov->current_x - WD_FRAMERECT_RIGHT;
1188 		int bottom = cov->pos_y + cov->current_y;
1189 		top = DrawStationCoverageAreaText(left, right, top, SCT_ALL, rad, false) + WD_PAR_VSEP_NORMAL;
1190 		top = DrawStationCoverageAreaText(left, right, top, SCT_ALL, rad, true) + WD_PAR_VSEP_NORMAL;
1191 		/* Resize background if the window is too small.
1192 		 * Never make the window smaller to avoid oscillating if the size change affects the acceptance.
1193 		 * (This is the case, if making the window bigger moves the mouse into the window.) */
1194 		if (top > bottom) {
1195 			this->coverage_height += top - bottom;
1196 			this->ReInit();
1197 		}
1198 	}
1199 
UpdateWidgetSizeBuildRailStationWindow1200 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1201 	{
1202 		switch (widget) {
1203 			case WID_BRAS_NEWST_LIST: {
1204 				Dimension d = {0, 0};
1205 				for (auto station_class : this->station_classes) {
1206 					d = maxdim(d, GetStringBoundingBox(StationClass::Get(station_class)->name));
1207 				}
1208 				size->width = std::max(size->width, d.width + padding.width);
1209 				this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
1210 				size->height = 5 * this->line_height;
1211 				resize->height = this->line_height;
1212 				break;
1213 			}
1214 
1215 			case WID_BRAS_SHOW_NEWST_TYPE: {
1216 				if (!_railstation.newstations) {
1217 					size->width = 0;
1218 					size->height = 0;
1219 					break;
1220 				}
1221 
1222 				/* If newstations exist, compute the non-zero minimal size. */
1223 				Dimension d = {0, 0};
1224 				StringID str = this->GetWidget<NWidgetCore>(widget)->widget_data;
1225 				for (auto station_class : this->station_classes) {
1226 					StationClass *stclass = StationClass::Get(station_class);
1227 					for (uint j = 0; j < stclass->GetSpecCount(); j++) {
1228 						const StationSpec *statspec = stclass->GetSpec(j);
1229 						SetDParam(0, (statspec != nullptr && statspec->name != 0) ? statspec->name : STR_STATION_CLASS_DFLT);
1230 						d = maxdim(d, GetStringBoundingBox(str));
1231 					}
1232 				}
1233 				size->width = std::max(size->width, d.width + padding.width);
1234 				break;
1235 			}
1236 
1237 			case WID_BRAS_PLATFORM_DIR_X:
1238 			case WID_BRAS_PLATFORM_DIR_Y:
1239 			case WID_BRAS_IMAGE:
1240 				size->width  = ScaleGUITrad(64) + 2;
1241 				size->height = ScaleGUITrad(58) + 2;
1242 				break;
1243 
1244 			case WID_BRAS_COVERAGE_TEXTS:
1245 				size->height = this->coverage_height;
1246 				break;
1247 
1248 			case WID_BRAS_MATRIX:
1249 				fill->height = 1;
1250 				resize->height = 1;
1251 				break;
1252 		}
1253 	}
1254 
DrawWidgetBuildRailStationWindow1255 	void DrawWidget(const Rect &r, int widget) const override
1256 	{
1257 		DrawPixelInfo tmp_dpi;
1258 
1259 		switch (GB(widget, 0, 16)) {
1260 			case WID_BRAS_PLATFORM_DIR_X:
1261 				/* Set up a clipping area for the '/' station preview */
1262 				if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
1263 					DrawPixelInfo *old_dpi = _cur_dpi;
1264 					_cur_dpi = &tmp_dpi;
1265 					int x = ScaleGUITrad(31) + 1;
1266 					int y = r.bottom - r.top - ScaleGUITrad(31);
1267 					if (!DrawStationTile(x, y, _cur_railtype, AXIS_X, _railstation.station_class, _railstation.station_type)) {
1268 						StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 2);
1269 					}
1270 					_cur_dpi = old_dpi;
1271 				}
1272 				break;
1273 
1274 			case WID_BRAS_PLATFORM_DIR_Y:
1275 				/* Set up a clipping area for the '\' station preview */
1276 				if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
1277 					DrawPixelInfo *old_dpi = _cur_dpi;
1278 					_cur_dpi = &tmp_dpi;
1279 					int x = ScaleGUITrad(31) + 1;
1280 					int y = r.bottom - r.top - ScaleGUITrad(31);
1281 					if (!DrawStationTile(x, y, _cur_railtype, AXIS_Y, _railstation.station_class, _railstation.station_type)) {
1282 						StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 3);
1283 					}
1284 					_cur_dpi = old_dpi;
1285 				}
1286 				break;
1287 
1288 			case WID_BRAS_NEWST_LIST: {
1289 				uint statclass = 0;
1290 				uint row = 0;
1291 				for (auto station_class : this->station_classes) {
1292 					if (this->vscroll->IsVisible(statclass)) {
1293 						DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, row * this->line_height + r.top + WD_MATRIX_TOP,
1294 								StationClass::Get(station_class)->name,
1295 								station_class == _railstation.station_class ? TC_WHITE : TC_BLACK);
1296 						row++;
1297 					}
1298 					statclass++;
1299 				}
1300 				break;
1301 			}
1302 
1303 			case WID_BRAS_IMAGE: {
1304 				byte type = GB(widget, 16, 16);
1305 				assert(type < _railstation.station_count);
1306 				/* Check station availability callback */
1307 				const StationSpec *statspec = StationClass::Get(_railstation.station_class)->GetSpec(type);
1308 				if (!IsStationAvailable(statspec)) {
1309 					GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER);
1310 				}
1311 
1312 				/* Set up a clipping area for the station preview. */
1313 				if (FillDrawPixelInfo(&tmp_dpi, r.left, r.top, r.right - r.left + 1, r.bottom - r.top + 1)) {
1314 					DrawPixelInfo *old_dpi = _cur_dpi;
1315 					_cur_dpi = &tmp_dpi;
1316 					int x = ScaleGUITrad(31) + 1;
1317 					int y = r.bottom - r.top - ScaleGUITrad(31);
1318 					if (!DrawStationTile(x, y, _cur_railtype, _railstation.orientation, _railstation.station_class, type)) {
1319 						StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 2 + _railstation.orientation);
1320 					}
1321 					_cur_dpi = old_dpi;
1322 				}
1323 				break;
1324 			}
1325 		}
1326 	}
1327 
OnResizeBuildRailStationWindow1328 	void OnResize() override
1329 	{
1330 		if (this->vscroll != nullptr) { // New stations available.
1331 			this->vscroll->SetCapacityFromWidget(this, WID_BRAS_NEWST_LIST);
1332 		}
1333 	}
1334 
SetStringParametersBuildRailStationWindow1335 	void SetStringParameters(int widget) const override
1336 	{
1337 		if (widget == WID_BRAS_SHOW_NEWST_TYPE) {
1338 			const StationSpec *statspec = StationClass::Get(_railstation.station_class)->GetSpec(_railstation.station_type);
1339 			SetDParam(0, (statspec != nullptr && statspec->name != 0) ? statspec->name : STR_STATION_CLASS_DFLT);
1340 		}
1341 	}
1342 
OnClickBuildRailStationWindow1343 	void OnClick(Point pt, int widget, int click_count) override
1344 	{
1345 		switch (GB(widget, 0, 16)) {
1346 			case WID_BRAS_PLATFORM_DIR_X:
1347 			case WID_BRAS_PLATFORM_DIR_Y:
1348 				this->RaiseWidget(_railstation.orientation + WID_BRAS_PLATFORM_DIR_X);
1349 				_railstation.orientation = (Axis)(widget - WID_BRAS_PLATFORM_DIR_X);
1350 				this->LowerWidget(_railstation.orientation + WID_BRAS_PLATFORM_DIR_X);
1351 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1352 				this->SetDirty();
1353 				CloseWindowById(WC_SELECT_STATION, 0);
1354 				break;
1355 
1356 			case WID_BRAS_PLATFORM_NUM_1:
1357 			case WID_BRAS_PLATFORM_NUM_2:
1358 			case WID_BRAS_PLATFORM_NUM_3:
1359 			case WID_BRAS_PLATFORM_NUM_4:
1360 			case WID_BRAS_PLATFORM_NUM_5:
1361 			case WID_BRAS_PLATFORM_NUM_6:
1362 			case WID_BRAS_PLATFORM_NUM_7: {
1363 				this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1364 				this->RaiseWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1365 
1366 				_settings_client.gui.station_numtracks = widget - WID_BRAS_PLATFORM_NUM_BEGIN;
1367 				_settings_client.gui.station_dragdrop = false;
1368 
1369 				_settings_client.gui.station_dragdrop = false;
1370 
1371 				const StationSpec *statspec = _railstation.newstations ? StationClass::Get(_railstation.station_class)->GetSpec(_railstation.station_type) : nullptr;
1372 				if (statspec != nullptr && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1373 					/* The previously selected number of platforms in invalid */
1374 					for (uint i = 0; i < 7; i++) {
1375 						if (!HasBit(statspec->disallowed_lengths, i)) {
1376 							this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1377 							_settings_client.gui.station_platlength = i + 1;
1378 							break;
1379 						}
1380 					}
1381 				}
1382 
1383 				this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1384 				this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1385 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1386 				this->SetDirty();
1387 				CloseWindowById(WC_SELECT_STATION, 0);
1388 				break;
1389 			}
1390 
1391 			case WID_BRAS_PLATFORM_LEN_1:
1392 			case WID_BRAS_PLATFORM_LEN_2:
1393 			case WID_BRAS_PLATFORM_LEN_3:
1394 			case WID_BRAS_PLATFORM_LEN_4:
1395 			case WID_BRAS_PLATFORM_LEN_5:
1396 			case WID_BRAS_PLATFORM_LEN_6:
1397 			case WID_BRAS_PLATFORM_LEN_7: {
1398 				this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1399 				this->RaiseWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1400 
1401 				_settings_client.gui.station_platlength = widget - WID_BRAS_PLATFORM_LEN_BEGIN;
1402 				_settings_client.gui.station_dragdrop = false;
1403 
1404 				_settings_client.gui.station_dragdrop = false;
1405 
1406 				const StationSpec *statspec = _railstation.newstations ? StationClass::Get(_railstation.station_class)->GetSpec(_railstation.station_type) : nullptr;
1407 				if (statspec != nullptr && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1408 					/* The previously selected number of tracks in invalid */
1409 					for (uint i = 0; i < 7; i++) {
1410 						if (!HasBit(statspec->disallowed_platforms, i)) {
1411 							this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1412 							_settings_client.gui.station_numtracks = i + 1;
1413 							break;
1414 						}
1415 					}
1416 				}
1417 
1418 				this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1419 				this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1420 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1421 				this->SetDirty();
1422 				CloseWindowById(WC_SELECT_STATION, 0);
1423 				break;
1424 			}
1425 
1426 			case WID_BRAS_PLATFORM_DRAG_N_DROP: {
1427 				_settings_client.gui.station_dragdrop ^= true;
1428 
1429 				this->ToggleWidgetLoweredState(WID_BRAS_PLATFORM_DRAG_N_DROP);
1430 
1431 				/* get the first allowed length/number of platforms */
1432 				const StationSpec *statspec = _railstation.newstations ? StationClass::Get(_railstation.station_class)->GetSpec(_railstation.station_type) : nullptr;
1433 				if (statspec != nullptr && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1434 					for (uint i = 0; i < 7; i++) {
1435 						if (!HasBit(statspec->disallowed_lengths, i)) {
1436 							this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1437 							_settings_client.gui.station_platlength = i + 1;
1438 							break;
1439 						}
1440 					}
1441 				}
1442 				if (statspec != nullptr && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1443 					for (uint i = 0; i < 7; i++) {
1444 						if (!HasBit(statspec->disallowed_platforms, i)) {
1445 							this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1446 							_settings_client.gui.station_numtracks = i + 1;
1447 							break;
1448 						}
1449 					}
1450 				}
1451 
1452 				this->SetWidgetLoweredState(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN, !_settings_client.gui.station_dragdrop);
1453 				this->SetWidgetLoweredState(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN, !_settings_client.gui.station_dragdrop);
1454 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1455 				this->SetDirty();
1456 				CloseWindowById(WC_SELECT_STATION, 0);
1457 				break;
1458 			}
1459 
1460 			case WID_BRAS_HIGHLIGHT_OFF:
1461 			case WID_BRAS_HIGHLIGHT_ON:
1462 				_settings_client.gui.station_show_coverage = (widget != WID_BRAS_HIGHLIGHT_OFF);
1463 
1464 				this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
1465 				this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
1466 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1467 				this->SetDirty();
1468 				SetViewportCatchmentStation(nullptr, true);
1469 				break;
1470 
1471 			case WID_BRAS_NEWST_LIST: {
1472 				int y = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BRAS_NEWST_LIST);
1473 				if (y >= (int)this->station_classes.size()) return;
1474 				StationClassID station_class_id = this->station_classes[y];
1475 				if (_railstation.station_class != station_class_id) {
1476 					StationClass *station_class = StationClass::Get(station_class_id);
1477 					_railstation.station_class = station_class_id;
1478 					_railstation.station_count = station_class->GetSpecCount();
1479 					_railstation.station_type  = 0;
1480 
1481 					this->CheckSelectedSize(station_class->GetSpec(_railstation.station_type));
1482 
1483 					NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX);
1484 					matrix->SetCount(_railstation.station_count);
1485 					matrix->SetClicked(_railstation.station_type);
1486 				}
1487 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1488 				this->SetDirty();
1489 				CloseWindowById(WC_SELECT_STATION, 0);
1490 				break;
1491 			}
1492 
1493 			case WID_BRAS_IMAGE: {
1494 				int y = GB(widget, 16, 16);
1495 				if (y >= _railstation.station_count) return;
1496 
1497 				/* Check station availability callback */
1498 				const StationSpec *statspec = StationClass::Get(_railstation.station_class)->GetSpec(y);
1499 				if (!IsStationAvailable(statspec)) return;
1500 
1501 				_railstation.station_type = y;
1502 
1503 				this->CheckSelectedSize(statspec);
1504 				this->GetWidget<NWidgetMatrix>(WID_BRAS_MATRIX)->SetClicked(_railstation.station_type);
1505 
1506 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1507 				this->SetDirty();
1508 				CloseWindowById(WC_SELECT_STATION, 0);
1509 				break;
1510 			}
1511 		}
1512 	}
1513 
OnRealtimeTickBuildRailStationWindow1514 	void OnRealtimeTick(uint delta_ms) override
1515 	{
1516 		CheckRedrawStationCoverage(this);
1517 	}
1518 
1519 	static HotkeyList hotkeys;
1520 };
1521 
1522 /**
1523  * Handler for global hotkeys of the BuildRailStationWindow.
1524  * @param hotkey Hotkey
1525  * @return ES_HANDLED if hotkey was accepted.
1526  */
BuildRailStationGlobalHotkeys(int hotkey)1527 static EventState BuildRailStationGlobalHotkeys(int hotkey)
1528 {
1529 	if (_game_mode == GM_MENU) return ES_NOT_HANDLED;
1530 	Window *w = ShowStationBuilder(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
1531 	if (w == nullptr) return ES_NOT_HANDLED;
1532 	return w->OnHotkey(hotkey);
1533 }
1534 
1535 static Hotkey buildrailstation_hotkeys[] = {
1536 	Hotkey('F', "focus_filter_box", BRASHK_FOCUS_FILTER_BOX),
1537 	HOTKEY_LIST_END
1538 };
1539 HotkeyList BuildRailStationWindow::hotkeys("buildrailstation", buildrailstation_hotkeys, BuildRailStationGlobalHotkeys);
1540 
1541 Listing BuildRailStationWindow::last_sorting = { false, 0 };
1542 Filtering BuildRailStationWindow::last_filtering = { false, 0 };
1543 
1544 BuildRailStationWindow::GUIStationClassList::SortFunction * const BuildRailStationWindow::sorter_funcs[] = {
1545 	&StationClassIDSorter,
1546 };
1547 
1548 BuildRailStationWindow::GUIStationClassList::FilterFunction * const BuildRailStationWindow::filter_funcs[] = {
1549 	&TagNameFilter,
1550 };
1551 
1552 static const NWidgetPart _nested_station_builder_widgets[] = {
1553 	NWidget(NWID_HORIZONTAL),
1554 		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1555 		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_RAIL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1556 		NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
1557 		NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_SHOW_NEWST_DEFSIZE),
1558 			NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
1559 		EndContainer(),
1560 	EndContainer(),
1561 	NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
1562 		NWidget(NWID_HORIZONTAL), SetPadding(2, 0, 0, 2),
1563 			NWidget(NWID_VERTICAL),
1564 				NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_FILTER_CONTAINER),
1565 					NWidget(NWID_HORIZONTAL), SetPadding(0, 5, 2, 0),
1566 						NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
1567 						NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BRAS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0),
1568 								SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1569 					EndContainer(),
1570 				EndContainer(),
1571 				NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_SHOW_NEWST_ADDITIONS),
1572 					NWidget(NWID_HORIZONTAL), SetPadding(0, 5, 2, 0),
1573 						NWidget(WWT_MATRIX, COLOUR_GREY, WID_BRAS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
1574 								SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BRAS_NEWST_SCROLL),
1575 						NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BRAS_NEWST_SCROLL),
1576 					EndContainer(),
1577 				EndContainer(),
1578 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetPadding(1, 2, 0, 0),
1579 				NWidget(NWID_HORIZONTAL),
1580 					NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
1581 					NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAS_PLATFORM_DIR_X), SetMinimalSize(66, 60), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
1582 					NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
1583 					NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAS_PLATFORM_DIR_Y), SetMinimalSize(66, 60), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
1584 					NWidget(NWID_SPACER), SetMinimalSize(7, 0), SetFill(1, 0),
1585 				EndContainer(),
1586 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BRAS_SHOW_NEWST_TYPE), SetMinimalSize(144, 11), SetDataTip(STR_ORANGE_STRING, STR_NULL), SetPadding(1, 2, 4, 2),
1587 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_NUMBER_OF_TRACKS, STR_NULL), SetPadding(0, 2, 0, 2),
1588 				NWidget(NWID_HORIZONTAL),
1589 					NWidget(NWID_SPACER), SetFill(1, 0),
1590 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1591 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1592 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1593 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1594 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1595 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1596 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1597 					NWidget(NWID_SPACER), SetFill(1, 0),
1598 				EndContainer(),
1599 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_PLATFORM_LENGTH, STR_NULL), SetPadding(2, 2, 0, 2),
1600 				NWidget(NWID_HORIZONTAL),
1601 					NWidget(NWID_SPACER), SetFill(1, 0),
1602 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_1), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1603 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_2), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1604 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_3), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1605 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_4), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1606 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_5), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1607 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_6), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1608 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_7), SetMinimalSize(15, 12), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1609 					NWidget(NWID_SPACER), SetFill(1, 0),
1610 				EndContainer(),
1611 				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
1612 				NWidget(NWID_HORIZONTAL),
1613 					NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
1614 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_DRAG_N_DROP), SetMinimalSize(75, 12), SetDataTip(STR_STATION_BUILD_DRAG_DROP, STR_STATION_BUILD_DRAG_DROP_TOOLTIP),
1615 					NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
1616 				EndContainer(),
1617 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetPadding(3, 2, 0, 0),
1618 				NWidget(NWID_HORIZONTAL),
1619 					NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
1620 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_HIGHLIGHT_OFF), SetMinimalSize(60, 12),
1621 												SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
1622 					NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_HIGHLIGHT_ON), SetMinimalSize(60, 12),
1623 												SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
1624 					NWidget(NWID_SPACER), SetMinimalSize(2, 0), SetFill(1, 0),
1625 				EndContainer(),
1626 			EndContainer(),
1627 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_SHOW_NEWST_MATRIX),
1628 				/* We need an additional background for the matrix, as the matrix cannot handle the scrollbar due to not being an NWidgetCore. */
1629 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BRAS_MATRIX_SCROLL),
1630 					NWidget(NWID_HORIZONTAL),
1631 						NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BRAS_MATRIX), SetScrollbar(WID_BRAS_MATRIX_SCROLL), SetPIP(0, 2, 0),
1632 							NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BRAS_IMAGE), SetMinimalSize(66, 60),
1633 									SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BRAS_MATRIX_SCROLL),
1634 							EndContainer(),
1635 						EndContainer(),
1636 						NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BRAS_MATRIX_SCROLL),
1637 					EndContainer(),
1638 				EndContainer(),
1639 			EndContainer(),
1640 		EndContainer(),
1641 		NWidget(NWID_HORIZONTAL),
1642 			NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BRAS_COVERAGE_TEXTS), SetPadding(2, 5, 0, 1), SetFill(1, 1), SetResize(1, 0),
1643 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BRAS_SHOW_NEWST_RESIZE),
1644 				NWidget(NWID_VERTICAL),
1645 					NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 1), EndContainer(),
1646 					NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
1647 				EndContainer(),
1648 			EndContainer(),
1649 		EndContainer(),
1650 	EndContainer(),
1651 };
1652 
1653 /** High level window description of the station-build window (default & newGRF) */
1654 static WindowDesc _station_builder_desc(
1655 	WDP_AUTO, "build_station_rail", 350, 0,
1656 	WC_BUILD_STATION, WC_BUILD_TOOLBAR,
1657 	WDF_CONSTRUCTION,
1658 	_nested_station_builder_widgets, lengthof(_nested_station_builder_widgets),
1659 	&BuildRailStationWindow::hotkeys
1660 );
1661 
1662 /** Open station build window */
ShowStationBuilder(Window * parent)1663 static Window *ShowStationBuilder(Window *parent)
1664 {
1665 	bool newstations = StationClass::GetClassCount() > 2 || StationClass::Get(STAT_CLASS_DFLT)->GetSpecCount() != 1;
1666 	return new BuildRailStationWindow(&_station_builder_desc, parent, newstations);
1667 }
1668 
1669 struct BuildSignalWindow : public PickerWindowBase {
1670 private:
1671 	Dimension sig_sprite_size;     ///< Maximum size of signal GUI sprites.
1672 	int sig_sprite_bottom_offset;  ///< Maximum extent of signal GUI sprite from reference point towards bottom.
1673 
1674 	/**
1675 	 * Draw dynamic a signal-sprite in a button in the signal GUI
1676 	 * Draw the sprite +1px to the right and down if the button is lowered
1677 	 *
1678 	 * @param widget_index index of this widget in the window
1679 	 * @param image        the sprite to draw
1680 	 */
DrawSignalSpriteBuildSignalWindow1681 	void DrawSignalSprite(byte widget_index, SpriteID image) const
1682 	{
1683 		Point offset;
1684 		Dimension sprite_size = GetSpriteSize(image, &offset);
1685 		const NWidgetBase *widget = this->GetWidget<NWidgetBase>(widget_index);
1686 		int x = widget->pos_x - offset.x +
1687 				(widget->current_x - sprite_size.width + offset.x) / 2;  // centered
1688 		int y = widget->pos_y - sig_sprite_bottom_offset + WD_IMGBTN_TOP +
1689 				(widget->current_y - WD_IMGBTN_TOP - WD_IMGBTN_BOTTOM + sig_sprite_size.height) / 2; // aligned to bottom
1690 
1691 		DrawSprite(image, PAL_NONE,
1692 				x + this->IsWidgetLowered(widget_index),
1693 				y + this->IsWidgetLowered(widget_index));
1694 	}
1695 
1696 	/** Show or hide buttons for non-path signals in the signal GUI */
SetSignalUIModeBuildSignalWindow1697 	void SetSignalUIMode()
1698 	{
1699 		bool show_non_path_signals = (_settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL);
1700 
1701 		this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_NORM_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1702 		this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_NORM_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1703 		this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_ENTRY_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1704 		this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_ENTRY_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1705 		this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_EXIT_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1706 		this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_EXIT_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1707 		this->GetWidget<NWidgetStacked>(WID_BS_SEMAPHORE_COMBO_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1708 		this->GetWidget<NWidgetStacked>(WID_BS_ELECTRIC_COMBO_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1709 	}
1710 
1711 public:
BuildSignalWindowBuildSignalWindow1712 	BuildSignalWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent)
1713 	{
1714 		this->CreateNestedTree();
1715 		this->SetSignalUIMode();
1716 		this->FinishInitNested(TRANSPORT_RAIL);
1717 		this->OnInvalidateData();
1718 	}
1719 
CloseBuildSignalWindow1720 	void Close() override
1721 	{
1722 		_convert_signal_button = false;
1723 		this->PickerWindowBase::Close();
1724 	}
1725 
OnInitBuildSignalWindow1726 	void OnInit() override
1727 	{
1728 		/* Calculate maximum signal sprite size. */
1729 		this->sig_sprite_size.width = 0;
1730 		this->sig_sprite_size.height = 0;
1731 		this->sig_sprite_bottom_offset = 0;
1732 		const RailtypeInfo *rti = GetRailTypeInfo(_cur_railtype);
1733 		for (uint type = SIGTYPE_NORMAL; type < SIGTYPE_END; type++) {
1734 			for (uint variant = SIG_ELECTRIC; variant <= SIG_SEMAPHORE; variant++) {
1735 				for (uint lowered = 0; lowered < 2; lowered++) {
1736 					Point offset;
1737 					Dimension sprite_size = GetSpriteSize(rti->gui_sprites.signals[type][variant][lowered], &offset);
1738 					this->sig_sprite_bottom_offset = std::max<int>(this->sig_sprite_bottom_offset, sprite_size.height);
1739 					this->sig_sprite_size.width = std::max<int>(this->sig_sprite_size.width, sprite_size.width - offset.x);
1740 					this->sig_sprite_size.height = std::max<int>(this->sig_sprite_size.height, sprite_size.height - offset.y);
1741 				}
1742 			}
1743 		}
1744 	}
1745 
UpdateWidgetSizeBuildSignalWindow1746 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1747 	{
1748 		if (widget == WID_BS_DRAG_SIGNALS_DENSITY_LABEL) {
1749 			/* Two digits for signals density. */
1750 			size->width = std::max(size->width, 2 * GetDigitWidth() + padding.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT);
1751 		} else if (IsInsideMM(widget, WID_BS_SEMAPHORE_NORM, WID_BS_ELECTRIC_PBS_OWAY + 1)) {
1752 			size->width = std::max(size->width, this->sig_sprite_size.width + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT);
1753 			size->height = std::max(size->height, this->sig_sprite_size.height + WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
1754 		} else if (widget == WID_BS_CAPTION) {
1755 			size->width += WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
1756 		}
1757 	}
1758 
SetStringParametersBuildSignalWindow1759 	void SetStringParameters(int widget) const override
1760 	{
1761 		switch (widget) {
1762 			case WID_BS_DRAG_SIGNALS_DENSITY_LABEL:
1763 				SetDParam(0, _settings_client.gui.drag_signals_density);
1764 				break;
1765 		}
1766 	}
1767 
DrawWidgetBuildSignalWindow1768 	void DrawWidget(const Rect &r, int widget) const override
1769 	{
1770 		if (IsInsideMM(widget, WID_BS_SEMAPHORE_NORM, WID_BS_ELECTRIC_PBS_OWAY + 1)) {
1771 			/* Extract signal from widget number. */
1772 			int type = (widget - WID_BS_SEMAPHORE_NORM) % SIGTYPE_END;
1773 			int var = SIG_SEMAPHORE - (widget - WID_BS_SEMAPHORE_NORM) / SIGTYPE_END; // SignalVariant order is reversed compared to the widgets.
1774 			SpriteID sprite = GetRailTypeInfo(_cur_railtype)->gui_sprites.signals[type][var][this->IsWidgetLowered(widget)];
1775 
1776 			this->DrawSignalSprite(widget, sprite);
1777 		}
1778 	}
1779 
OnClickBuildSignalWindow1780 	void OnClick(Point pt, int widget, int click_count) override
1781 	{
1782 		switch (widget) {
1783 			case WID_BS_SEMAPHORE_NORM:
1784 			case WID_BS_SEMAPHORE_ENTRY:
1785 			case WID_BS_SEMAPHORE_EXIT:
1786 			case WID_BS_SEMAPHORE_COMBO:
1787 			case WID_BS_SEMAPHORE_PBS:
1788 			case WID_BS_SEMAPHORE_PBS_OWAY:
1789 			case WID_BS_ELECTRIC_NORM:
1790 			case WID_BS_ELECTRIC_ENTRY:
1791 			case WID_BS_ELECTRIC_EXIT:
1792 			case WID_BS_ELECTRIC_COMBO:
1793 			case WID_BS_ELECTRIC_PBS:
1794 			case WID_BS_ELECTRIC_PBS_OWAY:
1795 				this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
1796 
1797 				_cur_signal_type = (SignalType)((uint)((widget - WID_BS_SEMAPHORE_NORM) % (SIGTYPE_LAST + 1)));
1798 				_cur_signal_variant = widget >= WID_BS_ELECTRIC_NORM ? SIG_ELECTRIC : SIG_SEMAPHORE;
1799 
1800 				/* If 'remove' button of rail build toolbar is active, disable it. */
1801 				if (_remove_button_clicked) {
1802 					Window *w = FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL);
1803 					if (w != nullptr) ToggleRailButton_Remove(w);
1804 				}
1805 
1806 				break;
1807 
1808 			case WID_BS_CONVERT:
1809 				_convert_signal_button = !_convert_signal_button;
1810 				break;
1811 
1812 			case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE:
1813 				if (_settings_client.gui.drag_signals_density > 1) {
1814 					_settings_client.gui.drag_signals_density--;
1815 					SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_SETTINGS);
1816 				}
1817 				break;
1818 
1819 			case WID_BS_DRAG_SIGNALS_DENSITY_INCREASE:
1820 				if (_settings_client.gui.drag_signals_density < 20) {
1821 					_settings_client.gui.drag_signals_density++;
1822 					SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_SETTINGS);
1823 				}
1824 				break;
1825 
1826 			case WID_BS_TOGGLE_SIZE:
1827 				_settings_client.gui.signal_gui_mode = (_settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL) ? SIGNAL_GUI_PATH : SIGNAL_GUI_ALL;
1828 				this->SetSignalUIMode();
1829 				this->ReInit();
1830 				break;
1831 
1832 			default: break;
1833 		}
1834 
1835 		this->InvalidateData();
1836 	}
1837 
1838 	/**
1839 	 * Some data on this window has become invalid.
1840 	 * @param data Information about the changed data.
1841 	 * @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.
1842 	 */
OnInvalidateDataBuildSignalWindow1843 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
1844 	{
1845 		if (!gui_scope) return;
1846 		this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
1847 
1848 		this->SetWidgetLoweredState(WID_BS_CONVERT, _convert_signal_button);
1849 
1850 		this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1);
1851 		this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20);
1852 	}
1853 };
1854 
1855 /** Nested widget definition of the build signal window */
1856 static const NWidgetPart _nested_signal_builder_widgets[] = {
1857 	NWidget(NWID_HORIZONTAL),
1858 		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1859 		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BS_CAPTION), SetDataTip(STR_BUILD_SIGNAL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1860 		NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_TOGGLE_SIZE), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_BUILD_SIGNAL_TOGGLE_ADVANCED_SIGNAL_TOOLTIP),
1861 	EndContainer(),
1862 	NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1863 		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1864 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_NORM_SEL),
1865 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP), EndContainer(), SetFill(1, 1),
1866 			EndContainer(),
1867 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_ENTRY_SEL),
1868 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
1869 			EndContainer(),
1870 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_EXIT_SEL),
1871 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
1872 			EndContainer(),
1873 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_SEMAPHORE_COMBO_SEL),
1874 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
1875 			EndContainer(),
1876 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
1877 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
1878 			NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(1, 1),
1879 		EndContainer(),
1880 		NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1881 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_NORM_SEL),
1882 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), EndContainer(), SetFill(1, 1),
1883 			EndContainer(),
1884 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_ENTRY_SEL),
1885 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), EndContainer(), SetFill(1, 1),
1886 			EndContainer(),
1887 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_EXIT_SEL),
1888 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), EndContainer(), SetFill(1, 1),
1889 			EndContainer(),
1890 			NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_ELECTRIC_COMBO_SEL),
1891 				NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), EndContainer(), SetFill(1, 1),
1892 			EndContainer(),
1893 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP), EndContainer(), SetFill(1, 1),
1894 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP), EndContainer(), SetFill(1, 1),
1895 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(1, 1),
1896 				NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BS_DRAG_SIGNALS_DENSITY_LABEL), SetDataTip(STR_ORANGE_INT, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(1, 1),
1897 				NWidget(NWID_HORIZONTAL), SetPIP(2, 0, 2),
1898 					NWidget(NWID_SPACER), SetFill(1, 0),
1899 					NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BS_DRAG_SIGNALS_DENSITY_DECREASE), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP),
1900 					NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BS_DRAG_SIGNALS_DENSITY_INCREASE), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP),
1901 					NWidget(NWID_SPACER), SetFill(1, 0),
1902 				EndContainer(),
1903 				NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetFill(1, 0),
1904 			EndContainer(),
1905 		EndContainer(),
1906 	EndContainer(),
1907 };
1908 
1909 /** Signal selection window description */
1910 static WindowDesc _signal_builder_desc(
1911 	WDP_AUTO, "build_signal", 0, 0,
1912 	WC_BUILD_SIGNAL, WC_BUILD_TOOLBAR,
1913 	WDF_CONSTRUCTION,
1914 	_nested_signal_builder_widgets, lengthof(_nested_signal_builder_widgets)
1915 );
1916 
1917 /**
1918  * Open the signal selection window
1919  */
ShowSignalBuilder(Window * parent)1920 static void ShowSignalBuilder(Window *parent)
1921 {
1922 	new BuildSignalWindow(&_signal_builder_desc, parent);
1923 }
1924 
1925 struct BuildRailDepotWindow : public PickerWindowBase {
BuildRailDepotWindowBuildRailDepotWindow1926 	BuildRailDepotWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent)
1927 	{
1928 		this->InitNested(TRANSPORT_RAIL);
1929 		this->LowerWidget(_build_depot_direction + WID_BRAD_DEPOT_NE);
1930 	}
1931 
UpdateWidgetSizeBuildRailDepotWindow1932 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
1933 	{
1934 		if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
1935 
1936 		size->width  = ScaleGUITrad(64) + 2;
1937 		size->height = ScaleGUITrad(48) + 2;
1938 	}
1939 
DrawWidgetBuildRailDepotWindow1940 	void DrawWidget(const Rect &r, int widget) const override
1941 	{
1942 		if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
1943 
1944 		DrawTrainDepotSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), widget - WID_BRAD_DEPOT_NE + DIAGDIR_NE, _cur_railtype);
1945 	}
1946 
OnClickBuildRailDepotWindow1947 	void OnClick(Point pt, int widget, int click_count) override
1948 	{
1949 		switch (widget) {
1950 			case WID_BRAD_DEPOT_NE:
1951 			case WID_BRAD_DEPOT_SE:
1952 			case WID_BRAD_DEPOT_SW:
1953 			case WID_BRAD_DEPOT_NW:
1954 				this->RaiseWidget(_build_depot_direction + WID_BRAD_DEPOT_NE);
1955 				_build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE);
1956 				this->LowerWidget(_build_depot_direction + WID_BRAD_DEPOT_NE);
1957 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1958 				this->SetDirty();
1959 				break;
1960 		}
1961 	}
1962 };
1963 
1964 /** Nested widget definition of the build rail depot window */
1965 static const NWidgetPart _nested_build_depot_widgets[] = {
1966 	NWidget(NWID_HORIZONTAL),
1967 		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1968 		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1969 	EndContainer(),
1970 	NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
1971 		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
1972 		NWidget(NWID_HORIZONTAL_LTR),
1973 			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
1974 			NWidget(NWID_VERTICAL),
1975 				NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAD_DEPOT_NW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1976 				EndContainer(),
1977 				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
1978 				NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAD_DEPOT_SW), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1979 				EndContainer(),
1980 			EndContainer(),
1981 			NWidget(NWID_SPACER), SetMinimalSize(2, 0),
1982 			NWidget(NWID_VERTICAL),
1983 				NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAD_DEPOT_NE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1984 				EndContainer(),
1985 				NWidget(NWID_SPACER), SetMinimalSize(0, 2),
1986 				NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAD_DEPOT_SE), SetMinimalSize(66, 50), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1987 				EndContainer(),
1988 			EndContainer(),
1989 			NWidget(NWID_SPACER), SetMinimalSize(3, 0), SetFill(1, 0),
1990 		EndContainer(),
1991 		NWidget(NWID_SPACER), SetMinimalSize(0, 3),
1992 	EndContainer(),
1993 };
1994 
1995 static WindowDesc _build_depot_desc(
1996 	WDP_AUTO, nullptr, 0, 0,
1997 	WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
1998 	WDF_CONSTRUCTION,
1999 	_nested_build_depot_widgets, lengthof(_nested_build_depot_widgets)
2000 );
2001 
ShowBuildTrainDepotPicker(Window * parent)2002 static void ShowBuildTrainDepotPicker(Window *parent)
2003 {
2004 	new BuildRailDepotWindow(&_build_depot_desc, parent);
2005 }
2006 
2007 struct BuildRailWaypointWindow : PickerWindowBase {
BuildRailWaypointWindowBuildRailWaypointWindow2008 	BuildRailWaypointWindow(WindowDesc *desc, Window *parent) : PickerWindowBase(desc, parent)
2009 	{
2010 		this->CreateNestedTree();
2011 
2012 		NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BRW_WAYPOINT_MATRIX);
2013 		matrix->SetScrollbar(this->GetScrollbar(WID_BRW_SCROLL));
2014 
2015 		this->FinishInitNested(TRANSPORT_RAIL);
2016 
2017 		matrix->SetCount(_waypoint_count);
2018 		matrix->SetClicked(_cur_waypoint_type);
2019 	}
2020 
UpdateWidgetSizeBuildRailWaypointWindow2021 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
2022 	{
2023 		switch (widget) {
2024 			case WID_BRW_WAYPOINT_MATRIX:
2025 				/* Three blobs high and wide. */
2026 				size->width  += resize->width  * 2;
2027 				size->height += resize->height * 2;
2028 
2029 				/* Resizing in X direction only at blob size, but at pixel level in Y. */
2030 				resize->height = 1;
2031 				break;
2032 
2033 			case WID_BRW_WAYPOINT:
2034 				size->width  = ScaleGUITrad(64) + 2;
2035 				size->height = ScaleGUITrad(58) + 2;
2036 				break;
2037 		}
2038 	}
2039 
DrawWidgetBuildRailWaypointWindow2040 	void DrawWidget(const Rect &r, int widget) const override
2041 	{
2042 		switch (GB(widget, 0, 16)) {
2043 			case WID_BRW_WAYPOINT: {
2044 				byte type = GB(widget, 16, 16);
2045 				const StationSpec *statspec = StationClass::Get(STAT_CLASS_WAYP)->GetSpec(type);
2046 				DrawWaypointSprite(r.left + 1 + ScaleGUITrad(31), r.bottom - ScaleGUITrad(31), type, _cur_railtype);
2047 
2048 				if (!IsStationAvailable(statspec)) {
2049 					GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER);
2050 				}
2051 			}
2052 		}
2053 	}
2054 
OnClickBuildRailWaypointWindow2055 	void OnClick(Point pt, int widget, int click_count) override
2056 	{
2057 		switch (GB(widget, 0, 16)) {
2058 			case WID_BRW_WAYPOINT: {
2059 				byte type = GB(widget, 16, 16);
2060 				this->GetWidget<NWidgetMatrix>(WID_BRW_WAYPOINT_MATRIX)->SetClicked(_cur_waypoint_type);
2061 
2062 				/* Check station availability callback */
2063 				const StationSpec *statspec = StationClass::Get(STAT_CLASS_WAYP)->GetSpec(type);
2064 				if (!IsStationAvailable(statspec)) return;
2065 
2066 				_cur_waypoint_type = type;
2067 				this->GetWidget<NWidgetMatrix>(WID_BRW_WAYPOINT_MATRIX)->SetClicked(_cur_waypoint_type);
2068 				if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
2069 				this->SetDirty();
2070 				break;
2071 			}
2072 		}
2073 	}
2074 };
2075 
2076 /** Nested widget definition for the build NewGRF rail waypoint window */
2077 static const NWidgetPart _nested_build_waypoint_widgets[] = {
2078 	NWidget(NWID_HORIZONTAL),
2079 		NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
2080 		NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_WAYPOINT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2081 		NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
2082 	EndContainer(),
2083 	NWidget(NWID_HORIZONTAL),
2084 		NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BRW_WAYPOINT_MATRIX), SetPIP(3, 2, 3), SetScrollbar(WID_BRW_SCROLL),
2085 			NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BRW_WAYPOINT), SetMinimalSize(66, 60), SetDataTip(0x0, STR_WAYPOINT_GRAPHICS_TOOLTIP), SetScrollbar(WID_BRW_SCROLL), EndContainer(),
2086 		EndContainer(),
2087 		NWidget(NWID_VERTICAL),
2088 			NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BRW_SCROLL),
2089 			NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
2090 		EndContainer(),
2091 	EndContainer(),
2092 };
2093 
2094 static WindowDesc _build_waypoint_desc(
2095 	WDP_AUTO, "build_waypoint", 0, 0,
2096 	WC_BUILD_WAYPOINT, WC_BUILD_TOOLBAR,
2097 	WDF_CONSTRUCTION,
2098 	_nested_build_waypoint_widgets, lengthof(_nested_build_waypoint_widgets)
2099 );
2100 
ShowBuildWaypointPicker(Window * parent)2101 static void ShowBuildWaypointPicker(Window *parent)
2102 {
2103 	new BuildRailWaypointWindow(&_build_waypoint_desc, parent);
2104 }
2105 
2106 /**
2107  * Initialize rail building GUI settings
2108  */
InitializeRailGui()2109 void InitializeRailGui()
2110 {
2111 	_build_depot_direction = DIAGDIR_NW;
2112 	_railstation.station_class = StationClassID::STAT_CLASS_DFLT;
2113 }
2114 
2115 /**
2116  * Re-initialize rail-build toolbar after toggling support for electric trains
2117  * @param disable Boolean whether electric trains are disabled (removed from the game)
2118  */
ReinitGuiAfterToggleElrail(bool disable)2119 void ReinitGuiAfterToggleElrail(bool disable)
2120 {
2121 	extern RailType _last_built_railtype;
2122 	if (disable && _last_built_railtype == RAILTYPE_ELECTRIC) {
2123 		_last_built_railtype = _cur_railtype = RAILTYPE_RAIL;
2124 		BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
2125 		if (w != nullptr) w->ModifyRailType(_cur_railtype);
2126 	}
2127 	MarkWholeScreenDirty();
2128 }
2129 
2130 /** Set the initial (default) railtype to use */
SetDefaultRailGui()2131 static void SetDefaultRailGui()
2132 {
2133 	if (_local_company == COMPANY_SPECTATOR || !Company::IsValidID(_local_company)) return;
2134 
2135 	extern RailType _last_built_railtype;
2136 	RailType rt;
2137 	switch (_settings_client.gui.default_rail_type) {
2138 		case 2: {
2139 			/* Find the most used rail type */
2140 			uint count[RAILTYPE_END];
2141 			memset(count, 0, sizeof(count));
2142 			for (TileIndex t = 0; t < MapSize(); t++) {
2143 				if (IsTileType(t, MP_RAILWAY) || IsLevelCrossingTile(t) || HasStationTileRail(t) ||
2144 						(IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL)) {
2145 					count[GetRailType(t)]++;
2146 				}
2147 			}
2148 
2149 			rt = static_cast<RailType>(std::max_element(count + RAILTYPE_BEGIN, count + RAILTYPE_END) - count);
2150 			if (count[rt] > 0) break;
2151 
2152 			/* No rail, just get the first available one */
2153 			FALLTHROUGH;
2154 		}
2155 		case 0: {
2156 			/* Use first available type */
2157 			std::vector<RailType>::const_iterator it = std::find_if(_sorted_railtypes.begin(), _sorted_railtypes.end(),
2158 					[](RailType r){ return HasRailtypeAvail(_local_company, r); });
2159 			rt = it != _sorted_railtypes.end() ? *it : RAILTYPE_BEGIN;
2160 			break;
2161 		}
2162 		case 1: {
2163 			/* Use last available type */
2164 			std::vector<RailType>::const_reverse_iterator it = std::find_if(_sorted_railtypes.rbegin(), _sorted_railtypes.rend(),
2165 					[](RailType r){ return HasRailtypeAvail(_local_company, r); });
2166 			rt = it != _sorted_railtypes.rend() ? *it : RAILTYPE_BEGIN;
2167 			break;
2168 		}
2169 		default:
2170 			NOT_REACHED();
2171 	}
2172 
2173 	_last_built_railtype = _cur_railtype = rt;
2174 	BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
2175 	if (w != nullptr) w->ModifyRailType(_cur_railtype);
2176 }
2177 
2178 /**
2179  * Updates the current signal variant used in the signal GUI
2180  * to the one adequate to current year.
2181  * @param new_value needed to be called when a setting changes
2182  */
ResetSignalVariant(int32 new_value)2183 void ResetSignalVariant(int32 new_value)
2184 {
2185 	SignalVariant new_variant = (_cur_year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
2186 
2187 	if (new_variant != _cur_signal_variant) {
2188 		Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
2189 		if (w != nullptr) {
2190 			w->SetDirty();
2191 			w->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
2192 		}
2193 		_cur_signal_variant = new_variant;
2194 	}
2195 }
2196 
2197 /**
2198  * Resets the rail GUI - sets default railtype to build
2199  * and resets the signal GUI
2200  */
InitializeRailGUI()2201 void InitializeRailGUI()
2202 {
2203 	SetDefaultRailGui();
2204 
2205 	_convert_signal_button = false;
2206 	_cur_signal_type = SIGTYPE_PBS_ONEWAY;
2207 	ResetSignalVariant();
2208 }
2209 
2210 /**
2211  * Create a drop down list for all the rail types of the local company.
2212  * @param for_replacement Whether this list is for the replacement window.
2213  * @param all_option Whether to add an 'all types' item.
2214  * @return The populated and sorted #DropDownList.
2215  */
GetRailTypeDropDownList(bool for_replacement,bool all_option)2216 DropDownList GetRailTypeDropDownList(bool for_replacement, bool all_option)
2217 {
2218 	RailTypes used_railtypes;
2219 	RailTypes avail_railtypes;
2220 
2221 	const Company *c = Company::Get(_local_company);
2222 
2223 	/* Find the used railtypes. */
2224 	if (for_replacement) {
2225 		avail_railtypes = GetCompanyRailtypes(c->index, false);
2226 		used_railtypes  = GetRailTypes(false);
2227 	} else {
2228 		avail_railtypes = c->avail_railtypes;
2229 		used_railtypes  = GetRailTypes(true);
2230 	}
2231 
2232 	DropDownList list;
2233 
2234 	if (all_option) {
2235 		list.emplace_back(new DropDownListStringItem(STR_REPLACE_ALL_RAILTYPE, INVALID_RAILTYPE, false));
2236 	}
2237 
2238 	Dimension d = { 0, 0 };
2239 	/* Get largest icon size, to ensure text is aligned on each menu item. */
2240 	if (!for_replacement) {
2241 		for (const auto &rt : _sorted_railtypes) {
2242 			if (!HasBit(used_railtypes, rt)) continue;
2243 			const RailtypeInfo *rti = GetRailTypeInfo(rt);
2244 			d = maxdim(d, GetSpriteSize(rti->gui_sprites.build_x_rail));
2245 		}
2246 	}
2247 
2248 	for (const auto &rt : _sorted_railtypes) {
2249 		/* If it's not used ever, don't show it to the user. */
2250 		if (!HasBit(used_railtypes, rt)) continue;
2251 
2252 		const RailtypeInfo *rti = GetRailTypeInfo(rt);
2253 
2254 		StringID str = for_replacement ? rti->strings.replace_text : (rti->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING);
2255 		DropDownListParamStringItem *item;
2256 		if (for_replacement) {
2257 			item = new DropDownListParamStringItem(str, rt, !HasBit(avail_railtypes, rt));
2258 		} else {
2259 			DropDownListIconItem *iconitem = new DropDownListIconItem(rti->gui_sprites.build_x_rail, PAL_NONE, str, rt, !HasBit(avail_railtypes, rt));
2260 			iconitem->SetDimension(d);
2261 			item = iconitem;
2262 		}
2263 		item->SetParam(0, rti->strings.menu_text);
2264 		item->SetParam(1, rti->max_speed);
2265 		list.emplace_back(item);
2266 	}
2267 
2268 	if (list.size() == 0) {
2269 		/* Empty dropdowns are not allowed */
2270 		list.emplace_back(new DropDownListStringItem(STR_NONE, INVALID_RAILTYPE, true));
2271 	}
2272 
2273 	return list;
2274 }
2275