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/>.
10 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
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"
25 #include "object_base.h"
26 #include "station_base.h"
28 #include "vehicle_base.h"
29 #include "map/depot.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>() };
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);
61 * Get the window number for the inspect window given a
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
;
74 * The type of a property to show. This is used to
75 * provide an appropriate representation in the GUI.
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. */
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
93 * Representation of the available callbacks with
94 * information on when they actually apply.
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. */
112 /** Helper class to wrap some functionality/queries in. */
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.
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
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
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
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
);
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
);
229 /** Container for all information for a given feature. */
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. */
285 /** For ground vehicles: Index in vehicle chain. */
288 /** The currently edited parameter, to update the right one. */
289 byte current_edit_param
;
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
;
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
;
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
)
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
);
381 case WID_NGRFI_MAINPANEL
:
382 resize
->height
= max(11, FONT_HEIGHT_NORMAL
+ 1);
385 size
->height
= 5 * resize
->height
+ TOP_OFFSET
+ BOTTOM_OFFSET
;
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
401 va_start(va
, format
);
402 vsnprintf(buf
, lengthof(buf
), format
, 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
414 case WID_NGRFI_VEH_CHAIN
: {
415 const Vehicle
*v
= Vehicle::Get(this->GetFeatureIndex());
419 for (const Vehicle
*u
= v
->First(); u
!= NULL
; u
= u
->Next()) {
420 if (u
== v
) sel_start
= total_width
;
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
;
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
);
445 DrawFrameRect(r
.left
+ sel_start
- skip
, y
, r
.left
+ sel_end
- skip
, y
+ h
, COLOUR_WHITE
, FR_BORDERONLY
);
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
);
460 if (nif
->variables
!= NULL
) {
461 this->DrawString(r
, i
++, "Variables:");
462 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= NULL
; niv
++) {
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
);
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
));
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
;
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();
507 string
= STR_JUST_INT
;
511 string
= value
!= INVALID_CARGO
? CargoSpec::Get(value
)->name
: STR_QUANTITY_N_A
;
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
;
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
);
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
)
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()));
561 case WID_NGRFI_VEH_PREV
:
562 if (this->chain_index
> 0) {
564 this->InvalidateData();
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
) {
574 this->InvalidateData();
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);
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
),
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),
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
),
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
),
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
),
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
)) {
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
;
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
)
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
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
828 SetDParam(0, this->current_sprite
);
829 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite
)));
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
);
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);
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
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 */
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
);
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
);
896 virtual void OnClick(Point pt
, int widget
, int click_count
)
899 case WID_SA_PREVIOUS
:
901 this->current_sprite
= (this->current_sprite
== 0 ? GetMaxSpriteID() : this->current_sprite
) - 1;
902 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
907 ShowQueryString(STR_EMPTY
, STR_SPRITE_ALIGNER_GOTO_CAPTION
, 7, this, CS_NUMERAL
, QSF_NONE
);
912 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
913 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
918 this->LowerWidget(WID_SA_PICKER
);
919 _newgrf_debug_sprite_picker
.mode
= SPM_WAIT_CLICK
;
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
;
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
));
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();
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();
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;
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
),
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),
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),
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),
1027 NWidget(WWT_PANEL
, COLOUR_DARK_BLUE
, WID_SA_SPRITE
), SetDataTip(STR_NULL
, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP
),
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),
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),
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),
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
),
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);