Translations update
[openttd/fttd.git] / src / newgrf_debug_gui.cpp
blob37a1d165ed8e08a79a96db2da3e618738fc1977a
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.h"
19 #include "strings_func.h"
20 #include "textbuf_gui.h"
21 #include "vehicle_gui.h"
22 #include "zoom_func.h"
24 #include "engine_base.h"
25 #include "industry.h"
26 #include "object_base.h"
27 #include "station_base.h"
28 #include "town.h"
29 #include "vehicle_base.h"
30 #include "map/depot.h"
31 #include "train.h"
32 #include "roadveh.h"
34 #include "newgrf_airporttiles.h"
35 #include "newgrf_debug.h"
36 #include "newgrf_object.h"
37 #include "newgrf_spritegroup.h"
38 #include "newgrf_station.h"
39 #include "newgrf_town.h"
40 #include "newgrf_railtype.h"
41 #include "newgrf_industries.h"
42 #include "newgrf_industrytiles.h"
44 #include "widgets/newgrf_debug_widget.h"
46 #include "table/strings.h"
48 /** The sprite picker. */
49 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
51 /**
52 * Get the feature index related to the window number.
53 * @param window_number The window to get the feature index from.
54 * @return the feature index
56 static inline uint GetFeatureIndex(uint window_number)
58 return GB(window_number, 0, 24);
61 /**
62 * Get the window number for the inspect window given a
63 * feature and index.
64 * @param feature The feature we want to inspect.
65 * @param index The index/identifier of the feature to inspect.
66 * @return the InspectWindow (Window)Number
68 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
70 assert((index >> 24) == 0);
71 return (feature << 24) | index;
74 /**
75 * The type of a property to show. This is used to
76 * provide an appropriate representation in the GUI.
78 enum NIType {
79 NIT_INT, ///< The property is a simple integer
80 NIT_CARGO, ///< The property is a cargo
83 /** Representation of the data from a NewGRF property. */
84 struct NIProperty {
85 const char *name; ///< A (human readable) name for the property
86 ptrdiff_t offset; ///< Offset of the variable in the class
87 byte read_size; ///< Number of bytes (i.e. byte, word, dword etc)
88 byte prop; ///< The number of the property
89 byte type;
93 /**
94 * Representation of the available callbacks with
95 * information on when they actually apply.
97 struct NICallback {
98 const char *name; ///< The human readable name of the callback
99 ptrdiff_t offset; ///< Offset of the variable in the class
100 byte read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read
101 byte cb_bit; ///< The bit that needs to be set for this callback to be enabled
102 uint16 cb_id; ///< The number of the callback
104 /** Mask to show no bit needs to be enabled for the callback. */
105 static const int CBM_NO_BIT = UINT8_MAX;
107 /** Representation on the NewGRF variables. */
108 struct NIVariable {
109 const char *name;
110 byte var;
113 /** Helper class to wrap some functionality/queries in. */
114 class NIHelper {
115 public:
116 /** Silence a warning. */
117 virtual ~NIHelper() {}
120 * Is the item with the given index inspectable?
121 * @param index the index to check.
122 * @return true iff the index is inspectable.
124 virtual bool IsInspectable(uint index) const = 0;
127 * Get the parent "window_number" of a given instance.
128 * @param index the instance to get the parent for.
129 * @return the parent's window_number or UINT32_MAX if there is none.
131 virtual uint GetParent(uint index) const = 0;
134 * Get the instance given an index.
135 * @param index the index to get the instance for.
136 * @return the instance.
138 virtual const void *GetInstance(uint index) const = 0;
141 * Get (NewGRF) specs given an index.
142 * @param index the index to get the specs for for.
143 * @return the specs.
145 virtual const void *GetSpec(uint index) const = 0;
148 * Set the string parameters to write the right data for a STRINGn.
149 * @param index the index to get the string parameters for.
151 virtual void SetStringParameters(uint index) const = 0;
154 * Get the GRFID of the file that includes this item.
155 * @param index index to check.
156 * @return GRFID of the item. 0 means that the item is not inspectable.
158 virtual uint32 GetGRFID(uint index) const = 0;
161 * Resolve (action2) variable for a given index.
162 * @param index The (instance) index to resolve the variable for.
163 * @param var The variable to actually resolve.
164 * @param param The varaction2 0x60+x parameter to pass.
165 * @param avail Return whether the variable is available.
166 * @return The resolved variable's value.
168 virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
171 * Used to decide if the PSA needs a parameter or not.
172 * @return True iff this item has a PSA that requires a parameter.
174 virtual bool PSAWithParameter() const
176 return false;
180 * Allows to know the size of the persistent storage.
181 * @param index Index of the item.
182 * @param grfid Parameter for the PSA. Only required for items with parameters.
183 * @return Size of the persistent storage in indices.
185 virtual uint GetPSASize(uint index, uint32 grfid) const
187 return 0;
191 * Gets the first position of the array containing the persistent storage.
192 * @param index Index of the item.
193 * @param grfid Parameter for the PSA. Only required for items with parameters.
194 * @return Pointer to the first position of the storage array or NULL if not present.
196 virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
198 return NULL;
201 protected:
203 * Helper to make setting the strings easier.
204 * @param string the string to actually draw.
205 * @param index the (instance) index for the string.
207 void SetSimpleStringParameters(StringID string, uint32 index) const
209 SetDParam(0, string);
210 SetDParam(1, index);
215 * Helper to make setting the strings easier for objects at a specific tile.
216 * @param string the string to draw the object's name
217 * @param index the (instance) index for the string.
218 * @param tile the tile the object is at
220 void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
222 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
223 SetDParam(1, string);
224 SetDParam(2, index);
225 SetDParam(3, tile);
230 /** Container for all information for a given feature. */
231 struct NIFeature {
232 const NIProperty *properties; ///< The properties associated with this feature.
233 const NICallback *callbacks; ///< The callbacks associated with this feature.
234 const NIVariable *variables; ///< The variables associated with this feature.
235 const NIHelper *helper; ///< The class container all helper functions.
238 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
239 #include "table/newgrf_debug_data.h"
242 * Get the feature number related to the window number.
243 * @param window_number The window to get the feature number for.
244 * @return The feature number.
246 static inline GrfSpecFeature GetFeatureNum(uint window_number)
248 return (GrfSpecFeature)GB(window_number, 24, 8);
252 * Get the NIFeature related to the window number.
253 * @param window_number The window to get the NIFeature for.
254 * @return the NIFeature, or NULL is there isn't one.
256 static inline const NIFeature *GetFeature(uint window_number)
258 GrfSpecFeature idx = GetFeatureNum(window_number);
259 return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
263 * Get the NIHelper related to the window number.
264 * @param window_number The window to get the NIHelper for.
265 * @pre GetFeature(window_number) != NULL
266 * @return the NIHelper
268 static inline const NIHelper *GetFeatureHelper(uint window_number)
270 return GetFeature(window_number)->helper;
273 /** Window used for inspecting NewGRFs. */
274 struct NewGRFInspectWindow : Window {
275 static const int LEFT_OFFSET = 5; ///< Position of left edge
276 static const int RIGHT_OFFSET = 5; ///< Position of right edge
277 static const int TOP_OFFSET = 5; ///< Position of top edge
278 static const int BOTTOM_OFFSET = 5; ///< Position of bottom edge
280 /** The value for the variable 60 parameters. */
281 static uint32 var60params[GSF_FAKE_END][0x20];
283 /** GRFID of the caller of this window, 0 if it has no caller. */
284 uint32 caller_grfid;
286 /** For ground vehicles: Index in vehicle chain. */
287 uint chain_index;
289 /** The currently edited parameter, to update the right one. */
290 byte current_edit_param;
292 Scrollbar *vscroll;
295 * Check whether the given variable has a parameter.
296 * @param variable the variable to check.
297 * @return true iff the variable has a parameter.
299 static bool HasVariableParameter(uint variable)
301 return IsInsideBS(variable, 0x60, 0x20);
305 * Set the GRFID of the item opening this window.
306 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
308 void SetCallerGRFID(uint32 grfid)
310 this->caller_grfid = grfid;
311 this->SetDirty();
315 * Check whether this feature has chain index, i.e. refers to ground vehicles.
317 bool HasChainIndex() const
319 GrfSpecFeature f = GetFeatureNum(this->window_number);
320 return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
324 * Get the feature index.
325 * @return the feature index
327 uint GetFeatureIndex() const
329 uint index = ::GetFeatureIndex(this->window_number);
330 if (this->chain_index > 0) {
331 assert(this->HasChainIndex());
332 const Vehicle *v = Vehicle::Get(index);
333 v = v->Move(this->chain_index);
334 if (v != NULL) index = v->index;
336 return index;
340 * Ensure that this->chain_index is in range.
342 void ValidateChainIndex()
344 if (this->chain_index == 0) return;
346 assert(this->HasChainIndex());
348 const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
349 v = v->Move(this->chain_index);
350 if (v == NULL) this->chain_index = 0;
353 NewGRFInspectWindow (const WindowDesc *desc, WindowNumber wno) :
354 Window (desc), caller_grfid (0), chain_index (0),
355 current_edit_param (0), vscroll (NULL)
357 this->CreateNestedTree();
358 this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
359 this->InitNested(wno);
361 this->vscroll->SetCount(0);
362 this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
364 this->OnInvalidateData(0, true);
367 virtual void SetStringParameters(int widget) const
369 if (widget != WID_NGRFI_CAPTION) return;
371 GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
374 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
376 switch (widget) {
377 case WID_NGRFI_VEH_CHAIN: {
378 assert(this->HasChainIndex());
379 GrfSpecFeature f = GetFeatureNum(this->window_number);
380 size->height = max(size->height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WD_BEVEL_TOP + WD_BEVEL_BOTTOM);
381 break;
384 case WID_NGRFI_MAINPANEL:
385 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
386 resize->width = 1;
388 size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
389 break;
394 * Helper function to draw a string (line) in the window.
395 * @param dpi The area to draw on
396 * @param r The (screen) rectangle we must draw within
397 * @param offset The offset (in lines) we want to draw for
398 * @param format The format string
400 void WARN_FORMAT(5, 6) DrawString (BlitArea *dpi, const Rect &r,
401 int offset, const char *format, ...) const
403 char buf[1024];
405 va_list va;
406 va_start(va, format);
407 bstrvfmt (buf, format, va);
408 va_end(va);
410 offset -= this->vscroll->GetPosition();
411 if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
413 ::DrawString (dpi, r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
416 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
418 switch (widget) {
419 case WID_NGRFI_VEH_CHAIN: {
420 const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
421 int total_width = 0;
422 int sel_start = 0;
423 int sel_end = 0;
424 for (const Vehicle *u = v->First(); u != NULL; u = u->Next()) {
425 if (u == v) sel_start = total_width;
426 switch (u->type) {
427 case VEH_TRAIN: total_width += Train ::From(u)->GetDisplayImageWidth(); break;
428 case VEH_ROAD: total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
429 default: NOT_REACHED();
431 if (u == v) sel_end = total_width;
434 int width = r.right + 1 - r.left - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
435 int skip = 0;
436 if (total_width > width) {
437 int sel_center = (sel_start + sel_end) / 2;
438 if (sel_center > width / 2) skip = min(total_width - width, sel_center - width / 2);
441 GrfSpecFeature f = GetFeatureNum(this->window_number);
442 int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
443 int y = (r.top + r.bottom - h) / 2;
444 DrawVehicleImage (v->First(), dpi, r.left + WD_BEVEL_LEFT, r.right - WD_BEVEL_RIGHT, y + 1, EIT_IN_DETAILS, skip);
446 /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
447 if (_current_text_dir == TD_RTL) {
448 DrawFrameRect (dpi, r.right - sel_end + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
449 } else {
450 DrawFrameRect (dpi, r.left + sel_start - skip, y, r.left + sel_end - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
452 break;
456 if (widget != WID_NGRFI_MAINPANEL) return;
458 uint index = this->GetFeatureIndex();
459 const NIFeature *nif = GetFeature(this->window_number);
460 const NIHelper *nih = nif->helper;
461 const void *base = nih->GetInstance(index);
462 const void *base_spec = nih->GetSpec(index);
464 uint i = 0;
465 if (nif->variables != NULL) {
466 this->DrawString (dpi, r, i++, "Variables:");
467 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
468 bool avail = true;
469 uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
470 uint value = nih->Resolve(index, niv->var, param, &avail);
472 if (!avail) continue;
474 if (HasVariableParameter(niv->var)) {
475 this->DrawString (dpi, r, i++, " %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
476 } else {
477 this->DrawString (dpi, r, i++, " %02x: %08x (%s)", niv->var, value, niv->name);
482 uint psa_size = nih->GetPSASize(index, this->caller_grfid);
483 const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
484 if (psa_size != 0 && psa != NULL) {
485 if (nih->PSAWithParameter()) {
486 this->DrawString (dpi, r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
487 } else {
488 this->DrawString (dpi, r, i++, "Persistent storage:");
490 assert(psa_size % 4 == 0);
491 for (uint j = 0; j < psa_size; j += 4, psa += 4) {
492 this->DrawString (dpi, r, i++, " %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
496 if (nif->properties != NULL) {
497 this->DrawString (dpi, r, i++, "Properties:");
498 for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
499 const void *ptr = (const byte *)base + nip->offset;
500 uint value;
501 switch (nip->read_size) {
502 case 1: value = *(const uint8 *)ptr; break;
503 case 2: value = *(const uint16 *)ptr; break;
504 case 4: value = *(const uint32 *)ptr; break;
505 default: NOT_REACHED();
508 StringID string;
509 SetDParam(0, value);
510 switch (nip->type) {
511 case NIT_INT:
512 string = STR_JUST_INT;
513 break;
515 case NIT_CARGO:
516 string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
517 break;
519 default:
520 NOT_REACHED();
523 char buffer[64];
524 GetString (buffer, string);
525 this->DrawString (dpi, r, i++, " %02x: %s (%s)", nip->prop, buffer, nip->name);
529 if (nif->callbacks != NULL) {
530 this->DrawString (dpi, r, i++, "Callbacks:");
531 for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
532 if (nic->cb_bit != CBM_NO_BIT) {
533 const void *ptr = (const byte *)base_spec + nic->offset;
534 uint value;
535 switch (nic->read_size) {
536 case 1: value = *(const uint8 *)ptr; break;
537 case 2: value = *(const uint16 *)ptr; break;
538 case 4: value = *(const uint32 *)ptr; break;
539 default: NOT_REACHED();
542 if (!HasBit(value, nic->cb_bit)) continue;
543 this->DrawString (dpi, r, i++, " %03x: %s", nic->cb_id, nic->name);
544 } else {
545 this->DrawString (dpi, r, i++, " %03x: %s (unmasked)", nic->cb_id, nic->name);
550 /* Not nice and certainly a hack, but it beats duplicating
551 * this whole function just to count the actual number of
552 * elements. Especially because they need to be redrawn. */
553 const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
556 virtual void OnClick(Point pt, int widget, int click_count)
558 switch (widget) {
559 case WID_NGRFI_PARENT: {
560 const NIHelper *nih = GetFeatureHelper(this->window_number);
561 uint index = nih->GetParent(this->GetFeatureIndex());
562 ::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
563 break;
566 case WID_NGRFI_VEH_PREV:
567 if (this->chain_index > 0) {
568 this->chain_index--;
569 this->InvalidateData();
571 break;
573 case WID_NGRFI_VEH_NEXT:
574 if (this->HasChainIndex()) {
575 uint index = this->GetFeatureIndex();
576 Vehicle *v = Vehicle::Get(index);
577 if (v != NULL && v->Next() != NULL) {
578 this->chain_index++;
579 this->InvalidateData();
582 break;
584 case WID_NGRFI_MAINPANEL: {
585 /* Does this feature have variables? */
586 const NIFeature *nif = GetFeature(this->window_number);
587 if (nif->variables == NULL) return;
589 /* Get the line, make sure it's within the boundaries. */
590 int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
591 if (line == INT_MAX) return;
593 /* Find the variable related to the line */
594 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
595 if (line != 1) continue; // 1 because of the "Variables:" line
597 if (!HasVariableParameter(niv->var)) break;
599 this->current_edit_param = niv->var;
600 ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
606 virtual void OnQueryTextFinished(char *str)
608 if (StrEmpty(str)) return;
610 NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
611 this->SetDirty();
614 virtual void OnResize()
616 this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
620 * Some data on this window has become invalid.
621 * @param data Information about the changed data.
622 * @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.
624 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
626 if (!gui_scope) return;
627 if (this->HasChainIndex()) {
628 this->ValidateChainIndex();
629 this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV, this->chain_index == 0);
630 Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
631 this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == NULL || v->Next() == NULL);
636 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
638 static const NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
639 NWidget(NWID_HORIZONTAL),
640 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
641 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
642 NWidget(WWT_SHADEBOX, COLOUR_GREY),
643 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
644 NWidget(WWT_STICKYBOX, COLOUR_GREY),
645 EndContainer(),
646 NWidget(WWT_PANEL, COLOUR_GREY),
647 NWidget(NWID_HORIZONTAL),
648 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_PREV), SetDataTip(AWV_DECREASE, STR_NULL),
649 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_NEXT), SetDataTip(AWV_INCREASE, STR_NULL),
650 NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
651 EndContainer(),
652 EndContainer(),
653 NWidget(NWID_HORIZONTAL),
654 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
655 NWidget(NWID_VERTICAL),
656 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
657 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
658 EndContainer(),
659 EndContainer(),
662 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
663 NWidget(NWID_HORIZONTAL),
664 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
665 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
666 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
667 NWidget(WWT_SHADEBOX, COLOUR_GREY),
668 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
669 NWidget(WWT_STICKYBOX, COLOUR_GREY),
670 EndContainer(),
671 NWidget(NWID_HORIZONTAL),
672 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
673 NWidget(NWID_VERTICAL),
674 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
675 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
676 EndContainer(),
677 EndContainer(),
680 static WindowDesc::Prefs _newgrf_inspect_chain_prefs ("newgrf_inspect_chain");
682 static const WindowDesc _newgrf_inspect_chain_desc(
683 WDP_AUTO, 400, 300,
684 WC_NEWGRF_INSPECT, WC_NONE,
686 _nested_newgrf_inspect_chain_widgets, lengthof(_nested_newgrf_inspect_chain_widgets),
687 &_newgrf_inspect_chain_prefs
690 static WindowDesc::Prefs _newgrf_inspect_prefs ("newgrf_inspect");
692 static const WindowDesc _newgrf_inspect_desc(
693 WDP_AUTO, 400, 300,
694 WC_NEWGRF_INSPECT, WC_NONE,
696 _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets),
697 &_newgrf_inspect_prefs
701 * Show the inspect window for a given feature and index.
702 * The index is normally an in-game location/identifier, such
703 * as a TileIndex or an IndustryID depending on the feature
704 * we want to inspect.
705 * @param feature The feature we want to inspect.
706 * @param index The index/identifier of the feature to inspect.
707 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
709 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
711 if (!IsNewGRFInspectable(feature, index)) return;
713 WindowNumber wno = GetInspectWindowNumber(feature, index);
714 const WindowDesc *desc = (feature == GSF_TRAINS || feature == GSF_ROADVEHICLES) ? &_newgrf_inspect_chain_desc : &_newgrf_inspect_desc;
715 NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(desc, wno, true);
716 w->SetCallerGRFID(grfid);
720 * Invalidate the inspect window for a given feature and index.
721 * The index is normally an in-game location/identifier, such
722 * as a TileIndex or an IndustryID depending on the feature
723 * we want to inspect.
724 * @param feature The feature we want to invalidate the window for.
725 * @param index The index/identifier of the feature to invalidate.
727 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
729 if (feature == GSF_INVALID) return;
731 WindowNumber wno = GetInspectWindowNumber(feature, index);
732 InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
736 * Delete inspect window for a given feature and index.
737 * The index is normally an in-game location/identifier, such
738 * as a TileIndex or an IndustryID depending on the feature
739 * we want to inspect.
740 * @param feature The feature we want to delete the window for.
741 * @param index The index/identifier of the feature to delete.
743 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
745 if (feature == GSF_INVALID) return;
747 WindowNumber wno = GetInspectWindowNumber(feature, index);
748 DeleteWindowById(WC_NEWGRF_INSPECT, wno);
750 /* Reinitialise the land information window to remove the "debug" sprite if needed.
751 * Note: Since we might be called from a command here, it is important to not execute
752 * the invalidation immediately. The landinfo window tests commands itself. */
753 InvalidateWindowData(WC_LAND_INFO, 0, 1);
757 * Can we inspect the data given a certain feature and index.
758 * The index is normally an in-game location/identifier, such
759 * as a TileIndex or an IndustryID depending on the feature
760 * we want to inspect.
761 * @param feature The feature we want to inspect.
762 * @param index The index/identifier of the feature to inspect.
763 * @return true if there is something to show.
765 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
767 const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
768 if (nif == NULL) return false;
769 return nif->helper->IsInspectable(index);
773 * Get the GrfSpecFeature associated with the tile.
774 * @param tile The tile to get the feature from.
775 * @return the GrfSpecFeature.
777 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
779 if (IsHouseTile(tile)) {
780 return GSF_HOUSES;
781 } else if (IsIndustryTile(tile)) {
782 return GSF_INDUSTRYTILES;
785 switch (GetTileType(tile)) {
786 default: return GSF_INVALID;
787 case TT_RAILWAY: return IsTileSubtype(tile, TT_TRACK) ? GSF_RAILTYPES : GSF_INVALID;
788 case TT_MISC: return (IsRailDepotTile(tile) || IsLevelCrossingTile(tile)) ? GSF_RAILTYPES : GSF_INVALID;
789 case TT_OBJECT: return GSF_OBJECTS;
791 case TT_STATION:
792 switch (GetStationType(tile)) {
793 case STATION_RAIL: return GSF_STATIONS;
794 case STATION_AIRPORT: return GSF_AIRPORTTILES;
795 default: return GSF_INVALID;
801 * Get the GrfSpecFeature associated with the vehicle.
802 * @param type The vehicle type to get the feature from.
803 * @return the GrfSpecFeature.
805 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
807 switch (type) {
808 case VEH_TRAIN: return GSF_TRAINS;
809 case VEH_ROAD: return GSF_ROADVEHICLES;
810 case VEH_SHIP: return GSF_SHIPS;
811 case VEH_AIRCRAFT: return GSF_AIRCRAFT;
812 default: return GSF_INVALID;
818 /**** Sprite Aligner ****/
820 /** Window used for aligning sprites. */
821 struct SpriteAlignerWindow : Window {
822 typedef SmallPair<int16, int16> XyOffs; ///< Pair for x and y offsets of the sprite before alignment. First value contains the x offset, second value y offset.
824 SpriteID current_sprite; ///< The currently shown sprite.
825 Scrollbar *vscroll;
826 SmallMap<SpriteID, XyOffs> offs_start_map; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window.
828 SpriteAlignerWindow (const WindowDesc *desc, WindowNumber wno) :
829 Window (desc), current_sprite (0), vscroll (NULL)
831 this->CreateNestedTree();
832 this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
833 this->InitNested(wno);
835 /* Oh yes, we assume there is at least one normal sprite! */
836 while (!IsNormalSprite (this->current_sprite)) this->current_sprite++;
839 virtual void SetStringParameters(int widget) const
841 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
842 switch (widget) {
843 case WID_SA_CAPTION:
844 SetDParam(0, this->current_sprite);
845 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
846 break;
848 case WID_SA_OFFSETS_ABS:
849 SetDParam(0, spr->x_offs);
850 SetDParam(1, spr->y_offs);
851 break;
853 case WID_SA_OFFSETS_REL: {
854 /* Relative offset is new absolute offset - starting absolute offset.
855 * Show 0, 0 as the relative offsets if entry is not in the map (meaning they have not been changed yet).
857 const SmallPair<SpriteID, XyOffs> *key_offs_pair = this->offs_start_map.Find(this->current_sprite);
858 if (key_offs_pair != this->offs_start_map.End()) {
859 SetDParam(0, spr->x_offs - key_offs_pair->second.first);
860 SetDParam(1, spr->y_offs - key_offs_pair->second.second);
861 } else {
862 SetDParam(0, 0);
863 SetDParam(1, 0);
865 break;
868 default:
869 break;
873 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
875 if (widget != WID_SA_LIST) return;
877 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
878 resize->width = 1;
880 /* Resize to about 200 pixels (for the preview) */
881 size->height = (1 + 200 / resize->height) * resize->height;
884 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
886 switch (widget) {
887 case WID_SA_SPRITE: {
888 /* Center the sprite ourselves */
889 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
890 int width = r.right - r.left + 1 - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
891 int height = r.bottom - r.top + 1 - WD_BEVEL_TOP - WD_BEVEL_BOTTOM;
892 int x = -UnScaleGUI(spr->x_offs) + (width - UnScaleGUI(spr->width) ) / 2;
893 int y = -UnScaleGUI(spr->y_offs) + (height - UnScaleGUI(spr->height)) / 2;
895 BlitArea new_dpi;
896 if (!InitBlitArea (dpi, &new_dpi, r.left + WD_BEVEL_LEFT, r.top + WD_BEVEL_TOP, width, height)) break;
898 DrawSprite (&new_dpi, this->current_sprite, PAL_NONE, x, y);
900 break;
903 case WID_SA_LIST: {
904 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
905 int step_size = nwid->resize_y;
907 SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
908 int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
910 int y = r.top + WD_FRAMERECT_TOP;
911 for (int i = this->vscroll->GetPosition(); i < max; i++) {
912 SetDParam(0, list[i]);
913 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
914 y += step_size;
916 break;
921 virtual void OnClick(Point pt, int widget, int click_count)
923 switch (widget) {
924 case WID_SA_PREVIOUS:
925 do {
926 this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
927 } while (!IsNormalSprite (this->current_sprite));
928 this->SetDirty();
929 break;
931 case WID_SA_GOTO:
932 ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
933 break;
935 case WID_SA_NEXT:
936 do {
937 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
938 } while (!IsNormalSprite (this->current_sprite));
939 this->SetDirty();
940 break;
942 case WID_SA_PICKER:
943 this->LowerWidget(WID_SA_PICKER);
944 _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
945 this->SetDirty();
946 break;
948 case WID_SA_LIST: {
949 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
950 int step_size = nwid->resize_y;
952 uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
953 if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
954 SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
955 if (IsNormalSprite (spr)) this->current_sprite = spr;
957 this->SetDirty();
958 break;
961 case WID_SA_UP:
962 case WID_SA_DOWN:
963 case WID_SA_LEFT:
964 case WID_SA_RIGHT: {
966 * Yes... this is a hack.
968 * No... I don't think it is useful to make this less of a hack.
970 * If you want to align sprites, you just need the number. Generally
971 * the sprite caches are big enough to not remove the sprite from the
972 * cache. If that's not the case, just let the NewGRF developer
973 * increase the cache size instead of storing thousands of offsets
974 * for the incredibly small chance that it's actually going to be
975 * used by someone and the sprite cache isn't big enough for that
976 * particular NewGRF developer.
978 Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
980 /* Remember the original offsets of the current sprite, if not already in mapping. */
981 if (!(this->offs_start_map.Contains(this->current_sprite))) {
982 this->offs_start_map.Insert(this->current_sprite, XyOffs(spr->x_offs, spr->y_offs));
984 switch (widget) {
985 /* Move eight units at a time if ctrl is pressed. */
986 case WID_SA_UP: spr->y_offs -= _ctrl_pressed ? 8 : 1; break;
987 case WID_SA_DOWN: spr->y_offs += _ctrl_pressed ? 8 : 1; break;
988 case WID_SA_LEFT: spr->x_offs -= _ctrl_pressed ? 8 : 1; break;
989 case WID_SA_RIGHT: spr->x_offs += _ctrl_pressed ? 8 : 1; break;
991 /* Of course, we need to redraw the sprite, but where is it used?
992 * Everywhere is a safe bet. */
993 MarkWholeScreenDirty();
994 break;
997 case WID_SA_RESET_REL:
998 /* Reset the starting offsets for the current sprite. */
999 this->offs_start_map.Erase(this->current_sprite);
1000 this->SetDirty();
1001 break;
1005 virtual void OnQueryTextFinished(char *str)
1007 if (StrEmpty(str)) return;
1009 this->current_sprite = atoi(str);
1010 if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
1011 while (!IsNormalSprite (this->current_sprite)) {
1012 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
1014 this->SetDirty();
1018 * Some data on this window has become invalid.
1019 * @param data Information about the changed data.
1020 * @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.
1022 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1024 if (!gui_scope) return;
1025 if (data == 1) {
1026 /* Sprite picker finished */
1027 this->RaiseWidget(WID_SA_PICKER);
1028 this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
1032 virtual void OnResize()
1034 this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
1038 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
1039 NWidget(NWID_HORIZONTAL),
1040 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1041 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1042 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1043 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1044 EndContainer(),
1045 NWidget(WWT_PANEL, COLOUR_GREY),
1046 NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
1047 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1048 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
1049 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
1050 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
1051 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
1052 EndContainer(),
1053 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1054 NWidget(NWID_SPACER), SetFill(1, 1),
1055 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1056 NWidget(NWID_SPACER), SetFill(1, 1),
1057 EndContainer(),
1058 NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
1059 NWidget(NWID_VERTICAL),
1060 NWidget(NWID_SPACER), SetFill(1, 1),
1061 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1062 NWidget(NWID_SPACER), SetFill(1, 1),
1063 EndContainer(),
1064 NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
1065 EndContainer(),
1066 NWidget(NWID_VERTICAL),
1067 NWidget(NWID_SPACER), SetFill(1, 1),
1068 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1069 NWidget(NWID_SPACER), SetFill(1, 1),
1070 EndContainer(),
1071 EndContainer(),
1072 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1073 NWidget(NWID_SPACER), SetFill(1, 1),
1074 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1075 NWidget(NWID_SPACER), SetFill(1, 1),
1076 EndContainer(),
1077 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),
1078 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),
1079 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1080 NWidget(NWID_SPACER), SetFill(1, 1),
1081 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_RESET_REL), SetDataTip(STR_SPRITE_ALIGNER_RESET_BUTTON, STR_SPRITE_ALIGNER_RESET_TOOLTIP), SetFill(0, 0),
1082 NWidget(NWID_SPACER), SetFill(1, 1),
1083 EndContainer(),
1084 EndContainer(),
1085 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1086 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
1087 NWidget(NWID_HORIZONTAL),
1088 NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
1089 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
1090 EndContainer(),
1091 EndContainer(),
1092 EndContainer(),
1093 EndContainer(),
1096 static WindowDesc::Prefs _sprite_aligner_prefs ("sprite_aligner");
1098 static const WindowDesc _sprite_aligner_desc(
1099 WDP_AUTO, 400, 300,
1100 WC_SPRITE_ALIGNER, WC_NONE,
1102 _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets),
1103 &_sprite_aligner_prefs
1107 * Show the window for aligning sprites.
1109 void ShowSpriteAlignerWindow()
1111 AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);