Add support for deleted functions
[openttd/fttd.git] / src / newgrf_debug_gui.cpp
blobe9631f0857b794be0f9ee5e3e5e2875226ee05c2
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
12 #include "stdafx.h"
13 #include <stdarg.h>
14 #include "window_gui.h"
15 #include "window_func.h"
16 #include "fileio_func.h"
17 #include "spritecache.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "textbuf_gui.h"
21 #include "vehicle_gui.h"
23 #include "engine_base.h"
24 #include "industry.h"
25 #include "object_base.h"
26 #include "station_base.h"
27 #include "town.h"
28 #include "vehicle_base.h"
29 #include "map/depot.h"
30 #include "train.h"
31 #include "roadveh.h"
33 #include "newgrf_airporttiles.h"
34 #include "newgrf_debug.h"
35 #include "newgrf_object.h"
36 #include "newgrf_spritegroup.h"
37 #include "newgrf_station.h"
38 #include "newgrf_town.h"
39 #include "newgrf_railtype.h"
40 #include "newgrf_industries.h"
41 #include "newgrf_industrytiles.h"
43 #include "widgets/newgrf_debug_widget.h"
45 #include "table/strings.h"
47 /** The sprite picker. */
48 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
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
55 static inline uint GetFeatureIndex(uint window_number)
57 return GB(window_number, 0, 24);
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
67 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
69 assert((index >> 24) == 0);
70 return (feature << 24) | index;
73 /**
74 * The type of a property to show. This is used to
75 * provide an appropriate representation in the GUI.
77 enum NIType {
78 NIT_INT, ///< The property is a simple integer
79 NIT_CARGO, ///< The property is a cargo
82 /** Representation of the data from a NewGRF property. */
83 struct NIProperty {
84 const char *name; ///< A (human readable) name for the property
85 ptrdiff_t offset; ///< Offset of the variable in the class
86 byte read_size; ///< Number of bytes (i.e. byte, word, dword etc)
87 byte prop; ///< The number of the property
88 byte type;
92 /**
93 * Representation of the available callbacks with
94 * information on when they actually apply.
96 struct NICallback {
97 const char *name; ///< The human readable name of the callback
98 ptrdiff_t offset; ///< Offset of the variable in the class
99 byte read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read
100 byte cb_bit; ///< The bit that needs to be set for this callback to be enabled
101 uint16 cb_id; ///< The number of the callback
103 /** Mask to show no bit needs to be enabled for the callback. */
104 static const int CBM_NO_BIT = UINT8_MAX;
106 /** Representation on the NewGRF variables. */
107 struct NIVariable {
108 const char *name;
109 byte var;
112 /** Helper class to wrap some functionality/queries in. */
113 class NIHelper {
114 public:
115 /** Silence a warning. */
116 virtual ~NIHelper() {}
119 * Is the item with the given index inspectable?
120 * @param index the index to check.
121 * @return true iff the index is inspectable.
123 virtual bool IsInspectable(uint index) const = 0;
126 * Get the parent "window_number" of a given instance.
127 * @param index the instance to get the parent for.
128 * @return the parent's window_number or UINT32_MAX if there is none.
130 virtual uint GetParent(uint index) const = 0;
133 * Get the instance given an index.
134 * @param index the index to get the instance for.
135 * @return the instance.
137 virtual const void *GetInstance(uint index) const = 0;
140 * Get (NewGRF) specs given an index.
141 * @param index the index to get the specs for for.
142 * @return the specs.
144 virtual const void *GetSpec(uint index) const = 0;
147 * Set the string parameters to write the right data for a STRINGn.
148 * @param index the index to get the string parameters for.
150 virtual void SetStringParameters(uint index) const = 0;
153 * Get the GRFID of the file that includes this item.
154 * @param index index to check.
155 * @return GRFID of the item. 0 means that the item is not inspectable.
157 virtual uint32 GetGRFID(uint index) const = 0;
160 * Resolve (action2) variable for a given index.
161 * @param index The (instance) index to resolve the variable for.
162 * @param var The variable to actually resolve.
163 * @param param The varaction2 0x60+x parameter to pass.
164 * @param avail Return whether the variable is available.
165 * @return The resolved variable's value.
167 virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
170 * Used to decide if the PSA needs a parameter or not.
171 * @return True iff this item has a PSA that requires a parameter.
173 virtual bool PSAWithParameter() const
175 return false;
179 * Allows to know the size of the persistent storage.
180 * @param index Index of the item.
181 * @param grfid Parameter for the PSA. Only required for items with parameters.
182 * @return Size of the persistent storage in indices.
184 virtual uint GetPSASize(uint index, uint32 grfid) const
186 return 0;
190 * Gets the first position of the array containing the persistent storage.
191 * @param index Index of the item.
192 * @param grfid Parameter for the PSA. Only required for items with parameters.
193 * @return Pointer to the first position of the storage array or NULL if not present.
195 virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
197 return NULL;
200 protected:
202 * Helper to make setting the strings easier.
203 * @param string the string to actually draw.
204 * @param index the (instance) index for the string.
206 void SetSimpleStringParameters(StringID string, uint32 index) const
208 SetDParam(0, string);
209 SetDParam(1, index);
214 * Helper to make setting the strings easier for objects at a specific tile.
215 * @param string the string to draw the object's name
216 * @param index the (instance) index for the string.
217 * @param tile the tile the object is at
219 void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
221 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
222 SetDParam(1, string);
223 SetDParam(2, index);
224 SetDParam(3, tile);
229 /** Container for all information for a given feature. */
230 struct NIFeature {
231 const NIProperty *properties; ///< The properties associated with this feature.
232 const NICallback *callbacks; ///< The callbacks associated with this feature.
233 const NIVariable *variables; ///< The variables associated with this feature.
234 const NIHelper *helper; ///< The class container all helper functions.
237 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
238 #include "table/newgrf_debug_data.h"
241 * Get the feature number related to the window number.
242 * @param window_number The window to get the feature number for.
243 * @return The feature number.
245 static inline GrfSpecFeature GetFeatureNum(uint window_number)
247 return (GrfSpecFeature)GB(window_number, 24, 8);
251 * Get the NIFeature related to the window number.
252 * @param window_number The window to get the NIFeature for.
253 * @return the NIFeature, or NULL is there isn't one.
255 static inline const NIFeature *GetFeature(uint window_number)
257 GrfSpecFeature idx = GetFeatureNum(window_number);
258 return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
262 * Get the NIHelper related to the window number.
263 * @param window_number The window to get the NIHelper for.
264 * @pre GetFeature(window_number) != NULL
265 * @return the NIHelper
267 static inline const NIHelper *GetFeatureHelper(uint window_number)
269 return GetFeature(window_number)->helper;
272 /** Window used for inspecting NewGRFs. */
273 struct NewGRFInspectWindow : Window {
274 static const int LEFT_OFFSET = 5; ///< Position of left edge
275 static const int RIGHT_OFFSET = 5; ///< Position of right edge
276 static const int TOP_OFFSET = 5; ///< Position of top edge
277 static const int BOTTOM_OFFSET = 5; ///< Position of bottom edge
279 /** The value for the variable 60 parameters. */
280 static uint32 var60params[GSF_FAKE_END][0x20];
282 /** GRFID of the caller of this window, 0 if it has no caller. */
283 uint32 caller_grfid;
285 /** For ground vehicles: Index in vehicle chain. */
286 uint chain_index;
288 /** The currently edited parameter, to update the right one. */
289 byte current_edit_param;
291 Scrollbar *vscroll;
294 * Check whether the given variable has a parameter.
295 * @param variable the variable to check.
296 * @return true iff the variable has a parameter.
298 static bool HasVariableParameter(uint variable)
300 return IsInsideBS(variable, 0x60, 0x20);
304 * Set the GRFID of the item opening this window.
305 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
307 void SetCallerGRFID(uint32 grfid)
309 this->caller_grfid = grfid;
310 this->SetDirty();
314 * Check whether this feature has chain index, i.e. refers to ground vehicles.
316 bool HasChainIndex() const
318 GrfSpecFeature f = GetFeatureNum(this->window_number);
319 return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
323 * Get the feature index.
324 * @return the feature index
326 uint GetFeatureIndex() const
328 uint index = ::GetFeatureIndex(this->window_number);
329 if (this->chain_index > 0) {
330 assert(this->HasChainIndex());
331 const Vehicle *v = Vehicle::Get(index);
332 v = v->Move(this->chain_index);
333 if (v != NULL) index = v->index;
335 return index;
339 * Ensure that this->chain_index is in range.
341 void ValidateChainIndex()
343 if (this->chain_index == 0) return;
345 assert(this->HasChainIndex());
347 const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
348 v = v->Move(this->chain_index);
349 if (v == NULL) this->chain_index = 0;
352 NewGRFInspectWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
354 this->CreateNestedTree();
355 this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
356 this->FinishInitNested(wno);
358 this->vscroll->SetCount(0);
359 this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
361 this->OnInvalidateData(0, true);
364 virtual void SetStringParameters(int widget) const
366 if (widget != WID_NGRFI_CAPTION) return;
368 GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
371 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
373 switch (widget) {
374 case WID_NGRFI_VEH_CHAIN: {
375 assert(this->HasChainIndex());
376 GrfSpecFeature f = GetFeatureNum(this->window_number);
377 size->height = max(size->height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WD_BEVEL_TOP + WD_BEVEL_BOTTOM);
378 break;
381 case WID_NGRFI_MAINPANEL:
382 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
383 resize->width = 1;
385 size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
386 break;
391 * Helper function to draw a string (line) in the window.
392 * @param r The (screen) rectangle we must draw within
393 * @param offset The offset (in lines) we want to draw for
394 * @param format The format string
396 void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
398 char buf[1024];
400 va_list va;
401 va_start(va, format);
402 vsnprintf(buf, lengthof(buf), format, va);
403 va_end(va);
405 offset -= this->vscroll->GetPosition();
406 if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
408 ::DrawString(r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
411 virtual void DrawWidget(const Rect &r, int widget) const
413 switch (widget) {
414 case WID_NGRFI_VEH_CHAIN: {
415 const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
416 int total_width = 0;
417 int sel_start = 0;
418 int sel_end = 0;
419 for (const Vehicle *u = v->First(); u != NULL; u = u->Next()) {
420 if (u == v) sel_start = total_width;
421 switch (u->type) {
422 case VEH_TRAIN: total_width += Train ::From(u)->GetDisplayImageWidth(); break;
423 case VEH_ROAD: total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
424 default: NOT_REACHED();
426 if (u == v) sel_end = total_width;
429 int width = r.right + 1 - r.left - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
430 int skip = 0;
431 if (total_width > width) {
432 int sel_center = (sel_start + sel_end) / 2;
433 if (sel_center > width / 2) skip = min(total_width - width, sel_center - width / 2);
436 GrfSpecFeature f = GetFeatureNum(this->window_number);
437 int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
438 int y = (r.top + r.bottom - h) / 2;
439 DrawVehicleImage(v->First(), r.left + WD_BEVEL_LEFT, r.right - WD_BEVEL_RIGHT, y + 1, INVALID_VEHICLE, EIT_IN_DETAILS, skip);
441 /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
442 if (_current_text_dir == TD_RTL) {
443 DrawFrameRect(r.right - sel_end + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
444 } else {
445 DrawFrameRect(r.left + sel_start - skip, y, r.left + sel_end - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
447 break;
451 if (widget != WID_NGRFI_MAINPANEL) return;
453 uint index = this->GetFeatureIndex();
454 const NIFeature *nif = GetFeature(this->window_number);
455 const NIHelper *nih = nif->helper;
456 const void *base = nih->GetInstance(index);
457 const void *base_spec = nih->GetSpec(index);
459 uint i = 0;
460 if (nif->variables != NULL) {
461 this->DrawString(r, i++, "Variables:");
462 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
463 bool avail = true;
464 uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
465 uint value = nih->Resolve(index, niv->var, param, &avail);
467 if (!avail) continue;
469 if (HasVariableParameter(niv->var)) {
470 this->DrawString(r, i++, " %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
471 } else {
472 this->DrawString(r, i++, " %02x: %08x (%s)", niv->var, value, niv->name);
477 uint psa_size = nih->GetPSASize(index, this->caller_grfid);
478 const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
479 if (psa_size != 0 && psa != NULL) {
480 if (nih->PSAWithParameter()) {
481 this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
482 } else {
483 this->DrawString(r, i++, "Persistent storage:");
485 assert(psa_size % 4 == 0);
486 for (uint j = 0; j < psa_size; j += 4, psa += 4) {
487 this->DrawString(r, i++, " %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
491 if (nif->properties != NULL) {
492 this->DrawString(r, i++, "Properties:");
493 for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
494 const void *ptr = (const byte *)base + nip->offset;
495 uint value;
496 switch (nip->read_size) {
497 case 1: value = *(const uint8 *)ptr; break;
498 case 2: value = *(const uint16 *)ptr; break;
499 case 4: value = *(const uint32 *)ptr; break;
500 default: NOT_REACHED();
503 StringID string;
504 SetDParam(0, value);
505 switch (nip->type) {
506 case NIT_INT:
507 string = STR_JUST_INT;
508 break;
510 case NIT_CARGO:
511 string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
512 break;
514 default:
515 NOT_REACHED();
518 char buffer[64];
519 GetString(buffer, string, lastof(buffer));
520 this->DrawString(r, i++, " %02x: %s (%s)", nip->prop, buffer, nip->name);
524 if (nif->callbacks != NULL) {
525 this->DrawString(r, i++, "Callbacks:");
526 for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
527 if (nic->cb_bit != CBM_NO_BIT) {
528 const void *ptr = (const byte *)base_spec + nic->offset;
529 uint value;
530 switch (nic->read_size) {
531 case 1: value = *(const uint8 *)ptr; break;
532 case 2: value = *(const uint16 *)ptr; break;
533 case 4: value = *(const uint32 *)ptr; break;
534 default: NOT_REACHED();
537 if (!HasBit(value, nic->cb_bit)) continue;
538 this->DrawString(r, i++, " %03x: %s", nic->cb_id, nic->name);
539 } else {
540 this->DrawString(r, i++, " %03x: %s (unmasked)", nic->cb_id, nic->name);
545 /* Not nice and certainly a hack, but it beats duplicating
546 * this whole function just to count the actual number of
547 * elements. Especially because they need to be redrawn. */
548 const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
551 virtual void OnClick(Point pt, int widget, int click_count)
553 switch (widget) {
554 case WID_NGRFI_PARENT: {
555 const NIHelper *nih = GetFeatureHelper(this->window_number);
556 uint index = nih->GetParent(this->GetFeatureIndex());
557 ::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
558 break;
561 case WID_NGRFI_VEH_PREV:
562 if (this->chain_index > 0) {
563 this->chain_index--;
564 this->InvalidateData();
566 break;
568 case WID_NGRFI_VEH_NEXT:
569 if (this->HasChainIndex()) {
570 uint index = this->GetFeatureIndex();
571 Vehicle *v = Vehicle::Get(index);
572 if (v != NULL && v->Next() != NULL) {
573 this->chain_index++;
574 this->InvalidateData();
577 break;
579 case WID_NGRFI_MAINPANEL: {
580 /* Does this feature have variables? */
581 const NIFeature *nif = GetFeature(this->window_number);
582 if (nif->variables == NULL) return;
584 /* Get the line, make sure it's within the boundaries. */
585 int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
586 if (line == INT_MAX) return;
588 /* Find the variable related to the line */
589 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
590 if (line != 1) continue; // 1 because of the "Variables:" line
592 if (!HasVariableParameter(niv->var)) break;
594 this->current_edit_param = niv->var;
595 ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
601 virtual void OnQueryTextFinished(char *str)
603 if (StrEmpty(str)) return;
605 NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
606 this->SetDirty();
609 virtual void OnResize()
611 this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
615 * Some data on this window has become invalid.
616 * @param data Information about the changed data.
617 * @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.
619 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
621 if (!gui_scope) return;
622 if (this->HasChainIndex()) {
623 this->ValidateChainIndex();
624 this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV, this->chain_index == 0);
625 Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
626 this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == NULL || v->Next() == NULL);
631 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
633 static const NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
634 NWidget(NWID_HORIZONTAL),
635 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
636 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
637 NWidget(WWT_SHADEBOX, COLOUR_GREY),
638 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
639 NWidget(WWT_STICKYBOX, COLOUR_GREY),
640 EndContainer(),
641 NWidget(WWT_PANEL, COLOUR_GREY),
642 NWidget(NWID_HORIZONTAL),
643 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_PREV), SetDataTip(AWV_DECREASE, STR_NULL),
644 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_NEXT), SetDataTip(AWV_INCREASE, STR_NULL),
645 NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
646 EndContainer(),
647 EndContainer(),
648 NWidget(NWID_HORIZONTAL),
649 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
650 NWidget(NWID_VERTICAL),
651 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
652 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
653 EndContainer(),
654 EndContainer(),
657 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
658 NWidget(NWID_HORIZONTAL),
659 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
660 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
661 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
662 NWidget(WWT_SHADEBOX, COLOUR_GREY),
663 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
664 NWidget(WWT_STICKYBOX, COLOUR_GREY),
665 EndContainer(),
666 NWidget(NWID_HORIZONTAL),
667 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
668 NWidget(NWID_VERTICAL),
669 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
670 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
671 EndContainer(),
672 EndContainer(),
675 static WindowDesc _newgrf_inspect_chain_desc(
676 WDP_AUTO, "newgrf_inspect_chain", 400, 300,
677 WC_NEWGRF_INSPECT, WC_NONE,
679 _nested_newgrf_inspect_chain_widgets, lengthof(_nested_newgrf_inspect_chain_widgets)
682 static WindowDesc _newgrf_inspect_desc(
683 WDP_AUTO, "newgrf_inspect", 400, 300,
684 WC_NEWGRF_INSPECT, WC_NONE,
686 _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
690 * Show the inspect window for a given feature and index.
691 * The index is normally an in-game location/identifier, such
692 * as a TileIndex or an IndustryID depending on the feature
693 * we want to inspect.
694 * @param feature The feature we want to inspect.
695 * @param index The index/identifier of the feature to inspect.
696 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
698 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
700 if (!IsNewGRFInspectable(feature, index)) return;
702 WindowNumber wno = GetInspectWindowNumber(feature, index);
703 NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(feature == GSF_TRAINS || feature == GSF_ROADVEHICLES ? &_newgrf_inspect_chain_desc : &_newgrf_inspect_desc, wno);
704 if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
705 w->SetCallerGRFID(grfid);
709 * Invalidate the inspect window for a given feature and index.
710 * The index is normally an in-game location/identifier, such
711 * as a TileIndex or an IndustryID depending on the feature
712 * we want to inspect.
713 * @param feature The feature we want to invalidate the window for.
714 * @param index The index/identifier of the feature to invalidate.
716 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
718 if (feature == GSF_INVALID) return;
720 WindowNumber wno = GetInspectWindowNumber(feature, index);
721 InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
725 * Delete inspect window for a given feature and index.
726 * The index is normally an in-game location/identifier, such
727 * as a TileIndex or an IndustryID depending on the feature
728 * we want to inspect.
729 * @param feature The feature we want to delete the window for.
730 * @param index The index/identifier of the feature to delete.
732 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
734 if (feature == GSF_INVALID) return;
736 WindowNumber wno = GetInspectWindowNumber(feature, index);
737 DeleteWindowById(WC_NEWGRF_INSPECT, wno);
739 /* Reinitialise the land information window to remove the "debug" sprite if needed.
740 * Note: Since we might be called from a command here, it is important to not execute
741 * the invalidation immediately. The landinfo window tests commands itself. */
742 InvalidateWindowData(WC_LAND_INFO, 0, 1);
746 * Can we inspect the data given a certain feature and index.
747 * The index is normally an in-game location/identifier, such
748 * as a TileIndex or an IndustryID depending on the feature
749 * we want to inspect.
750 * @param feature The feature we want to inspect.
751 * @param index The index/identifier of the feature to inspect.
752 * @return true if there is something to show.
754 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
756 const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
757 if (nif == NULL) return false;
758 return nif->helper->IsInspectable(index);
762 * Get the GrfSpecFeature associated with the tile.
763 * @param tile The tile to get the feature from.
764 * @return the GrfSpecFeature.
766 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
768 if (IsHouseTile(tile)) {
769 return GSF_HOUSES;
770 } else if (IsIndustryTile(tile)) {
771 return GSF_INDUSTRYTILES;
774 switch (GetTileType(tile)) {
775 default: return GSF_INVALID;
776 case TT_RAILWAY: return IsTileSubtype(tile, TT_TRACK) ? GSF_RAILTYPES : GSF_INVALID;
777 case TT_MISC: return (IsRailDepotTile(tile) || IsLevelCrossingTile(tile)) ? GSF_RAILTYPES : GSF_INVALID;
778 case TT_OBJECT: return GSF_OBJECTS;
780 case TT_STATION:
781 switch (GetStationType(tile)) {
782 case STATION_RAIL: return GSF_STATIONS;
783 case STATION_AIRPORT: return GSF_AIRPORTTILES;
784 default: return GSF_INVALID;
790 * Get the GrfSpecFeature associated with the vehicle.
791 * @param type The vehicle type to get the feature from.
792 * @return the GrfSpecFeature.
794 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
796 switch (type) {
797 case VEH_TRAIN: return GSF_TRAINS;
798 case VEH_ROAD: return GSF_ROADVEHICLES;
799 case VEH_SHIP: return GSF_SHIPS;
800 case VEH_AIRCRAFT: return GSF_AIRCRAFT;
801 default: return GSF_INVALID;
807 /**** Sprite Aligner ****/
809 /** Window used for aligning sprites. */
810 struct SpriteAlignerWindow : Window {
811 SpriteID current_sprite; ///< The currently shown sprite
812 Scrollbar *vscroll;
814 SpriteAlignerWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
816 this->CreateNestedTree();
817 this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
818 this->FinishInitNested(wno);
820 /* Oh yes, we assume there is at least one normal sprite! */
821 while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
824 virtual void SetStringParameters(int widget) const
826 switch (widget) {
827 case WID_SA_CAPTION:
828 SetDParam(0, this->current_sprite);
829 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
830 break;
832 case WID_SA_OFFSETS: {
833 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
834 SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
835 SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
836 break;
839 default:
840 break;
844 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
846 if (widget != WID_SA_LIST) return;
848 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
849 resize->width = 1;
851 /* Resize to about 200 pixels (for the preview) */
852 size->height = (1 + 200 / resize->height) * resize->height;
855 virtual void DrawWidget(const Rect &r, int widget) const
857 switch (widget) {
858 case WID_SA_SPRITE: {
859 /* Center the sprite ourselves */
860 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
861 int width = r.right - r.left + 1;
862 int height = r.bottom - r.top + 1;
863 int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width - spr->width / ZOOM_LVL_BASE) / 2;
864 int y = r.top - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
866 /* And draw only the part within the sprite area */
867 SubSprite subspr = {
868 spr->x_offs + (spr->width - width * ZOOM_LVL_BASE) / 2 + 1,
869 spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
870 spr->x_offs + (spr->width + width * ZOOM_LVL_BASE) / 2 - 1,
871 spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
874 DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
875 break;
878 case WID_SA_LIST: {
879 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
880 int step_size = nwid->resize_y;
882 SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
883 int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
885 int y = r.top + WD_FRAMERECT_TOP;
886 for (int i = this->vscroll->GetPosition(); i < max; i++) {
887 SetDParam(0, list[i]);
888 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
889 y += step_size;
891 break;
896 virtual void OnClick(Point pt, int widget, int click_count)
898 switch (widget) {
899 case WID_SA_PREVIOUS:
900 do {
901 this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
902 } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
903 this->SetDirty();
904 break;
906 case WID_SA_GOTO:
907 ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
908 break;
910 case WID_SA_NEXT:
911 do {
912 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
913 } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
914 this->SetDirty();
915 break;
917 case WID_SA_PICKER:
918 this->LowerWidget(WID_SA_PICKER);
919 _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
920 this->SetDirty();
921 break;
923 case WID_SA_LIST: {
924 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
925 int step_size = nwid->resize_y;
927 uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
928 if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
929 SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
930 if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
932 this->SetDirty();
933 break;
936 case WID_SA_UP:
937 case WID_SA_DOWN:
938 case WID_SA_LEFT:
939 case WID_SA_RIGHT: {
941 * Yes... this is a hack.
943 * No... I don't think it is useful to make this less of a hack.
945 * If you want to align sprites, you just need the number. Generally
946 * the sprite caches are big enough to not remove the sprite from the
947 * cache. If that's not the case, just let the NewGRF developer
948 * increase the cache size instead of storing thousands of offsets
949 * for the incredibly small chance that it's actually going to be
950 * used by someone and the sprite cache isn't big enough for that
951 * particular NewGRF developer.
953 Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
954 switch (widget) {
955 case WID_SA_UP: spr->y_offs -= ZOOM_LVL_BASE; break;
956 case WID_SA_DOWN: spr->y_offs += ZOOM_LVL_BASE; break;
957 case WID_SA_LEFT: spr->x_offs -= ZOOM_LVL_BASE; break;
958 case WID_SA_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
960 /* Of course, we need to redraw the sprite, but where is it used?
961 * Everywhere is a safe bet. */
962 MarkWholeScreenDirty();
963 break;
968 virtual void OnQueryTextFinished(char *str)
970 if (StrEmpty(str)) return;
972 this->current_sprite = atoi(str);
973 if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
974 while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
975 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
977 this->SetDirty();
981 * Some data on this window has become invalid.
982 * @param data Information about the changed data.
983 * @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.
985 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
987 if (!gui_scope) return;
988 if (data == 1) {
989 /* Sprite picker finished */
990 this->RaiseWidget(WID_SA_PICKER);
991 this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
995 virtual void OnResize()
997 this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
1001 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
1002 NWidget(NWID_HORIZONTAL),
1003 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1004 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1005 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1006 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1007 EndContainer(),
1008 NWidget(WWT_PANEL, COLOUR_GREY),
1009 NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
1010 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1011 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
1012 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
1013 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
1014 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
1015 EndContainer(),
1016 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1017 NWidget(NWID_SPACER), SetFill(1, 1),
1018 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1019 NWidget(NWID_SPACER), SetFill(1, 1),
1020 EndContainer(),
1021 NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
1022 NWidget(NWID_VERTICAL),
1023 NWidget(NWID_SPACER), SetFill(1, 1),
1024 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1025 NWidget(NWID_SPACER), SetFill(1, 1),
1026 EndContainer(),
1027 NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
1028 EndContainer(),
1029 NWidget(NWID_VERTICAL),
1030 NWidget(NWID_SPACER), SetFill(1, 1),
1031 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1032 NWidget(NWID_SPACER), SetFill(1, 1),
1033 EndContainer(),
1034 EndContainer(),
1035 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1036 NWidget(NWID_SPACER), SetFill(1, 1),
1037 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1038 NWidget(NWID_SPACER), SetFill(1, 1),
1039 EndContainer(),
1040 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1041 NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
1042 EndContainer(),
1043 EndContainer(),
1044 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1045 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
1046 NWidget(NWID_HORIZONTAL),
1047 NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
1048 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
1049 EndContainer(),
1050 EndContainer(),
1051 EndContainer(),
1052 EndContainer(),
1055 static WindowDesc _sprite_aligner_desc(
1056 WDP_AUTO, "sprite_aligner", 400, 300,
1057 WC_SPRITE_ALIGNER, WC_NONE,
1059 _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
1063 * Show the window for aligning sprites.
1065 void ShowSpriteAlignerWindow()
1067 AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);