Add a method in NewGRFInspectWindow to resolve FeatureIndex
[openttd/fttd.git] / src / newgrf_debug_gui.cpp
blobdb3bfb2b319622813f2f7fd12b50f3ea51134009
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
12 #include "stdafx.h"
13 #include <stdarg.h>
14 #include "window_gui.h"
15 #include "window_func.h"
16 #include "fileio_func.h"
17 #include "spritecache.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "textbuf_gui.h"
22 #include "engine_base.h"
23 #include "industry.h"
24 #include "object_base.h"
25 #include "station_base.h"
26 #include "town.h"
27 #include "vehicle_base.h"
28 #include "map/depot.h"
30 #include "newgrf_airporttiles.h"
31 #include "newgrf_debug.h"
32 #include "newgrf_object.h"
33 #include "newgrf_spritegroup.h"
34 #include "newgrf_station.h"
35 #include "newgrf_town.h"
36 #include "newgrf_railtype.h"
37 #include "newgrf_industries.h"
38 #include "newgrf_industrytiles.h"
40 #include "widgets/newgrf_debug_widget.h"
42 #include "table/strings.h"
44 /** The sprite picker. */
45 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
47 /**
48 * Get the feature index related to the window number.
49 * @param window_number The window to get the feature index from.
50 * @return the feature index
52 static inline uint GetFeatureIndex(uint window_number)
54 return GB(window_number, 0, 24);
57 /**
58 * Get the window number for the inspect window given a
59 * feature and index.
60 * @param feature The feature we want to inspect.
61 * @param index The index/identifier of the feature to inspect.
62 * @return the InspectWindow (Window)Number
64 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
66 assert((index >> 24) == 0);
67 return (feature << 24) | index;
70 /**
71 * The type of a property to show. This is used to
72 * provide an appropriate representation in the GUI.
74 enum NIType {
75 NIT_INT, ///< The property is a simple integer
76 NIT_CARGO, ///< The property is a cargo
79 /** Representation of the data from a NewGRF property. */
80 struct NIProperty {
81 const char *name; ///< A (human readable) name for the property
82 ptrdiff_t offset; ///< Offset of the variable in the class
83 byte read_size; ///< Number of bytes (i.e. byte, word, dword etc)
84 byte prop; ///< The number of the property
85 byte type;
89 /**
90 * Representation of the available callbacks with
91 * information on when they actually apply.
93 struct NICallback {
94 const char *name; ///< The human readable name of the callback
95 ptrdiff_t offset; ///< Offset of the variable in the class
96 byte read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read
97 byte cb_bit; ///< The bit that needs to be set for this callback to be enabled
98 uint16 cb_id; ///< The number of the callback
100 /** Mask to show no bit needs to be enabled for the callback. */
101 static const int CBM_NO_BIT = UINT8_MAX;
103 /** Representation on the NewGRF variables. */
104 struct NIVariable {
105 const char *name;
106 byte var;
109 /** Helper class to wrap some functionality/queries in. */
110 class NIHelper {
111 public:
112 /** Silence a warning. */
113 virtual ~NIHelper() {}
116 * Is the item with the given index inspectable?
117 * @param index the index to check.
118 * @return true iff the index is inspectable.
120 virtual bool IsInspectable(uint index) const = 0;
123 * Get the parent "window_number" of a given instance.
124 * @param index the instance to get the parent for.
125 * @return the parent's window_number or UINT32_MAX if there is none.
127 virtual uint GetParent(uint index) const = 0;
130 * Get the instance given an index.
131 * @param index the index to get the instance for.
132 * @return the instance.
134 virtual const void *GetInstance(uint index) const = 0;
137 * Get (NewGRF) specs given an index.
138 * @param index the index to get the specs for for.
139 * @return the specs.
141 virtual const void *GetSpec(uint index) const = 0;
144 * Set the string parameters to write the right data for a STRINGn.
145 * @param index the index to get the string parameters for.
147 virtual void SetStringParameters(uint index) const = 0;
150 * Get the GRFID of the file that includes this item.
151 * @param index index to check.
152 * @return GRFID of the item. 0 means that the item is not inspectable.
154 virtual uint32 GetGRFID(uint index) const = 0;
157 * Resolve (action2) variable for a given index.
158 * @param index The (instance) index to resolve the variable for.
159 * @param var The variable to actually resolve.
160 * @param param The varaction2 0x60+x parameter to pass.
161 * @param avail Return whether the variable is available.
162 * @return The resolved variable's value.
164 virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
167 * Used to decide if the PSA needs a parameter or not.
168 * @return True iff this item has a PSA that requires a parameter.
170 virtual bool PSAWithParameter() const
172 return false;
176 * Allows to know the size of the persistent storage.
177 * @param index Index of the item.
178 * @param grfid Parameter for the PSA. Only required for items with parameters.
179 * @return Size of the persistent storage in indices.
181 virtual uint GetPSASize(uint index, uint32 grfid) const
183 return 0;
187 * Gets the first position of the array containing the persistent storage.
188 * @param index Index of the item.
189 * @param grfid Parameter for the PSA. Only required for items with parameters.
190 * @return Pointer to the first position of the storage array or NULL if not present.
192 virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
194 return NULL;
197 protected:
199 * Helper to make setting the strings easier.
200 * @param string the string to actually draw.
201 * @param index the (instance) index for the string.
203 void SetSimpleStringParameters(StringID string, uint32 index) const
205 SetDParam(0, string);
206 SetDParam(1, index);
211 * Helper to make setting the strings easier for objects at a specific tile.
212 * @param string the string to draw the object's name
213 * @param index the (instance) index for the string.
214 * @param tile the tile the object is at
216 void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
218 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
219 SetDParam(1, string);
220 SetDParam(2, index);
221 SetDParam(3, tile);
226 /** Container for all information for a given feature. */
227 struct NIFeature {
228 const NIProperty *properties; ///< The properties associated with this feature.
229 const NICallback *callbacks; ///< The callbacks associated with this feature.
230 const NIVariable *variables; ///< The variables associated with this feature.
231 const NIHelper *helper; ///< The class container all helper functions.
234 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
235 #include "table/newgrf_debug_data.h"
238 * Get the feature number related to the window number.
239 * @param window_number The window to get the feature number for.
240 * @return The feature number.
242 static inline GrfSpecFeature GetFeatureNum(uint window_number)
244 return (GrfSpecFeature)GB(window_number, 24, 8);
248 * Get the NIFeature related to the window number.
249 * @param window_number The window to get the NIFeature for.
250 * @return the NIFeature, or NULL is there isn't one.
252 static inline const NIFeature *GetFeature(uint window_number)
254 GrfSpecFeature idx = GetFeatureNum(window_number);
255 return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
259 * Get the NIHelper related to the window number.
260 * @param window_number The window to get the NIHelper for.
261 * @pre GetFeature(window_number) != NULL
262 * @return the NIHelper
264 static inline const NIHelper *GetFeatureHelper(uint window_number)
266 return GetFeature(window_number)->helper;
269 /** Window used for inspecting NewGRFs. */
270 struct NewGRFInspectWindow : Window {
271 static const int LEFT_OFFSET = 5; ///< Position of left edge
272 static const int RIGHT_OFFSET = 5; ///< Position of right edge
273 static const int TOP_OFFSET = 5; ///< Position of top edge
274 static const int BOTTOM_OFFSET = 5; ///< Position of bottom edge
276 /** The value for the variable 60 parameters. */
277 static uint32 var60params[GSF_FAKE_END][0x20];
279 /** GRFID of the caller of this window, 0 if it has no caller. */
280 uint32 caller_grfid;
282 /** The currently edited parameter, to update the right one. */
283 byte current_edit_param;
285 Scrollbar *vscroll;
288 * Check whether the given variable has a parameter.
289 * @param variable the variable to check.
290 * @return true iff the variable has a parameter.
292 static bool HasVariableParameter(uint variable)
294 return IsInsideBS(variable, 0x60, 0x20);
298 * Set the GRFID of the item opening this window.
299 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
301 void SetCallerGRFID(uint32 grfid)
303 this->caller_grfid = grfid;
304 this->SetDirty();
308 * Get the feature index.
309 * @return the feature index
311 uint GetFeatureIndex() const
313 uint index = ::GetFeatureIndex(this->window_number);
314 return index;
317 NewGRFInspectWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
319 this->CreateNestedTree();
320 this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
321 this->FinishInitNested(wno);
323 this->vscroll->SetCount(0);
324 this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
327 virtual void SetStringParameters(int widget) const
329 if (widget != WID_NGRFI_CAPTION) return;
331 GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
334 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
336 if (widget != WID_NGRFI_MAINPANEL) return;
338 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
339 resize->width = 1;
341 size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
345 * Helper function to draw a string (line) in the window.
346 * @param r The (screen) rectangle we must draw within
347 * @param offset The offset (in lines) we want to draw for
348 * @param format The format string
350 void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
352 char buf[1024];
354 va_list va;
355 va_start(va, format);
356 vsnprintf(buf, lengthof(buf), format, va);
357 va_end(va);
359 offset -= this->vscroll->GetPosition();
360 if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
362 ::DrawString(r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
365 virtual void DrawWidget(const Rect &r, int widget) const
367 if (widget != WID_NGRFI_MAINPANEL) return;
369 uint index = this->GetFeatureIndex();
370 const NIFeature *nif = GetFeature(this->window_number);
371 const NIHelper *nih = nif->helper;
372 const void *base = nih->GetInstance(index);
373 const void *base_spec = nih->GetSpec(index);
375 uint i = 0;
376 if (nif->variables != NULL) {
377 this->DrawString(r, i++, "Variables:");
378 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
379 bool avail = true;
380 uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
381 uint value = nih->Resolve(index, niv->var, param, &avail);
383 if (!avail) continue;
385 if (HasVariableParameter(niv->var)) {
386 this->DrawString(r, i++, " %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
387 } else {
388 this->DrawString(r, i++, " %02x: %08x (%s)", niv->var, value, niv->name);
393 uint psa_size = nih->GetPSASize(index, this->caller_grfid);
394 const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
395 if (psa_size != 0 && psa != NULL) {
396 if (nih->PSAWithParameter()) {
397 this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
398 } else {
399 this->DrawString(r, i++, "Persistent storage:");
401 assert(psa_size % 4 == 0);
402 for (uint j = 0; j < psa_size; j += 4, psa += 4) {
403 this->DrawString(r, i++, " %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
407 if (nif->properties != NULL) {
408 this->DrawString(r, i++, "Properties:");
409 for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
410 const void *ptr = (const byte *)base + nip->offset;
411 uint value;
412 switch (nip->read_size) {
413 case 1: value = *(const uint8 *)ptr; break;
414 case 2: value = *(const uint16 *)ptr; break;
415 case 4: value = *(const uint32 *)ptr; break;
416 default: NOT_REACHED();
419 StringID string;
420 SetDParam(0, value);
421 switch (nip->type) {
422 case NIT_INT:
423 string = STR_JUST_INT;
424 break;
426 case NIT_CARGO:
427 string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
428 break;
430 default:
431 NOT_REACHED();
434 char buffer[64];
435 GetString(buffer, string, lastof(buffer));
436 this->DrawString(r, i++, " %02x: %s (%s)", nip->prop, buffer, nip->name);
440 if (nif->callbacks != NULL) {
441 this->DrawString(r, i++, "Callbacks:");
442 for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
443 if (nic->cb_bit != CBM_NO_BIT) {
444 const void *ptr = (const byte *)base_spec + nic->offset;
445 uint value;
446 switch (nic->read_size) {
447 case 1: value = *(const uint8 *)ptr; break;
448 case 2: value = *(const uint16 *)ptr; break;
449 case 4: value = *(const uint32 *)ptr; break;
450 default: NOT_REACHED();
453 if (!HasBit(value, nic->cb_bit)) continue;
454 this->DrawString(r, i++, " %03x: %s", nic->cb_id, nic->name);
455 } else {
456 this->DrawString(r, i++, " %03x: %s (unmasked)", nic->cb_id, nic->name);
461 /* Not nice and certainly a hack, but it beats duplicating
462 * this whole function just to count the actual number of
463 * elements. Especially because they need to be redrawn. */
464 const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
467 virtual void OnClick(Point pt, int widget, int click_count)
469 switch (widget) {
470 case WID_NGRFI_PARENT: {
471 const NIHelper *nih = GetFeatureHelper(this->window_number);
472 uint index = nih->GetParent(this->GetFeatureIndex());
473 ::ShowNewGRFInspectWindow((GrfSpecFeature)GB(index, 24, 8), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
474 break;
477 case WID_NGRFI_MAINPANEL: {
478 /* Does this feature have variables? */
479 const NIFeature *nif = GetFeature(this->window_number);
480 if (nif->variables == NULL) return;
482 /* Get the line, make sure it's within the boundaries. */
483 int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
484 if (line == INT_MAX) return;
486 /* Find the variable related to the line */
487 for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
488 if (line != 1) continue; // 1 because of the "Variables:" line
490 if (!HasVariableParameter(niv->var)) break;
492 this->current_edit_param = niv->var;
493 ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
499 virtual void OnQueryTextFinished(char *str)
501 if (StrEmpty(str)) return;
503 NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
504 this->SetDirty();
507 virtual void OnResize()
509 this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
513 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
515 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
516 NWidget(NWID_HORIZONTAL),
517 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
518 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
519 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
520 NWidget(WWT_SHADEBOX, COLOUR_GREY),
521 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
522 NWidget(WWT_STICKYBOX, COLOUR_GREY),
523 EndContainer(),
524 NWidget(NWID_HORIZONTAL),
525 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
526 NWidget(NWID_VERTICAL),
527 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
528 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
529 EndContainer(),
530 EndContainer(),
533 static WindowDesc _newgrf_inspect_desc(
534 WDP_AUTO, "newgrf_inspect", 400, 300,
535 WC_NEWGRF_INSPECT, WC_NONE,
537 _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
541 * Show the inspect window for a given feature and index.
542 * The index is normally an in-game location/identifier, such
543 * as a TileIndex or an IndustryID depending on the feature
544 * we want to inspect.
545 * @param feature The feature we want to inspect.
546 * @param index The index/identifier of the feature to inspect.
547 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
549 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
551 if (!IsNewGRFInspectable(feature, index)) return;
553 WindowNumber wno = GetInspectWindowNumber(feature, index);
554 NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(&_newgrf_inspect_desc, wno);
555 if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
556 w->SetCallerGRFID(grfid);
560 * Delete inspect window for a given feature and index.
561 * The index is normally an in-game location/identifier, such
562 * as a TileIndex or an IndustryID depending on the feature
563 * we want to inspect.
564 * @param feature The feature we want to delete the window for.
565 * @param index The index/identifier of the feature to delete.
567 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
569 if (feature == GSF_INVALID) return;
571 WindowNumber wno = GetInspectWindowNumber(feature, index);
572 DeleteWindowById(WC_NEWGRF_INSPECT, wno);
574 /* Reinitialise the land information window to remove the "debug" sprite if needed.
575 * Note: Since we might be called from a command here, it is important to not execute
576 * the invalidation immediately. The landinfo window tests commands itself. */
577 InvalidateWindowData(WC_LAND_INFO, 0, 1);
581 * Can we inspect the data given a certain feature and index.
582 * The index is normally an in-game location/identifier, such
583 * as a TileIndex or an IndustryID depending on the feature
584 * we want to inspect.
585 * @param feature The feature we want to inspect.
586 * @param index The index/identifier of the feature to inspect.
587 * @return true if there is something to show.
589 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
591 const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
592 if (nif == NULL) return false;
593 return nif->helper->IsInspectable(index);
597 * Get the GrfSpecFeature associated with the tile.
598 * @param tile The tile to get the feature from.
599 * @return the GrfSpecFeature.
601 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
603 if (IsHouseTile(tile)) {
604 return GSF_HOUSES;
605 } else if (IsIndustryTile(tile)) {
606 return GSF_INDUSTRYTILES;
609 switch (GetTileType(tile)) {
610 default: return GSF_INVALID;
611 case TT_RAILWAY: return IsTileSubtype(tile, TT_TRACK) ? GSF_RAILTYPES : GSF_INVALID;
612 case TT_MISC: return (IsRailDepotTile(tile) || IsLevelCrossingTile(tile)) ? GSF_RAILTYPES : GSF_INVALID;
613 case TT_OBJECT: return GSF_OBJECTS;
615 case TT_STATION:
616 switch (GetStationType(tile)) {
617 case STATION_RAIL: return GSF_STATIONS;
618 case STATION_AIRPORT: return GSF_AIRPORTTILES;
619 default: return GSF_INVALID;
625 * Get the GrfSpecFeature associated with the vehicle.
626 * @param type The vehicle type to get the feature from.
627 * @return the GrfSpecFeature.
629 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
631 switch (type) {
632 case VEH_TRAIN: return GSF_TRAINS;
633 case VEH_ROAD: return GSF_ROADVEHICLES;
634 case VEH_SHIP: return GSF_SHIPS;
635 case VEH_AIRCRAFT: return GSF_AIRCRAFT;
636 default: return GSF_INVALID;
642 /**** Sprite Aligner ****/
644 /** Window used for aligning sprites. */
645 struct SpriteAlignerWindow : Window {
646 SpriteID current_sprite; ///< The currently shown sprite
647 Scrollbar *vscroll;
649 SpriteAlignerWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
651 this->CreateNestedTree();
652 this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
653 this->FinishInitNested(wno);
655 /* Oh yes, we assume there is at least one normal sprite! */
656 while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
659 virtual void SetStringParameters(int widget) const
661 switch (widget) {
662 case WID_SA_CAPTION:
663 SetDParam(0, this->current_sprite);
664 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
665 break;
667 case WID_SA_OFFSETS: {
668 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
669 SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
670 SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
671 break;
674 default:
675 break;
679 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
681 if (widget != WID_SA_LIST) return;
683 resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
684 resize->width = 1;
686 /* Resize to about 200 pixels (for the preview) */
687 size->height = (1 + 200 / resize->height) * resize->height;
690 virtual void DrawWidget(const Rect &r, int widget) const
692 switch (widget) {
693 case WID_SA_SPRITE: {
694 /* Center the sprite ourselves */
695 const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
696 int width = r.right - r.left + 1;
697 int height = r.bottom - r.top + 1;
698 int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width - spr->width / ZOOM_LVL_BASE) / 2;
699 int y = r.top - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
701 /* And draw only the part within the sprite area */
702 SubSprite subspr = {
703 spr->x_offs + (spr->width - width * ZOOM_LVL_BASE) / 2 + 1,
704 spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
705 spr->x_offs + (spr->width + width * ZOOM_LVL_BASE) / 2 - 1,
706 spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
709 DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
710 break;
713 case WID_SA_LIST: {
714 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
715 int step_size = nwid->resize_y;
717 SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
718 int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
720 int y = r.top + WD_FRAMERECT_TOP;
721 for (int i = this->vscroll->GetPosition(); i < max; i++) {
722 SetDParam(0, list[i]);
723 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
724 y += step_size;
726 break;
731 virtual void OnClick(Point pt, int widget, int click_count)
733 switch (widget) {
734 case WID_SA_PREVIOUS:
735 do {
736 this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
737 } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
738 this->SetDirty();
739 break;
741 case WID_SA_GOTO:
742 ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
743 break;
745 case WID_SA_NEXT:
746 do {
747 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
748 } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
749 this->SetDirty();
750 break;
752 case WID_SA_PICKER:
753 this->LowerWidget(WID_SA_PICKER);
754 _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
755 this->SetDirty();
756 break;
758 case WID_SA_LIST: {
759 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
760 int step_size = nwid->resize_y;
762 uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
763 if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
764 SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
765 if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
767 this->SetDirty();
768 break;
771 case WID_SA_UP:
772 case WID_SA_DOWN:
773 case WID_SA_LEFT:
774 case WID_SA_RIGHT: {
776 * Yes... this is a hack.
778 * No... I don't think it is useful to make this less of a hack.
780 * If you want to align sprites, you just need the number. Generally
781 * the sprite caches are big enough to not remove the sprite from the
782 * cache. If that's not the case, just let the NewGRF developer
783 * increase the cache size instead of storing thousands of offsets
784 * for the incredibly small chance that it's actually going to be
785 * used by someone and the sprite cache isn't big enough for that
786 * particular NewGRF developer.
788 Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
789 switch (widget) {
790 case WID_SA_UP: spr->y_offs -= ZOOM_LVL_BASE; break;
791 case WID_SA_DOWN: spr->y_offs += ZOOM_LVL_BASE; break;
792 case WID_SA_LEFT: spr->x_offs -= ZOOM_LVL_BASE; break;
793 case WID_SA_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
795 /* Of course, we need to redraw the sprite, but where is it used?
796 * Everywhere is a safe bet. */
797 MarkWholeScreenDirty();
798 break;
803 virtual void OnQueryTextFinished(char *str)
805 if (StrEmpty(str)) return;
807 this->current_sprite = atoi(str);
808 if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
809 while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
810 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
812 this->SetDirty();
816 * Some data on this window has become invalid.
817 * @param data Information about the changed data.
818 * @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.
820 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
822 if (!gui_scope) return;
823 if (data == 1) {
824 /* Sprite picker finished */
825 this->RaiseWidget(WID_SA_PICKER);
826 this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
830 virtual void OnResize()
832 this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
836 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
837 NWidget(NWID_HORIZONTAL),
838 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
839 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
840 NWidget(WWT_SHADEBOX, COLOUR_GREY),
841 NWidget(WWT_STICKYBOX, COLOUR_GREY),
842 EndContainer(),
843 NWidget(WWT_PANEL, COLOUR_GREY),
844 NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
845 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
846 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
847 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
848 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
849 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
850 EndContainer(),
851 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
852 NWidget(NWID_SPACER), SetFill(1, 1),
853 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
854 NWidget(NWID_SPACER), SetFill(1, 1),
855 EndContainer(),
856 NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
857 NWidget(NWID_VERTICAL),
858 NWidget(NWID_SPACER), SetFill(1, 1),
859 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
860 NWidget(NWID_SPACER), SetFill(1, 1),
861 EndContainer(),
862 NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
863 EndContainer(),
864 NWidget(NWID_VERTICAL),
865 NWidget(NWID_SPACER), SetFill(1, 1),
866 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
867 NWidget(NWID_SPACER), SetFill(1, 1),
868 EndContainer(),
869 EndContainer(),
870 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
871 NWidget(NWID_SPACER), SetFill(1, 1),
872 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
873 NWidget(NWID_SPACER), SetFill(1, 1),
874 EndContainer(),
875 NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
876 NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
877 EndContainer(),
878 EndContainer(),
879 NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
880 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
881 NWidget(NWID_HORIZONTAL),
882 NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
883 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
884 EndContainer(),
885 EndContainer(),
886 EndContainer(),
887 EndContainer(),
890 static WindowDesc _sprite_aligner_desc(
891 WDP_AUTO, "sprite_aligner", 400, 300,
892 WC_SPRITE_ALIGNER, WC_NONE,
894 _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
898 * Show the window for aligning sprites.
900 void ShowSpriteAlignerWindow()
902 AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);