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 newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
9 
10 #include "stdafx.h"
11 #include <stdarg.h>
12 #include "window_gui.h"
13 #include "window_func.h"
14 #include "random_access_file_type.h"
15 #include "spritecache.h"
16 #include "string_func.h"
17 #include "strings_func.h"
18 #include "textbuf_gui.h"
19 #include "vehicle_gui.h"
20 #include "zoom_func.h"
21 
22 #include "engine_base.h"
23 #include "industry.h"
24 #include "object_base.h"
25 #include "station_base.h"
26 #include "town.h"
27 #include "vehicle_base.h"
28 #include "train.h"
29 #include "roadveh.h"
30 
31 #include "newgrf_airporttiles.h"
32 #include "newgrf_debug.h"
33 #include "newgrf_object.h"
34 #include "newgrf_spritegroup.h"
35 #include "newgrf_station.h"
36 #include "newgrf_town.h"
37 #include "newgrf_railtype.h"
38 #include "newgrf_industries.h"
39 #include "newgrf_industrytiles.h"
40 
41 #include "widgets/newgrf_debug_widget.h"
42 
43 #include "table/strings.h"
44 
45 #include "safeguards.h"
46 
47 /** The sprite picker. */
48 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, nullptr, std::vector<SpriteID>() };
49 
50 /**
51  * Get the feature index related to the window number.
52  * @param window_number The window to get the feature index from.
53  * @return the feature index
54  */
GetFeatureIndex(uint window_number)55 static inline uint GetFeatureIndex(uint window_number)
56 {
57 	return GB(window_number, 0, 24);
58 }
59 
60 /**
61  * Get the window number for the inspect window given a
62  * feature and index.
63  * @param feature The feature we want to inspect.
64  * @param index   The index/identifier of the feature to inspect.
65  * @return the InspectWindow (Window)Number
66  */
GetInspectWindowNumber(GrfSpecFeature feature,uint index)67 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
68 {
69 	assert((index >> 24) == 0);
70 	return (feature << 24) | index;
71 }
72 
73 /**
74  * The type of a property to show. This is used to
75  * provide an appropriate representation in the GUI.
76  */
77 enum NIType {
78 	NIT_INT,   ///< The property is a simple integer
79 	NIT_CARGO, ///< The property is a cargo
80 };
81 
82 typedef const void *NIOffsetProc(const void *b);
83 
84 /** Representation of the data from a NewGRF property. */
85 struct NIProperty {
86 	const char *name;          ///< A (human readable) name for the property
87 	NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory
88 	byte read_size;            ///< Number of bytes (i.e. byte, word, dword etc)
89 	byte prop;                 ///< The number of the property
90 	byte type;
91 };
92 
93 
94 /**
95  * Representation of the available callbacks with
96  * information on when they actually apply.
97  */
98 struct NICallback {
99 	const char *name;          ///< The human readable name of the callback
100 	NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory
101 	byte read_size;            ///< The number of bytes (i.e. byte, word, dword etc) to read
102 	byte cb_bit;               ///< The bit that needs to be set for this callback to be enabled
103 	uint16 cb_id;              ///< The number of the callback
104 };
105 /** Mask to show no bit needs to be enabled for the callback. */
106 static const int CBM_NO_BIT = UINT8_MAX;
107 
108 /** Representation on the NewGRF variables. */
109 struct NIVariable {
110 	const char *name;
111 	byte var;
112 };
113 
114 /** Helper class to wrap some functionality/queries in. */
115 class NIHelper {
116 public:
117 	/** Silence a warning. */
~NIHelper()118 	virtual ~NIHelper() {}
119 
120 	/**
121 	 * Is the item with the given index inspectable?
122 	 * @param index the index to check.
123 	 * @return true iff the index is inspectable.
124 	 */
125 	virtual bool IsInspectable(uint index) const = 0;
126 
127 	/**
128 	 * Get the parent "window_number" of a given instance.
129 	 * @param index the instance to get the parent for.
130 	 * @return the parent's window_number or UINT32_MAX if there is none.
131 	 */
132 	virtual uint GetParent(uint index) const = 0;
133 
134 	/**
135 	 * Get the instance given an index.
136 	 * @param index the index to get the instance for.
137 	 * @return the instance.
138 	 */
139 	virtual const void *GetInstance(uint index) const = 0;
140 
141 	/**
142 	 * Get (NewGRF) specs given an index.
143 	 * @param index the index to get the specs for for.
144 	 * @return the specs.
145 	 */
146 	virtual const void *GetSpec(uint index) const = 0;
147 
148 	/**
149 	 * Set the string parameters to write the right data for a STRINGn.
150 	 * @param index the index to get the string parameters for.
151 	 */
152 	virtual void SetStringParameters(uint index) const = 0;
153 
154 	/**
155 	 * Get the GRFID of the file that includes this item.
156 	 * @param index index to check.
157 	 * @return GRFID of the item. 0 means that the item is not inspectable.
158 	 */
159 	virtual uint32 GetGRFID(uint index) const = 0;
160 
161 	/**
162 	 * Resolve (action2) variable for a given index.
163 	 * @param index The (instance) index to resolve the variable for.
164 	 * @param var   The variable to actually resolve.
165 	 * @param param The varaction2 0x60+x parameter to pass.
166 	 * @param avail Return whether the variable is available.
167 	 * @return The resolved variable's value.
168 	 */
169 	virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
170 
171 	/**
172 	 * Used to decide if the PSA needs a parameter or not.
173 	 * @return True iff this item has a PSA that requires a parameter.
174 	 */
PSAWithParameter() const175 	virtual bool PSAWithParameter() const
176 	{
177 		return false;
178 	}
179 
180 	/**
181 	 * Allows to know the size of the persistent storage.
182 	 * @param index Index of the item.
183 	 * @param grfid Parameter for the PSA. Only required for items with parameters.
184 	 * @return Size of the persistent storage in indices.
185 	 */
GetPSASize(uint index,uint32 grfid) const186 	virtual uint GetPSASize(uint index, uint32 grfid) const
187 	{
188 		return 0;
189 	}
190 
191 	/**
192 	 * Gets the first position of the array containing the persistent storage.
193 	 * @param index Index of the item.
194 	 * @param grfid Parameter for the PSA. Only required for items with parameters.
195 	 * @return Pointer to the first position of the storage array or nullptr if not present.
196 	 */
GetPSAFirstPosition(uint index,uint32 grfid) const197 	virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
198 	{
199 		return nullptr;
200 	}
201 
202 protected:
203 	/**
204 	 * Helper to make setting the strings easier.
205 	 * @param string the string to actually draw.
206 	 * @param index  the (instance) index for the string.
207 	 */
SetSimpleStringParameters(StringID string,uint32 index) const208 	void SetSimpleStringParameters(StringID string, uint32 index) const
209 	{
210 		SetDParam(0, string);
211 		SetDParam(1, index);
212 	}
213 
214 
215 	/**
216 	 * Helper to make setting the strings easier for objects at a specific tile.
217 	 * @param string the string to draw the object's name
218 	 * @param index  the (instance) index for the string.
219 	 * @param tile   the tile the object is at
220 	 */
SetObjectAtStringParameters(StringID string,uint32 index,TileIndex tile) const221 	void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
222 	{
223 		SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
224 		SetDParam(1, string);
225 		SetDParam(2, index);
226 		SetDParam(3, tile);
227 	}
228 };
229 
230 
231 /** Container for all information for a given feature. */
232 struct NIFeature {
233 	const NIProperty *properties; ///< The properties associated with this feature.
234 	const NICallback *callbacks;  ///< The callbacks associated with this feature.
235 	const NIVariable *variables;  ///< The variables associated with this feature.
236 	const NIHelper   *helper;     ///< The class container all helper functions.
237 };
238 
239 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
240 #include "table/newgrf_debug_data.h"
241 
242 /**
243  * Get the feature number related to the window number.
244  * @param window_number The window to get the feature number for.
245  * @return The feature number.
246  */
GetFeatureNum(uint window_number)247 static inline GrfSpecFeature GetFeatureNum(uint window_number)
248 {
249 	return (GrfSpecFeature)GB(window_number, 24, 8);
250 }
251 
252 /**
253  * Get the NIFeature related to the window number.
254  * @param window_number The window to get the NIFeature for.
255  * @return the NIFeature, or nullptr is there isn't one.
256  */
GetFeature(uint window_number)257 static inline const NIFeature *GetFeature(uint window_number)
258 {
259 	GrfSpecFeature idx = GetFeatureNum(window_number);
260 	return idx < GSF_FAKE_END ? _nifeatures[idx] : nullptr;
261 }
262 
263 /**
264  * Get the NIHelper related to the window number.
265  * @param window_number The window to get the NIHelper for.
266  * @pre GetFeature(window_number) != nullptr
267  * @return the NIHelper
268  */
GetFeatureHelper(uint window_number)269 static inline const NIHelper *GetFeatureHelper(uint window_number)
270 {
271 	return GetFeature(window_number)->helper;
272 }
273 
274 /** Window used for inspecting NewGRFs. */
275 struct NewGRFInspectWindow : Window {
276 	static const int LEFT_OFFSET   = 5; ///< Position of left edge
277 	static const int RIGHT_OFFSET  = 5; ///< Position of right edge
278 	static const int TOP_OFFSET    = 5; ///< Position of top edge
279 	static const int BOTTOM_OFFSET = 5; ///< Position of bottom edge
280 
281 	/** The value for the variable 60 parameters. */
282 	static uint32 var60params[GSF_FAKE_END][0x20];
283 
284 	/** GRFID of the caller of this window, 0 if it has no caller. */
285 	uint32 caller_grfid;
286 
287 	/** For ground vehicles: Index in vehicle chain. */
288 	uint chain_index;
289 
290 	/** The currently edited parameter, to update the right one. */
291 	byte current_edit_param;
292 
293 	Scrollbar *vscroll;
294 
295 	/**
296 	 * Check whether the given variable has a parameter.
297 	 * @param variable the variable to check.
298 	 * @return true iff the variable has a parameter.
299 	 */
HasVariableParameterNewGRFInspectWindow300 	static bool HasVariableParameter(uint variable)
301 	{
302 		return IsInsideBS(variable, 0x60, 0x20);
303 	}
304 
305 	/**
306 	 * Set the GRFID of the item opening this window.
307 	 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
308 	 */
SetCallerGRFIDNewGRFInspectWindow309 	void SetCallerGRFID(uint32 grfid)
310 	{
311 		this->caller_grfid = grfid;
312 		this->SetDirty();
313 	}
314 
315 	/**
316 	 * Check whether this feature has chain index, i.e. refers to ground vehicles.
317 	 */
HasChainIndexNewGRFInspectWindow318 	bool HasChainIndex() const
319 	{
320 		GrfSpecFeature f = GetFeatureNum(this->window_number);
321 		return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
322 	}
323 
324 	/**
325 	 * Get the feature index.
326 	 * @return the feature index
327 	 */
GetFeatureIndexNewGRFInspectWindow328 	uint GetFeatureIndex() const
329 	{
330 		uint index = ::GetFeatureIndex(this->window_number);
331 		if (this->chain_index > 0) {
332 			assert(this->HasChainIndex());
333 			const Vehicle *v = Vehicle::Get(index);
334 			v = v->Move(this->chain_index);
335 			if (v != nullptr) index = v->index;
336 		}
337 		return index;
338 	}
339 
340 	/**
341 	 * Ensure that this->chain_index is in range.
342 	 */
ValidateChainIndexNewGRFInspectWindow343 	void ValidateChainIndex()
344 	{
345 		if (this->chain_index == 0) return;
346 
347 		assert(this->HasChainIndex());
348 
349 		const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
350 		v = v->Move(this->chain_index);
351 		if (v == nullptr) this->chain_index = 0;
352 	}
353 
NewGRFInspectWindowNewGRFInspectWindow354 	NewGRFInspectWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
355 	{
356 		this->CreateNestedTree();
357 		this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
358 		this->FinishInitNested(wno);
359 
360 		this->vscroll->SetCount(0);
361 		this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
362 
363 		this->OnInvalidateData(0, true);
364 	}
365 
SetStringParametersNewGRFInspectWindow366 	void SetStringParameters(int widget) const override
367 	{
368 		if (widget != WID_NGRFI_CAPTION) return;
369 
370 		GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
371 	}
372 
UpdateWidgetSizeNewGRFInspectWindow373 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
374 	{
375 		switch (widget) {
376 			case WID_NGRFI_VEH_CHAIN: {
377 				assert(this->HasChainIndex());
378 				GrfSpecFeature f = GetFeatureNum(this->window_number);
379 				size->height = std::max(size->height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WD_BEVEL_TOP + WD_BEVEL_BOTTOM);
380 				break;
381 			}
382 
383 			case WID_NGRFI_MAINPANEL:
384 				resize->height = std::max(11, FONT_HEIGHT_NORMAL + 1);
385 				resize->width  = 1;
386 
387 				size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
388 				break;
389 		}
390 	}
391 
392 	/**
393 	 * Helper function to draw a string (line) in the window.
394 	 * @param r      The (screen) rectangle we must draw within
395 	 * @param offset The offset (in lines) we want to draw for
396 	 * @param format The format string
397 	 */
DrawStringNewGRFInspectWindow398 	void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
399 	{
400 		char buf[1024];
401 
402 		va_list va;
403 		va_start(va, format);
404 		vseprintf(buf, lastof(buf), format, va);
405 		va_end(va);
406 
407 		offset -= this->vscroll->GetPosition();
408 		if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
409 
410 		::DrawString(r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
411 	}
412 
DrawWidgetNewGRFInspectWindow413 	void DrawWidget(const Rect &r, int widget) const override
414 	{
415 		switch (widget) {
416 			case WID_NGRFI_VEH_CHAIN: {
417 				const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
418 				int total_width = 0;
419 				int sel_start = 0;
420 				int sel_end = 0;
421 				for (const Vehicle *u = v->First(); u != nullptr; u = u->Next()) {
422 					if (u == v) sel_start = total_width;
423 					switch (u->type) {
424 						case VEH_TRAIN: total_width += Train      ::From(u)->GetDisplayImageWidth(); break;
425 						case VEH_ROAD:  total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
426 						default: NOT_REACHED();
427 					}
428 					if (u == v) sel_end = total_width;
429 				}
430 
431 				int width = r.right + 1 - r.left - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
432 				int skip = 0;
433 				if (total_width > width) {
434 					int sel_center = (sel_start + sel_end) / 2;
435 					if (sel_center > width / 2) skip = std::min(total_width - width, sel_center - width / 2);
436 				}
437 
438 				GrfSpecFeature f = GetFeatureNum(this->window_number);
439 				int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
440 				int y = (r.top + r.bottom - h) / 2;
441 				DrawVehicleImage(v->First(), r.left + WD_BEVEL_LEFT, r.right - WD_BEVEL_RIGHT, y + 1, INVALID_VEHICLE, EIT_IN_DETAILS, skip);
442 
443 				/* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
444 				if (_current_text_dir == TD_RTL) {
445 					DrawFrameRect(r.right - sel_end   + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
446 				} else {
447 					DrawFrameRect(r.left  + sel_start - skip, y, r.left  + sel_end   - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
448 				}
449 				break;
450 			}
451 		}
452 
453 		if (widget != WID_NGRFI_MAINPANEL) return;
454 
455 		uint index = this->GetFeatureIndex();
456 		const NIFeature *nif  = GetFeature(this->window_number);
457 		const NIHelper *nih   = nif->helper;
458 		const void *base      = nih->GetInstance(index);
459 		const void *base_spec = nih->GetSpec(index);
460 
461 		uint i = 0;
462 		if (nif->variables != nullptr) {
463 			this->DrawString(r, i++, "Variables:");
464 			for (const NIVariable *niv = nif->variables; niv->name != nullptr; niv++) {
465 				bool avail = true;
466 				uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
467 				uint value = nih->Resolve(index, niv->var, param, &avail);
468 
469 				if (!avail) continue;
470 
471 				if (HasVariableParameter(niv->var)) {
472 					this->DrawString(r, i++, "  %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
473 				} else {
474 					this->DrawString(r, i++, "  %02x: %08x (%s)", niv->var, value, niv->name);
475 				}
476 			}
477 		}
478 
479 		uint psa_size = nih->GetPSASize(index, this->caller_grfid);
480 		const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
481 		if (psa_size != 0 && psa != nullptr) {
482 			if (nih->PSAWithParameter()) {
483 				this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
484 			} else {
485 				this->DrawString(r, i++, "Persistent storage:");
486 			}
487 			assert(psa_size % 4 == 0);
488 			for (uint j = 0; j < psa_size; j += 4, psa += 4) {
489 				this->DrawString(r, i++, "  %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
490 			}
491 		}
492 
493 		if (nif->properties != nullptr) {
494 			this->DrawString(r, i++, "Properties:");
495 			for (const NIProperty *nip = nif->properties; nip->name != nullptr; nip++) {
496 				const void *ptr = nip->offset_proc(base);
497 				uint value;
498 				switch (nip->read_size) {
499 					case 1: value = *(const uint8  *)ptr; break;
500 					case 2: value = *(const uint16 *)ptr; break;
501 					case 4: value = *(const uint32 *)ptr; break;
502 					default: NOT_REACHED();
503 				}
504 
505 				StringID string;
506 				SetDParam(0, value);
507 				switch (nip->type) {
508 					case NIT_INT:
509 						string = STR_JUST_INT;
510 						break;
511 
512 					case NIT_CARGO:
513 						string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
514 						break;
515 
516 					default:
517 						NOT_REACHED();
518 				}
519 
520 				char buffer[64];
521 				GetString(buffer, string, lastof(buffer));
522 				this->DrawString(r, i++, "  %02x: %s (%s)", nip->prop, buffer, nip->name);
523 			}
524 		}
525 
526 		if (nif->callbacks != nullptr) {
527 			this->DrawString(r, i++, "Callbacks:");
528 			for (const NICallback *nic = nif->callbacks; nic->name != nullptr; nic++) {
529 				if (nic->cb_bit != CBM_NO_BIT) {
530 					const void *ptr = nic->offset_proc(base_spec);
531 					uint value;
532 					switch (nic->read_size) {
533 						case 1: value = *(const uint8  *)ptr; break;
534 						case 2: value = *(const uint16 *)ptr; break;
535 						case 4: value = *(const uint32 *)ptr; break;
536 						default: NOT_REACHED();
537 					}
538 
539 					if (!HasBit(value, nic->cb_bit)) continue;
540 					this->DrawString(r, i++, "  %03x: %s", nic->cb_id, nic->name);
541 				} else {
542 					this->DrawString(r, i++, "  %03x: %s (unmasked)", nic->cb_id, nic->name);
543 				}
544 			}
545 		}
546 
547 		/* Not nice and certainly a hack, but it beats duplicating
548 		 * this whole function just to count the actual number of
549 		 * elements. Especially because they need to be redrawn. */
550 		const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
551 	}
552 
OnClickNewGRFInspectWindow553 	void OnClick(Point pt, int widget, int click_count) override
554 	{
555 		switch (widget) {
556 			case WID_NGRFI_PARENT: {
557 				const NIHelper *nih   = GetFeatureHelper(this->window_number);
558 				uint index = nih->GetParent(this->GetFeatureIndex());
559 				::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
560 				break;
561 			}
562 
563 			case WID_NGRFI_VEH_PREV:
564 				if (this->chain_index > 0) {
565 					this->chain_index--;
566 					this->InvalidateData();
567 				}
568 				break;
569 
570 			case WID_NGRFI_VEH_NEXT:
571 				if (this->HasChainIndex()) {
572 					uint index = this->GetFeatureIndex();
573 					Vehicle *v = Vehicle::Get(index);
574 					if (v != nullptr && v->Next() != nullptr) {
575 						this->chain_index++;
576 						this->InvalidateData();
577 					}
578 				}
579 				break;
580 
581 			case WID_NGRFI_MAINPANEL: {
582 				/* Does this feature have variables? */
583 				const NIFeature *nif  = GetFeature(this->window_number);
584 				if (nif->variables == nullptr) return;
585 
586 				/* Get the line, make sure it's within the boundaries. */
587 				int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
588 				if (line == INT_MAX) return;
589 
590 				/* Find the variable related to the line */
591 				for (const NIVariable *niv = nif->variables; niv->name != nullptr; niv++, line--) {
592 					if (line != 1) continue; // 1 because of the "Variables:" line
593 
594 					if (!HasVariableParameter(niv->var)) break;
595 
596 					this->current_edit_param = niv->var;
597 					ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
598 				}
599 			}
600 		}
601 	}
602 
OnQueryTextFinishedNewGRFInspectWindow603 	void OnQueryTextFinished(char *str) override
604 	{
605 		if (StrEmpty(str)) return;
606 
607 		NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, nullptr, 16);
608 		this->SetDirty();
609 	}
610 
OnResizeNewGRFInspectWindow611 	void OnResize() override
612 	{
613 		this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
614 	}
615 
616 	/**
617 	 * Some data on this window has become invalid.
618 	 * @param data Information about the changed data.
619 	 * @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.
620 	 */
OnInvalidateDataNewGRFInspectWindow621 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
622 	{
623 		if (!gui_scope) return;
624 		if (this->HasChainIndex()) {
625 			this->ValidateChainIndex();
626 			this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV, this->chain_index == 0);
627 			Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
628 			this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == nullptr || v->Next() == nullptr);
629 		}
630 	}
631 };
632 
633 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
634 
635 static const NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
636 	NWidget(NWID_HORIZONTAL),
637 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
638 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
639 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
640 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
641 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
642 	EndContainer(),
643 	NWidget(WWT_PANEL, COLOUR_GREY),
644 		NWidget(NWID_HORIZONTAL),
645 			NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_PREV), SetDataTip(AWV_DECREASE, STR_NULL),
646 			NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_NEXT), SetDataTip(AWV_INCREASE, STR_NULL),
647 			NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
648 		EndContainer(),
649 	EndContainer(),
650 	NWidget(NWID_HORIZONTAL),
651 		NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
652 		NWidget(NWID_VERTICAL),
653 			NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
654 			NWidget(WWT_RESIZEBOX, COLOUR_GREY),
655 		EndContainer(),
656 	EndContainer(),
657 };
658 
659 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
660 	NWidget(NWID_HORIZONTAL),
661 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
662 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
663 		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
664 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
665 		NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
666 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
667 	EndContainer(),
668 	NWidget(NWID_HORIZONTAL),
669 		NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
670 		NWidget(NWID_VERTICAL),
671 			NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
672 			NWidget(WWT_RESIZEBOX, COLOUR_GREY),
673 		EndContainer(),
674 	EndContainer(),
675 };
676 
677 static WindowDesc _newgrf_inspect_chain_desc(
678 	WDP_AUTO, "newgrf_inspect_chain", 400, 300,
679 	WC_NEWGRF_INSPECT, WC_NONE,
680 	0,
681 	_nested_newgrf_inspect_chain_widgets, lengthof(_nested_newgrf_inspect_chain_widgets)
682 );
683 
684 static WindowDesc _newgrf_inspect_desc(
685 	WDP_AUTO, "newgrf_inspect", 400, 300,
686 	WC_NEWGRF_INSPECT, WC_NONE,
687 	0,
688 	_nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
689 );
690 
691 /**
692  * Show the inspect window for a given feature and index.
693  * The index is normally an in-game location/identifier, such
694  * as a TileIndex or an IndustryID depending on the feature
695  * we want to inspect.
696  * @param feature The feature we want to inspect.
697  * @param index   The index/identifier of the feature to inspect.
698  * @param grfid   GRFID of the item opening this window, or 0 if not opened by other window.
699  */
ShowNewGRFInspectWindow(GrfSpecFeature feature,uint index,const uint32 grfid)700 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
701 {
702 	if (!IsNewGRFInspectable(feature, index)) return;
703 
704 	WindowNumber wno = GetInspectWindowNumber(feature, index);
705 	WindowDesc *desc = (feature == GSF_TRAINS || feature == GSF_ROADVEHICLES) ? &_newgrf_inspect_chain_desc : &_newgrf_inspect_desc;
706 	NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(desc, wno, true);
707 	w->SetCallerGRFID(grfid);
708 }
709 
710 /**
711  * Invalidate the inspect window for a given feature and index.
712  * The index is normally an in-game location/identifier, such
713  * as a TileIndex or an IndustryID depending on the feature
714  * we want to inspect.
715  * @param feature The feature we want to invalidate the window for.
716  * @param index   The index/identifier of the feature to invalidate.
717  */
InvalidateNewGRFInspectWindow(GrfSpecFeature feature,uint index)718 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
719 {
720 	if (feature == GSF_INVALID) return;
721 
722 	WindowNumber wno = GetInspectWindowNumber(feature, index);
723 	InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
724 }
725 
726 /**
727  * Delete inspect window for a given feature and index.
728  * The index is normally an in-game location/identifier, such
729  * as a TileIndex or an IndustryID depending on the feature
730  * we want to inspect.
731  * @param feature The feature we want to delete the window for.
732  * @param index   The index/identifier of the feature to delete.
733  */
DeleteNewGRFInspectWindow(GrfSpecFeature feature,uint index)734 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
735 {
736 	if (feature == GSF_INVALID) return;
737 
738 	WindowNumber wno = GetInspectWindowNumber(feature, index);
739 	CloseWindowById(WC_NEWGRF_INSPECT, wno);
740 
741 	/* Reinitialise the land information window to remove the "debug" sprite if needed.
742 	 * Note: Since we might be called from a command here, it is important to not execute
743 	 * the invalidation immediately. The landinfo window tests commands itself. */
744 	InvalidateWindowData(WC_LAND_INFO, 0, 1);
745 }
746 
747 /**
748  * Can we inspect the data given a certain feature and index.
749  * The index is normally an in-game location/identifier, such
750  * as a TileIndex or an IndustryID depending on the feature
751  * we want to inspect.
752  * @param feature The feature we want to inspect.
753  * @param index   The index/identifier of the feature to inspect.
754  * @return true if there is something to show.
755  */
IsNewGRFInspectable(GrfSpecFeature feature,uint index)756 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
757 {
758 	const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
759 	if (nif == nullptr) return false;
760 	return nif->helper->IsInspectable(index);
761 }
762 
763 /**
764  * Get the GrfSpecFeature associated with the tile.
765  * @param tile The tile to get the feature from.
766  * @return the GrfSpecFeature.
767  */
GetGrfSpecFeature(TileIndex tile)768 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
769 {
770 	switch (GetTileType(tile)) {
771 		default:              return GSF_INVALID;
772 		case MP_RAILWAY:      return GSF_RAILTYPES;
773 		case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
774 		case MP_HOUSE:        return GSF_HOUSES;
775 		case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
776 		case MP_OBJECT:       return GSF_OBJECTS;
777 
778 		case MP_STATION:
779 			switch (GetStationType(tile)) {
780 				case STATION_RAIL:    return GSF_STATIONS;
781 				case STATION_AIRPORT: return GSF_AIRPORTTILES;
782 				default:              return GSF_INVALID;
783 			}
784 	}
785 }
786 
787 /**
788  * Get the GrfSpecFeature associated with the vehicle.
789  * @param type The vehicle type to get the feature from.
790  * @return the GrfSpecFeature.
791  */
GetGrfSpecFeature(VehicleType type)792 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
793 {
794 	switch (type) {
795 		case VEH_TRAIN:    return GSF_TRAINS;
796 		case VEH_ROAD:     return GSF_ROADVEHICLES;
797 		case VEH_SHIP:     return GSF_SHIPS;
798 		case VEH_AIRCRAFT: return GSF_AIRCRAFT;
799 		default:           return GSF_INVALID;
800 	}
801 }
802 
803 
804 
805 /**** Sprite Aligner ****/
806 
807 /** Window used for aligning sprites. */
808 struct SpriteAlignerWindow : Window {
809 	typedef std::pair<int16, int16> XyOffs;    ///< Pair for x and y offsets of the sprite before alignment. First value contains the x offset, second value y offset.
810 
811 	SpriteID current_sprite;                   ///< The currently shown sprite.
812 	Scrollbar *vscroll;
813 	SmallMap<SpriteID, XyOffs> offs_start_map; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window.
814 
SpriteAlignerWindowSpriteAlignerWindow815 	SpriteAlignerWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
816 	{
817 		this->CreateNestedTree();
818 		this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
819 		this->FinishInitNested(wno);
820 
821 		/* Oh yes, we assume there is at least one normal sprite! */
822 		while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
823 	}
824 
SetStringParametersSpriteAlignerWindow825 	void SetStringParameters(int widget) const override
826 	{
827 		const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
828 		switch (widget) {
829 			case WID_SA_CAPTION:
830 				SetDParam(0, this->current_sprite);
831 				SetDParamStr(1, GetOriginFile(this->current_sprite)->GetSimplifiedFilename());
832 				break;
833 
834 			case WID_SA_OFFSETS_ABS:
835 				SetDParam(0, spr->x_offs);
836 				SetDParam(1, spr->y_offs);
837 				break;
838 
839 			case WID_SA_OFFSETS_REL: {
840 				/* Relative offset is new absolute offset - starting absolute offset.
841 				 * Show 0, 0 as the relative offsets if entry is not in the map (meaning they have not been changed yet).
842 				 */
843 				const auto key_offs_pair = this->offs_start_map.Find(this->current_sprite);
844 				if (key_offs_pair != this->offs_start_map.end()) {
845 					SetDParam(0, spr->x_offs - key_offs_pair->second.first);
846 					SetDParam(1, spr->y_offs - key_offs_pair->second.second);
847 				} else {
848 					SetDParam(0, 0);
849 					SetDParam(1, 0);
850 				}
851 				break;
852 			}
853 
854 			default:
855 				break;
856 		}
857 	}
858 
UpdateWidgetSizeSpriteAlignerWindow859 	void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
860 	{
861 		switch (widget) {
862 			case WID_SA_SPRITE:
863 				size->height = ScaleGUITrad(200);
864 				break;
865 			case WID_SA_LIST:
866 				resize->height = FONT_HEIGHT_NORMAL + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
867 				resize->width  = 1;
868 				fill->height = resize->height;
869 				break;
870 			default:
871 				break;
872 		}
873 	}
874 
DrawWidgetSpriteAlignerWindow875 	void DrawWidget(const Rect &r, int widget) const override
876 	{
877 		switch (widget) {
878 			case WID_SA_SPRITE: {
879 				/* Center the sprite ourselves */
880 				const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
881 				int width  = r.right  - r.left + 1 - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
882 				int height = r.bottom - r.top  + 1 - WD_BEVEL_TOP - WD_BEVEL_BOTTOM;
883 				int x = -UnScaleGUI(spr->x_offs) + (width  - UnScaleGUI(spr->width) ) / 2;
884 				int y = -UnScaleGUI(spr->y_offs) + (height - UnScaleGUI(spr->height)) / 2;
885 
886 				DrawPixelInfo new_dpi;
887 				if (!FillDrawPixelInfo(&new_dpi, r.left + WD_BEVEL_LEFT, r.top + WD_BEVEL_TOP, width, height)) break;
888 				DrawPixelInfo *old_dpi = _cur_dpi;
889 				_cur_dpi = &new_dpi;
890 
891 				DrawSprite(this->current_sprite, PAL_NONE, x, y, nullptr, ZOOM_LVL_GUI);
892 
893 				_cur_dpi = old_dpi;
894 
895 				break;
896 			}
897 
898 			case WID_SA_LIST: {
899 				const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
900 				int step_size = nwid->resize_y;
901 
902 				std::vector<SpriteID> &list = _newgrf_debug_sprite_picker.sprites;
903 				int max = std::min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (uint)list.size());
904 
905 				int y = r.top + WD_FRAMERECT_TOP;
906 				for (int i = this->vscroll->GetPosition(); i < max; i++) {
907 					SetDParam(0, list[i]);
908 					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
909 					y += step_size;
910 				}
911 				break;
912 			}
913 		}
914 	}
915 
OnClickSpriteAlignerWindow916 	void OnClick(Point pt, int widget, int click_count) override
917 	{
918 		switch (widget) {
919 			case WID_SA_PREVIOUS:
920 				do {
921 					this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() :  this->current_sprite) - 1;
922 				} while (GetSpriteType(this->current_sprite) != ST_NORMAL);
923 				this->SetDirty();
924 				break;
925 
926 			case WID_SA_GOTO:
927 				ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
928 				break;
929 
930 			case WID_SA_NEXT:
931 				do {
932 					this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
933 				} while (GetSpriteType(this->current_sprite) != ST_NORMAL);
934 				this->SetDirty();
935 				break;
936 
937 			case WID_SA_PICKER:
938 				this->LowerWidget(WID_SA_PICKER);
939 				_newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
940 				this->SetDirty();
941 				break;
942 
943 			case WID_SA_LIST: {
944 				const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
945 				int step_size = nwid->resize_y;
946 
947 				uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
948 				if (i < _newgrf_debug_sprite_picker.sprites.size()) {
949 					SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
950 					if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
951 				}
952 				this->SetDirty();
953 				break;
954 			}
955 
956 			case WID_SA_UP:
957 			case WID_SA_DOWN:
958 			case WID_SA_LEFT:
959 			case WID_SA_RIGHT: {
960 				/*
961 				 * Yes... this is a hack.
962 				 *
963 				 * No... I don't think it is useful to make this less of a hack.
964 				 *
965 				 * If you want to align sprites, you just need the number. Generally
966 				 * the sprite caches are big enough to not remove the sprite from the
967 				 * cache. If that's not the case, just let the NewGRF developer
968 				 * increase the cache size instead of storing thousands of offsets
969 				 * for the incredibly small chance that it's actually going to be
970 				 * used by someone and the sprite cache isn't big enough for that
971 				 * particular NewGRF developer.
972 				 */
973 				Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
974 
975 				/* Remember the original offsets of the current sprite, if not already in mapping. */
976 				if (!(this->offs_start_map.Contains(this->current_sprite))) {
977 					this->offs_start_map.Insert(this->current_sprite, XyOffs(spr->x_offs, spr->y_offs));
978 				}
979 				switch (widget) {
980 					/* Move eight units at a time if ctrl is pressed. */
981 					case WID_SA_UP:    spr->y_offs -= _ctrl_pressed ? 8 : 1; break;
982 					case WID_SA_DOWN:  spr->y_offs += _ctrl_pressed ? 8 : 1; break;
983 					case WID_SA_LEFT:  spr->x_offs -= _ctrl_pressed ? 8 : 1; break;
984 					case WID_SA_RIGHT: spr->x_offs += _ctrl_pressed ? 8 : 1; break;
985 				}
986 				/* Of course, we need to redraw the sprite, but where is it used?
987 				 * Everywhere is a safe bet. */
988 				MarkWholeScreenDirty();
989 				break;
990 			}
991 
992 			case WID_SA_RESET_REL:
993 				/* Reset the starting offsets for the current sprite. */
994 				this->offs_start_map.Erase(this->current_sprite);
995 				this->SetDirty();
996 				break;
997 		}
998 	}
999 
OnQueryTextFinishedSpriteAlignerWindow1000 	void OnQueryTextFinished(char *str) override
1001 	{
1002 		if (StrEmpty(str)) return;
1003 
1004 		this->current_sprite = atoi(str);
1005 		if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
1006 		while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
1007 			this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
1008 		}
1009 		this->SetDirty();
1010 	}
1011 
1012 	/**
1013 	 * Some data on this window has become invalid.
1014 	 * @param data Information about the changed data.
1015 	 * @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.
1016 	 */
OnInvalidateDataSpriteAlignerWindow1017 	void OnInvalidateData(int data = 0, bool gui_scope = true) override
1018 	{
1019 		if (!gui_scope) return;
1020 		if (data == 1) {
1021 			/* Sprite picker finished */
1022 			this->RaiseWidget(WID_SA_PICKER);
1023 			this->vscroll->SetCount((uint)_newgrf_debug_sprite_picker.sprites.size());
1024 		}
1025 	}
1026 
OnResizeSpriteAlignerWindow1027 	void OnResize() override
1028 	{
1029 		this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
1030 	}
1031 };
1032 
1033 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
1034 	NWidget(NWID_HORIZONTAL),
1035 		NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1036 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1037 		NWidget(WWT_SHADEBOX, COLOUR_GREY),
1038 		NWidget(WWT_STICKYBOX, COLOUR_GREY),
1039 	EndContainer(),
1040 	NWidget(WWT_PANEL, COLOUR_GREY),
1041 		NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
1042 			NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1043 				NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
1044 					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
1045 					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
1046 					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
1047 				EndContainer(),
1048 				NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1049 					NWidget(NWID_SPACER), SetFill(1, 1),
1050 					NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1051 					NWidget(NWID_SPACER), SetFill(1, 1),
1052 				EndContainer(),
1053 				NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
1054 					NWidget(NWID_VERTICAL),
1055 						NWidget(NWID_SPACER), SetFill(1, 1),
1056 						NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1057 						NWidget(NWID_SPACER), SetFill(1, 1),
1058 					EndContainer(),
1059 					NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
1060 					EndContainer(),
1061 					NWidget(NWID_VERTICAL),
1062 						NWidget(NWID_SPACER), SetFill(1, 1),
1063 						NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1064 						NWidget(NWID_SPACER), SetFill(1, 1),
1065 					EndContainer(),
1066 				EndContainer(),
1067 				NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1068 					NWidget(NWID_SPACER), SetFill(1, 1),
1069 					NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1070 					NWidget(NWID_SPACER), SetFill(1, 1),
1071 				EndContainer(),
1072 				NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS_ABS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS_ABS, STR_NULL), SetFill(1, 0), SetPadding(0, 10, 0, 10),
1073 				NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS_REL), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS_REL, STR_NULL), SetFill(1, 0), SetPadding(0, 10, 0, 10),
1074 				NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1075 					NWidget(NWID_SPACER), SetFill(1, 1),
1076 					NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_RESET_REL), SetDataTip(STR_SPRITE_ALIGNER_RESET_BUTTON, STR_SPRITE_ALIGNER_RESET_TOOLTIP), SetFill(0, 0),
1077 					NWidget(NWID_SPACER), SetFill(1, 1),
1078 				EndContainer(),
1079 			EndContainer(),
1080 			NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1081 				NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
1082 				NWidget(NWID_HORIZONTAL),
1083 					NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
1084 					NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
1085 				EndContainer(),
1086 			EndContainer(),
1087 		EndContainer(),
1088 	EndContainer(),
1089 };
1090 
1091 static WindowDesc _sprite_aligner_desc(
1092 	WDP_AUTO, "sprite_aligner", 400, 300,
1093 	WC_SPRITE_ALIGNER, WC_NONE,
1094 	0,
1095 	_nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
1096 );
1097 
1098 /**
1099  * Show the window for aligning sprites.
1100  */
ShowSpriteAlignerWindow()1101 void ShowSpriteAlignerWindow()
1102 {
1103 	AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
1104 }
1105