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"
22 #include "zoom_func.h"
24 #include "engine_base.h"
26 #include "object_base.h"
27 #include "station_base.h"
29 #include "vehicle_base.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 #include "safeguards.h"
49 /** The sprite picker. */
50 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker
= { SPM_NONE
, NULL
, 0, SmallVector
<SpriteID
, 256>() };
53 * Get the feature index related to the window number.
54 * @param window_number The window to get the feature index from.
55 * @return the feature index
57 static inline uint
GetFeatureIndex(uint window_number
)
59 return GB(window_number
, 0, 24);
63 * Get the window number for the inspect window given a
65 * @param feature The feature we want to inspect.
66 * @param index The index/identifier of the feature to inspect.
67 * @return the InspectWindow (Window)Number
69 static inline uint
GetInspectWindowNumber(GrfSpecFeature feature
, uint index
)
71 assert((index
>> 24) == 0);
72 return (feature
<< 24) | index
;
76 * The type of a property to show. This is used to
77 * provide an appropriate representation in the GUI.
80 NIT_INT
, ///< The property is a simple integer
81 NIT_CARGO
, ///< The property is a cargo
84 /** Representation of the data from a NewGRF property. */
86 const char *name
; ///< A (human readable) name for the property
87 ptrdiff_t offset
; ///< Offset of the variable in the class
88 byte read_size
; ///< Number of bytes (i.e. byte, word, dword etc)
89 byte prop
; ///< The number of the property
95 * Representation of the available callbacks with
96 * information on when they actually apply.
99 const char *name
; ///< The human readable name of the callback
100 ptrdiff_t offset
; ///< Offset of the variable in the class
101 byte read_size
; ///< The number of bytes (i.e. byte, word, dword etc) to read
102 byte cb_bit
; ///< The bit that needs to be set for this callback to be enabled
103 uint16 cb_id
; ///< The number of the callback
105 /** Mask to show no bit needs to be enabled for the callback. */
106 static const int CBM_NO_BIT
= UINT8_MAX
;
108 /** Representation on the NewGRF variables. */
114 /** Helper class to wrap some functionality/queries in. */
117 /** Silence a warning. */
118 virtual ~NIHelper() {}
121 * Is the item with the given index inspectable?
122 * @param index the index to check.
123 * @return true iff the index is inspectable.
125 virtual bool IsInspectable(uint index
) const = 0;
128 * Get the parent "window_number" of a given instance.
129 * @param index the instance to get the parent for.
130 * @return the parent's window_number or UINT32_MAX if there is none.
132 virtual uint
GetParent(uint index
) const = 0;
135 * Get the instance given an index.
136 * @param index the index to get the instance for.
137 * @return the instance.
139 virtual const void *GetInstance(uint index
) const = 0;
142 * Get (NewGRF) specs given an index.
143 * @param index the index to get the specs for for.
146 virtual const void *GetSpec(uint index
) const = 0;
149 * Set the string parameters to write the right data for a STRINGn.
150 * @param index the index to get the string parameters for.
152 virtual void SetStringParameters(uint index
) const = 0;
155 * Get the GRFID of the file that includes this item.
156 * @param index index to check.
157 * @return GRFID of the item. 0 means that the item is not inspectable.
159 virtual uint32
GetGRFID(uint index
) const = 0;
162 * Resolve (action2) variable for a given index.
163 * @param index The (instance) index to resolve the variable for.
164 * @param var The variable to actually resolve.
165 * @param param The varaction2 0x60+x parameter to pass.
166 * @param avail Return whether the variable is available.
167 * @return The resolved variable's value.
169 virtual uint
Resolve(uint index
, uint var
, uint param
, bool *avail
) const = 0;
172 * Used to decide if the PSA needs a parameter or not.
173 * @return True iff this item has a PSA that requires a parameter.
175 virtual bool PSAWithParameter() const
181 * Allows to know the size of the persistent storage.
182 * @param index Index of the item.
183 * @param grfid Parameter for the PSA. Only required for items with parameters.
184 * @return Size of the persistent storage in indices.
186 virtual uint
GetPSASize(uint index
, uint32 grfid
) const
192 * Gets the first position of the array containing the persistent storage.
193 * @param index Index of the item.
194 * @param grfid Parameter for the PSA. Only required for items with parameters.
195 * @return Pointer to the first position of the storage array or NULL if not present.
197 virtual const int32
*GetPSAFirstPosition(uint index
, uint32 grfid
) const
204 * Helper to make setting the strings easier.
205 * @param string the string to actually draw.
206 * @param index the (instance) index for the string.
208 void SetSimpleStringParameters(StringID string
, uint32 index
) const
210 SetDParam(0, string
);
216 * Helper to make setting the strings easier for objects at a specific tile.
217 * @param string the string to draw the object's name
218 * @param index the (instance) index for the string.
219 * @param tile the tile the object is at
221 void SetObjectAtStringParameters(StringID string
, uint32 index
, TileIndex tile
) const
223 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT
);
224 SetDParam(1, string
);
231 /** Container for all information for a given feature. */
233 const NIProperty
*properties
; ///< The properties associated with this feature.
234 const NICallback
*callbacks
; ///< The callbacks associated with this feature.
235 const NIVariable
*variables
; ///< The variables associated with this feature.
236 const NIHelper
*helper
; ///< The class container all helper functions.
239 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
240 #include "table/newgrf_debug_data.h"
243 * Get the feature number related to the window number.
244 * @param window_number The window to get the feature number for.
245 * @return The feature number.
247 static inline GrfSpecFeature
GetFeatureNum(uint window_number
)
249 return (GrfSpecFeature
)GB(window_number
, 24, 8);
253 * Get the NIFeature related to the window number.
254 * @param window_number The window to get the NIFeature for.
255 * @return the NIFeature, or NULL is there isn't one.
257 static inline const NIFeature
*GetFeature(uint window_number
)
259 GrfSpecFeature idx
= GetFeatureNum(window_number
);
260 return idx
< GSF_FAKE_END
? _nifeatures
[idx
] : NULL
;
264 * Get the NIHelper related to the window number.
265 * @param window_number The window to get the NIHelper for.
266 * @pre GetFeature(window_number) != NULL
267 * @return the NIHelper
269 static inline const NIHelper
*GetFeatureHelper(uint window_number
)
271 return GetFeature(window_number
)->helper
;
274 /** Window used for inspecting NewGRFs. */
275 struct NewGRFInspectWindow
: Window
{
276 static const int LEFT_OFFSET
= 5; ///< Position of left edge
277 static const int RIGHT_OFFSET
= 5; ///< Position of right edge
278 static const int TOP_OFFSET
= 5; ///< Position of top edge
279 static const int BOTTOM_OFFSET
= 5; ///< Position of bottom edge
281 /** The value for the variable 60 parameters. */
282 static uint32 var60params
[GSF_FAKE_END
][0x20];
284 /** GRFID of the caller of this window, 0 if it has no caller. */
287 /** For ground vehicles: Index in vehicle chain. */
290 /** The currently edited parameter, to update the right one. */
291 byte current_edit_param
;
296 * Check whether the given variable has a parameter.
297 * @param variable the variable to check.
298 * @return true iff the variable has a parameter.
300 static bool HasVariableParameter(uint variable
)
302 return IsInsideBS(variable
, 0x60, 0x20);
306 * Set the GRFID of the item opening this window.
307 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
309 void SetCallerGRFID(uint32 grfid
)
311 this->caller_grfid
= grfid
;
316 * Check whether this feature has chain index, i.e. refers to ground vehicles.
318 bool HasChainIndex() const
320 GrfSpecFeature f
= GetFeatureNum(this->window_number
);
321 return f
== GSF_TRAINS
|| f
== GSF_ROADVEHICLES
;
325 * Get the feature index.
326 * @return the feature index
328 uint
GetFeatureIndex() const
330 uint index
= ::GetFeatureIndex(this->window_number
);
331 if (this->chain_index
> 0) {
332 assert(this->HasChainIndex());
333 const Vehicle
*v
= Vehicle::Get(index
);
334 v
= v
->Move(this->chain_index
);
335 if (v
!= NULL
) index
= v
->index
;
341 * Ensure that this->chain_index is in range.
343 void ValidateChainIndex()
345 if (this->chain_index
== 0) return;
347 assert(this->HasChainIndex());
349 const Vehicle
*v
= Vehicle::Get(::GetFeatureIndex(this->window_number
));
350 v
= v
->Move(this->chain_index
);
351 if (v
== NULL
) this->chain_index
= 0;
354 NewGRFInspectWindow(WindowDesc
*desc
, WindowNumber wno
) : Window(desc
)
356 this->CreateNestedTree();
357 this->vscroll
= this->GetScrollbar(WID_NGRFI_SCROLLBAR
);
358 this->FinishInitNested(wno
);
360 this->vscroll
->SetCount(0);
361 this->SetWidgetDisabledState(WID_NGRFI_PARENT
, GetFeatureHelper(this->window_number
)->GetParent(this->GetFeatureIndex()) == UINT32_MAX
);
363 this->OnInvalidateData(0, true);
366 virtual void SetStringParameters(int widget
) const
368 if (widget
!= WID_NGRFI_CAPTION
) return;
370 GetFeatureHelper(this->window_number
)->SetStringParameters(this->GetFeatureIndex());
373 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
376 case WID_NGRFI_VEH_CHAIN
: {
377 assert(this->HasChainIndex());
378 GrfSpecFeature f
= GetFeatureNum(this->window_number
);
379 size
->height
= max(size
->height
, GetVehicleImageCellSize((VehicleType
)(VEH_TRAIN
+ (f
- GSF_TRAINS
)), EIT_IN_DEPOT
).height
+ 2 + WD_BEVEL_TOP
+ WD_BEVEL_BOTTOM
);
383 case WID_NGRFI_MAINPANEL
:
384 resize
->height
= max(11, FONT_HEIGHT_NORMAL
+ 1);
387 size
->height
= 5 * resize
->height
+ TOP_OFFSET
+ BOTTOM_OFFSET
;
393 * Helper function to draw a string (line) in the window.
394 * @param r The (screen) rectangle we must draw within
395 * @param offset The offset (in lines) we want to draw for
396 * @param format The format string
398 void WARN_FORMAT(4, 5) DrawString(const Rect
&r
, int offset
, const char *format
, ...) const
403 va_start(va
, format
);
404 vseprintf(buf
, lastof(buf
), format
, va
);
407 offset
-= this->vscroll
->GetPosition();
408 if (offset
< 0 || offset
>= this->vscroll
->GetCapacity()) return;
410 ::DrawString(r
.left
+ LEFT_OFFSET
, r
.right
- RIGHT_OFFSET
, r
.top
+ TOP_OFFSET
+ (offset
* this->resize
.step_height
), buf
, TC_BLACK
);
413 virtual void DrawWidget(const Rect
&r
, int widget
) const
416 case WID_NGRFI_VEH_CHAIN
: {
417 const Vehicle
*v
= Vehicle::Get(this->GetFeatureIndex());
421 for (const Vehicle
*u
= v
->First(); u
!= NULL
; u
= u
->Next()) {
422 if (u
== v
) sel_start
= total_width
;
424 case VEH_TRAIN
: total_width
+= Train ::From(u
)->GetDisplayImageWidth(); break;
425 case VEH_ROAD
: total_width
+= RoadVehicle::From(u
)->GetDisplayImageWidth(); break;
426 default: NOT_REACHED();
428 if (u
== v
) sel_end
= total_width
;
431 int width
= r
.right
+ 1 - r
.left
- WD_BEVEL_LEFT
- WD_BEVEL_RIGHT
;
433 if (total_width
> width
) {
434 int sel_center
= (sel_start
+ sel_end
) / 2;
435 if (sel_center
> width
/ 2) skip
= min(total_width
- width
, sel_center
- width
/ 2);
438 GrfSpecFeature f
= GetFeatureNum(this->window_number
);
439 int h
= GetVehicleImageCellSize((VehicleType
)(VEH_TRAIN
+ (f
- GSF_TRAINS
)), EIT_IN_DEPOT
).height
;
440 int y
= (r
.top
+ r
.bottom
- h
) / 2;
441 DrawVehicleImage(v
->First(), r
.left
+ WD_BEVEL_LEFT
, r
.right
- WD_BEVEL_RIGHT
, y
+ 1, INVALID_VEHICLE
, EIT_IN_DETAILS
, skip
);
443 /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
444 if (_current_text_dir
== TD_RTL
) {
445 DrawFrameRect(r
.right
- sel_end
+ skip
, y
, r
.right
- sel_start
+ skip
, y
+ h
, COLOUR_WHITE
, FR_BORDERONLY
);
447 DrawFrameRect(r
.left
+ sel_start
- skip
, y
, r
.left
+ sel_end
- skip
, y
+ h
, COLOUR_WHITE
, FR_BORDERONLY
);
453 if (widget
!= WID_NGRFI_MAINPANEL
) return;
455 uint index
= this->GetFeatureIndex();
456 const NIFeature
*nif
= GetFeature(this->window_number
);
457 const NIHelper
*nih
= nif
->helper
;
458 const void *base
= nih
->GetInstance(index
);
459 const void *base_spec
= nih
->GetSpec(index
);
462 if (nif
->variables
!= NULL
) {
463 this->DrawString(r
, i
++, "Variables:");
464 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= NULL
; niv
++) {
466 uint param
= HasVariableParameter(niv
->var
) ? NewGRFInspectWindow::var60params
[GetFeatureNum(this->window_number
)][niv
->var
- 0x60] : 0;
467 uint value
= nih
->Resolve(index
, niv
->var
, param
, &avail
);
469 if (!avail
) continue;
471 if (HasVariableParameter(niv
->var
)) {
472 this->DrawString(r
, i
++, " %02x[%02x]: %08x (%s)", niv
->var
, param
, value
, niv
->name
);
474 this->DrawString(r
, i
++, " %02x: %08x (%s)", niv
->var
, value
, niv
->name
);
479 uint psa_size
= nih
->GetPSASize(index
, this->caller_grfid
);
480 const int32
*psa
= nih
->GetPSAFirstPosition(index
, this->caller_grfid
);
481 if (psa_size
!= 0 && psa
!= NULL
) {
482 if (nih
->PSAWithParameter()) {
483 this->DrawString(r
, i
++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid
));
485 this->DrawString(r
, i
++, "Persistent storage:");
487 assert(psa_size
% 4 == 0);
488 for (uint j
= 0; j
< psa_size
; j
+= 4, psa
+= 4) {
489 this->DrawString(r
, i
++, " %i: %i %i %i %i", j
, psa
[0], psa
[1], psa
[2], psa
[3]);
493 if (nif
->properties
!= NULL
) {
494 this->DrawString(r
, i
++, "Properties:");
495 for (const NIProperty
*nip
= nif
->properties
; nip
->name
!= NULL
; nip
++) {
496 const void *ptr
= (const byte
*)base
+ nip
->offset
;
498 switch (nip
->read_size
) {
499 case 1: value
= *(const uint8
*)ptr
; break;
500 case 2: value
= *(const uint16
*)ptr
; break;
501 case 4: value
= *(const uint32
*)ptr
; break;
502 default: NOT_REACHED();
509 string
= STR_JUST_INT
;
513 string
= value
!= INVALID_CARGO
? CargoSpec::Get(value
)->name
: STR_QUANTITY_N_A
;
521 GetString(buffer
, string
, lastof(buffer
));
522 this->DrawString(r
, i
++, " %02x: %s (%s)", nip
->prop
, buffer
, nip
->name
);
526 if (nif
->callbacks
!= NULL
) {
527 this->DrawString(r
, i
++, "Callbacks:");
528 for (const NICallback
*nic
= nif
->callbacks
; nic
->name
!= NULL
; nic
++) {
529 if (nic
->cb_bit
!= CBM_NO_BIT
) {
530 const void *ptr
= (const byte
*)base_spec
+ nic
->offset
;
532 switch (nic
->read_size
) {
533 case 1: value
= *(const uint8
*)ptr
; break;
534 case 2: value
= *(const uint16
*)ptr
; break;
535 case 4: value
= *(const uint32
*)ptr
; break;
536 default: NOT_REACHED();
539 if (!HasBit(value
, nic
->cb_bit
)) continue;
540 this->DrawString(r
, i
++, " %03x: %s", nic
->cb_id
, nic
->name
);
542 this->DrawString(r
, i
++, " %03x: %s (unmasked)", nic
->cb_id
, nic
->name
);
547 /* Not nice and certainly a hack, but it beats duplicating
548 * this whole function just to count the actual number of
549 * elements. Especially because they need to be redrawn. */
550 const_cast<NewGRFInspectWindow
*>(this)->vscroll
->SetCount(i
);
553 virtual void OnClick(Point pt
, int widget
, int click_count
)
556 case WID_NGRFI_PARENT
: {
557 const NIHelper
*nih
= GetFeatureHelper(this->window_number
);
558 uint index
= nih
->GetParent(this->GetFeatureIndex());
559 ::ShowNewGRFInspectWindow(GetFeatureNum(index
), ::GetFeatureIndex(index
), nih
->GetGRFID(this->GetFeatureIndex()));
563 case WID_NGRFI_VEH_PREV
:
564 if (this->chain_index
> 0) {
566 this->InvalidateData();
570 case WID_NGRFI_VEH_NEXT
:
571 if (this->HasChainIndex()) {
572 uint index
= this->GetFeatureIndex();
573 Vehicle
*v
= Vehicle::Get(index
);
574 if (v
!= NULL
&& v
->Next() != NULL
) {
576 this->InvalidateData();
581 case WID_NGRFI_MAINPANEL
: {
582 /* Does this feature have variables? */
583 const NIFeature
*nif
= GetFeature(this->window_number
);
584 if (nif
->variables
== NULL
) return;
586 /* Get the line, make sure it's within the boundaries. */
587 int line
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_NGRFI_MAINPANEL
, TOP_OFFSET
);
588 if (line
== INT_MAX
) return;
590 /* Find the variable related to the line */
591 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= NULL
; niv
++, line
--) {
592 if (line
!= 1) continue; // 1 because of the "Variables:" line
594 if (!HasVariableParameter(niv
->var
)) break;
596 this->current_edit_param
= niv
->var
;
597 ShowQueryString(STR_EMPTY
, STR_NEWGRF_INSPECT_QUERY_CAPTION
, 9, this, CS_HEXADECIMAL
, QSF_NONE
);
603 virtual void OnQueryTextFinished(char *str
)
605 if (StrEmpty(str
)) return;
607 NewGRFInspectWindow::var60params
[GetFeatureNum(this->window_number
)][this->current_edit_param
- 0x60] = strtol(str
, NULL
, 16);
611 virtual void OnResize()
613 this->vscroll
->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL
, TOP_OFFSET
+ BOTTOM_OFFSET
);
617 * Some data on this window has become invalid.
618 * @param data Information about the changed data.
619 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
621 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
623 if (!gui_scope
) return;
624 if (this->HasChainIndex()) {
625 this->ValidateChainIndex();
626 this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV
, this->chain_index
== 0);
627 Vehicle
*v
= Vehicle::Get(this->GetFeatureIndex());
628 this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT
, v
== NULL
|| v
->Next() == NULL
);
633 /* static */ uint32
NewGRFInspectWindow::var60params
[GSF_FAKE_END
][0x20] = { {0} }; // Use spec to have 0s in whole array
635 static const NWidgetPart _nested_newgrf_inspect_chain_widgets
[] = {
636 NWidget(NWID_HORIZONTAL
),
637 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
638 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_NGRFI_CAPTION
), SetDataTip(STR_NEWGRF_INSPECT_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
639 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
640 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
641 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
643 NWidget(WWT_PANEL
, COLOUR_GREY
),
644 NWidget(NWID_HORIZONTAL
),
645 NWidget(WWT_PUSHARROWBTN
, COLOUR_GREY
, WID_NGRFI_VEH_PREV
), SetDataTip(AWV_DECREASE
, STR_NULL
),
646 NWidget(WWT_PUSHARROWBTN
, COLOUR_GREY
, WID_NGRFI_VEH_NEXT
), SetDataTip(AWV_INCREASE
, STR_NULL
),
647 NWidget(WWT_EMPTY
, COLOUR_GREY
, WID_NGRFI_VEH_CHAIN
), SetFill(1, 0), SetResize(1, 0),
650 NWidget(NWID_HORIZONTAL
),
651 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_NGRFI_MAINPANEL
), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR
), EndContainer(),
652 NWidget(NWID_VERTICAL
),
653 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_NGRFI_SCROLLBAR
),
654 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
659 static const NWidgetPart _nested_newgrf_inspect_widgets
[] = {
660 NWidget(NWID_HORIZONTAL
),
661 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
662 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_NGRFI_CAPTION
), SetDataTip(STR_NEWGRF_INSPECT_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
663 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_NGRFI_PARENT
), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON
, STR_NEWGRF_INSPECT_PARENT_TOOLTIP
),
664 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
665 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
666 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
668 NWidget(NWID_HORIZONTAL
),
669 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_NGRFI_MAINPANEL
), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR
), EndContainer(),
670 NWidget(NWID_VERTICAL
),
671 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_NGRFI_SCROLLBAR
),
672 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
677 static WindowDesc
_newgrf_inspect_chain_desc(
678 WDP_AUTO
, "newgrf_inspect_chain", 400, 300,
679 WC_NEWGRF_INSPECT
, WC_NONE
,
681 _nested_newgrf_inspect_chain_widgets
, lengthof(_nested_newgrf_inspect_chain_widgets
)
684 static WindowDesc
_newgrf_inspect_desc(
685 WDP_AUTO
, "newgrf_inspect", 400, 300,
686 WC_NEWGRF_INSPECT
, WC_NONE
,
688 _nested_newgrf_inspect_widgets
, lengthof(_nested_newgrf_inspect_widgets
)
692 * Show the inspect window for a given feature and index.
693 * The index is normally an in-game location/identifier, such
694 * as a TileIndex or an IndustryID depending on the feature
695 * we want to inspect.
696 * @param feature The feature we want to inspect.
697 * @param index The index/identifier of the feature to inspect.
698 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
700 void ShowNewGRFInspectWindow(GrfSpecFeature feature
, uint index
, const uint32 grfid
)
702 if (!IsNewGRFInspectable(feature
, index
)) return;
704 WindowNumber wno
= GetInspectWindowNumber(feature
, index
);
705 WindowDesc
*desc
= (feature
== GSF_TRAINS
|| feature
== GSF_ROADVEHICLES
) ? &_newgrf_inspect_chain_desc
: &_newgrf_inspect_desc
;
706 NewGRFInspectWindow
*w
= AllocateWindowDescFront
<NewGRFInspectWindow
>(desc
, wno
, true);
707 w
->SetCallerGRFID(grfid
);
711 * Invalidate the inspect window for a given feature and index.
712 * The index is normally an in-game location/identifier, such
713 * as a TileIndex or an IndustryID depending on the feature
714 * we want to inspect.
715 * @param feature The feature we want to invalidate the window for.
716 * @param index The index/identifier of the feature to invalidate.
718 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature
, uint index
)
720 if (feature
== GSF_INVALID
) return;
722 WindowNumber wno
= GetInspectWindowNumber(feature
, index
);
723 InvalidateWindowData(WC_NEWGRF_INSPECT
, wno
);
727 * Delete inspect window for a given feature and index.
728 * The index is normally an in-game location/identifier, such
729 * as a TileIndex or an IndustryID depending on the feature
730 * we want to inspect.
731 * @param feature The feature we want to delete the window for.
732 * @param index The index/identifier of the feature to delete.
734 void DeleteNewGRFInspectWindow(GrfSpecFeature feature
, uint index
)
736 if (feature
== GSF_INVALID
) return;
738 WindowNumber wno
= GetInspectWindowNumber(feature
, index
);
739 DeleteWindowById(WC_NEWGRF_INSPECT
, wno
);
741 /* Reinitialise the land information window to remove the "debug" sprite if needed.
742 * Note: Since we might be called from a command here, it is important to not execute
743 * the invalidation immediately. The landinfo window tests commands itself. */
744 InvalidateWindowData(WC_LAND_INFO
, 0, 1);
748 * Can we inspect the data given a certain feature and index.
749 * The index is normally an in-game location/identifier, such
750 * as a TileIndex or an IndustryID depending on the feature
751 * we want to inspect.
752 * @param feature The feature we want to inspect.
753 * @param index The index/identifier of the feature to inspect.
754 * @return true if there is something to show.
756 bool IsNewGRFInspectable(GrfSpecFeature feature
, uint index
)
758 const NIFeature
*nif
= GetFeature(GetInspectWindowNumber(feature
, index
));
759 if (nif
== NULL
) return false;
760 return nif
->helper
->IsInspectable(index
);
764 * Get the GrfSpecFeature associated with the tile.
765 * @param tile The tile to get the feature from.
766 * @return the GrfSpecFeature.
768 GrfSpecFeature
GetGrfSpecFeature(TileIndex tile
)
770 switch (GetTileType(tile
)) {
771 default: return GSF_INVALID
;
772 case MP_RAILWAY
: return GSF_RAILTYPES
;
773 case MP_ROAD
: return IsLevelCrossing(tile
) ? GSF_RAILTYPES
: GSF_INVALID
;
774 case MP_HOUSE
: return GSF_HOUSES
;
775 case MP_INDUSTRY
: return GSF_INDUSTRYTILES
;
776 case MP_OBJECT
: return GSF_OBJECTS
;
779 switch (GetStationType(tile
)) {
780 case STATION_RAIL
: return GSF_STATIONS
;
781 case STATION_AIRPORT
: return GSF_AIRPORTTILES
;
782 default: return GSF_INVALID
;
788 * Get the GrfSpecFeature associated with the vehicle.
789 * @param type The vehicle type to get the feature from.
790 * @return the GrfSpecFeature.
792 GrfSpecFeature
GetGrfSpecFeature(VehicleType type
)
795 case VEH_TRAIN
: return GSF_TRAINS
;
796 case VEH_ROAD
: return GSF_ROADVEHICLES
;
797 case VEH_SHIP
: return GSF_SHIPS
;
798 case VEH_AIRCRAFT
: return GSF_AIRCRAFT
;
799 default: return GSF_INVALID
;
805 /**** Sprite Aligner ****/
807 /** Window used for aligning sprites. */
808 struct SpriteAlignerWindow
: Window
{
809 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.
811 SpriteID current_sprite
; ///< The currently shown sprite.
813 SmallMap
<SpriteID
, XyOffs
> offs_start_map
; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window.
815 SpriteAlignerWindow(WindowDesc
*desc
, WindowNumber wno
) : Window(desc
)
817 this->CreateNestedTree();
818 this->vscroll
= this->GetScrollbar(WID_SA_SCROLLBAR
);
819 this->FinishInitNested(wno
);
821 /* Oh yes, we assume there is at least one normal sprite! */
822 while (GetSpriteType(this->current_sprite
) != ST_NORMAL
) this->current_sprite
++;
825 virtual void SetStringParameters(int widget
) const
827 const Sprite
*spr
= GetSprite(this->current_sprite
, ST_NORMAL
);
830 SetDParam(0, this->current_sprite
);
831 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite
)));
834 case WID_SA_OFFSETS_ABS
:
835 SetDParam(0, spr
->x_offs
);
836 SetDParam(1, spr
->y_offs
);
839 case WID_SA_OFFSETS_REL
: {
840 /* Relative offset is new absolute offset - starting absolute offset.
841 * Show 0, 0 as the relative offsets if entry is not in the map (meaning they have not been changed yet).
843 const SmallPair
<SpriteID
, XyOffs
> *key_offs_pair
= this->offs_start_map
.Find(this->current_sprite
);
844 if (key_offs_pair
!= this->offs_start_map
.End()) {
845 SetDParam(0, spr
->x_offs
- key_offs_pair
->second
.first
);
846 SetDParam(1, spr
->y_offs
- key_offs_pair
->second
.second
);
859 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
861 if (widget
!= WID_SA_LIST
) return;
863 resize
->height
= max(11, FONT_HEIGHT_NORMAL
+ 1);
866 /* Resize to about 200 pixels (for the preview) */
867 size
->height
= (1 + 200 / resize
->height
) * resize
->height
;
870 virtual void DrawWidget(const Rect
&r
, int widget
) const
873 case WID_SA_SPRITE
: {
874 /* Center the sprite ourselves */
875 const Sprite
*spr
= GetSprite(this->current_sprite
, ST_NORMAL
);
876 int width
= r
.right
- r
.left
+ 1 - WD_BEVEL_LEFT
- WD_BEVEL_RIGHT
;
877 int height
= r
.bottom
- r
.top
+ 1 - WD_BEVEL_TOP
- WD_BEVEL_BOTTOM
;
878 int x
= -UnScaleGUI(spr
->x_offs
) + (width
- UnScaleGUI(spr
->width
) ) / 2;
879 int y
= -UnScaleGUI(spr
->y_offs
) + (height
- UnScaleGUI(spr
->height
)) / 2;
881 DrawPixelInfo new_dpi
;
882 if (!FillDrawPixelInfo(&new_dpi
, r
.left
+ WD_BEVEL_LEFT
, r
.top
+ WD_BEVEL_TOP
, width
, height
)) break;
883 DrawPixelInfo
*old_dpi
= _cur_dpi
;
886 DrawSprite(this->current_sprite
, PAL_NONE
, x
, y
, NULL
, ZOOM_LVL_GUI
);
894 const NWidgetBase
*nwid
= this->GetWidget
<NWidgetBase
>(widget
);
895 int step_size
= nwid
->resize_y
;
897 SmallVector
<SpriteID
, 256> &list
= _newgrf_debug_sprite_picker
.sprites
;
898 int max
= min
<int>(this->vscroll
->GetPosition() + this->vscroll
->GetCapacity(), list
.Length());
900 int y
= r
.top
+ WD_FRAMERECT_TOP
;
901 for (int i
= this->vscroll
->GetPosition(); i
< max
; i
++) {
902 SetDParam(0, list
[i
]);
903 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_BLACK_COMMA
, TC_FROMSTRING
, SA_RIGHT
| SA_FORCE
);
911 virtual void OnClick(Point pt
, int widget
, int click_count
)
914 case WID_SA_PREVIOUS
:
916 this->current_sprite
= (this->current_sprite
== 0 ? GetMaxSpriteID() : this->current_sprite
) - 1;
917 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
922 ShowQueryString(STR_EMPTY
, STR_SPRITE_ALIGNER_GOTO_CAPTION
, 7, this, CS_NUMERAL
, QSF_NONE
);
927 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
928 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
933 this->LowerWidget(WID_SA_PICKER
);
934 _newgrf_debug_sprite_picker
.mode
= SPM_WAIT_CLICK
;
939 const NWidgetBase
*nwid
= this->GetWidget
<NWidgetBase
>(widget
);
940 int step_size
= nwid
->resize_y
;
942 uint i
= this->vscroll
->GetPosition() + (pt
.y
- nwid
->pos_y
) / step_size
;
943 if (i
< _newgrf_debug_sprite_picker
.sprites
.Length()) {
944 SpriteID spr
= _newgrf_debug_sprite_picker
.sprites
[i
];
945 if (GetSpriteType(spr
) == ST_NORMAL
) this->current_sprite
= spr
;
956 * Yes... this is a hack.
958 * No... I don't think it is useful to make this less of a hack.
960 * If you want to align sprites, you just need the number. Generally
961 * the sprite caches are big enough to not remove the sprite from the
962 * cache. If that's not the case, just let the NewGRF developer
963 * increase the cache size instead of storing thousands of offsets
964 * for the incredibly small chance that it's actually going to be
965 * used by someone and the sprite cache isn't big enough for that
966 * particular NewGRF developer.
968 Sprite
*spr
= const_cast<Sprite
*>(GetSprite(this->current_sprite
, ST_NORMAL
));
970 /* Remember the original offsets of the current sprite, if not already in mapping. */
971 if (!(this->offs_start_map
.Contains(this->current_sprite
))) {
972 this->offs_start_map
.Insert(this->current_sprite
, XyOffs(spr
->x_offs
, spr
->y_offs
));
975 /* Move ten units at a time if ctrl is pressed. */
976 case WID_SA_UP
: spr
->y_offs
-= _ctrl_pressed
? 8 : 1; break;
977 case WID_SA_DOWN
: spr
->y_offs
+= _ctrl_pressed
? 8 : 1; break;
978 case WID_SA_LEFT
: spr
->x_offs
-= _ctrl_pressed
? 8 : 1; break;
979 case WID_SA_RIGHT
: spr
->x_offs
+= _ctrl_pressed
? 8 : 1; break;
981 /* Of course, we need to redraw the sprite, but where is it used?
982 * Everywhere is a safe bet. */
983 MarkWholeScreenDirty();
987 case WID_SA_RESET_REL
:
988 /* Reset the starting offsets for the current sprite. */
989 this->offs_start_map
.Erase(this->current_sprite
);
995 virtual void OnQueryTextFinished(char *str
)
997 if (StrEmpty(str
)) return;
999 this->current_sprite
= atoi(str
);
1000 if (this->current_sprite
>= GetMaxSpriteID()) this->current_sprite
= 0;
1001 while (GetSpriteType(this->current_sprite
) != ST_NORMAL
) {
1002 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
1008 * Some data on this window has become invalid.
1009 * @param data Information about the changed data.
1010 * @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.
1012 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
1014 if (!gui_scope
) return;
1016 /* Sprite picker finished */
1017 this->RaiseWidget(WID_SA_PICKER
);
1018 this->vscroll
->SetCount(_newgrf_debug_sprite_picker
.sprites
.Length());
1022 virtual void OnResize()
1024 this->vscroll
->SetCapacityFromWidget(this, WID_SA_LIST
);
1028 static const NWidgetPart _nested_sprite_aligner_widgets
[] = {
1029 NWidget(NWID_HORIZONTAL
),
1030 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
1031 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_SA_CAPTION
), SetDataTip(STR_SPRITE_ALIGNER_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1032 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
1033 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
1035 NWidget(WWT_PANEL
, COLOUR_GREY
),
1036 NWidget(NWID_HORIZONTAL
), SetPIP(0, 0, 10),
1037 NWidget(NWID_VERTICAL
), SetPIP(10, 5, 10),
1038 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(10, 5, 10),
1039 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_PREVIOUS
), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON
, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP
), SetFill(1, 0),
1040 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_GOTO
), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON
, STR_SPRITE_ALIGNER_GOTO_TOOLTIP
), SetFill(1, 0),
1041 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_NEXT
), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON
, STR_SPRITE_ALIGNER_NEXT_TOOLTIP
), SetFill(1, 0),
1043 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
1044 NWidget(NWID_SPACER
), SetFill(1, 1),
1045 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_UP
), SetDataTip(SPR_ARROW_UP
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
1046 NWidget(NWID_SPACER
), SetFill(1, 1),
1048 NWidget(NWID_HORIZONTAL_LTR
), SetPIP(10, 5, 10),
1049 NWidget(NWID_VERTICAL
),
1050 NWidget(NWID_SPACER
), SetFill(1, 1),
1051 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_LEFT
), SetDataTip(SPR_ARROW_LEFT
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
1052 NWidget(NWID_SPACER
), SetFill(1, 1),
1054 NWidget(WWT_PANEL
, COLOUR_DARK_BLUE
, WID_SA_SPRITE
), SetDataTip(STR_NULL
, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP
),
1056 NWidget(NWID_VERTICAL
),
1057 NWidget(NWID_SPACER
), SetFill(1, 1),
1058 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_RIGHT
), SetDataTip(SPR_ARROW_RIGHT
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
1059 NWidget(NWID_SPACER
), SetFill(1, 1),
1062 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
1063 NWidget(NWID_SPACER
), SetFill(1, 1),
1064 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_DOWN
), SetDataTip(SPR_ARROW_DOWN
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
1065 NWidget(NWID_SPACER
), SetFill(1, 1),
1067 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),
1068 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),
1069 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
1070 NWidget(NWID_SPACER
), SetFill(1, 1),
1071 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_RESET_REL
), SetDataTip(STR_SPRITE_ALIGNER_RESET_BUTTON
, STR_SPRITE_ALIGNER_RESET_TOOLTIP
), SetFill(0, 0),
1072 NWidget(NWID_SPACER
), SetFill(1, 1),
1075 NWidget(NWID_VERTICAL
), SetPIP(10, 5, 10),
1076 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_SA_PICKER
), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON
, STR_SPRITE_ALIGNER_PICKER_TOOLTIP
), SetFill(1, 0),
1077 NWidget(NWID_HORIZONTAL
),
1078 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_SA_LIST
), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL
), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR
),
1079 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_SA_SCROLLBAR
),
1086 static WindowDesc
_sprite_aligner_desc(
1087 WDP_AUTO
, "sprite_aligner", 400, 300,
1088 WC_SPRITE_ALIGNER
, WC_NONE
,
1090 _nested_sprite_aligner_widgets
, lengthof(_nested_sprite_aligner_widgets
)
1094 * Show the window for aligning sprites.
1096 void ShowSpriteAlignerWindow()
1098 AllocateWindowDescFront
<SpriteAlignerWindow
>(&_sprite_aligner_desc
, 0);