# newgrf_debug_gui.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include <stdarg.h>
    00014 #include "window_gui.h"
    00015 #include "window_func.h"
    00016 #include "fileio_func.h"
    00017 #include "spritecache.h"
    00018 #include "string_func.h"
    00019 #include "strings_func.h"
    00020 #include "textbuf_gui.h"
    00021
    00022 #include "engine_base.h"
    00023 #include "industry.h"
    00024 #include "object_base.h"
    00025 #include "station_base.h"
    00026 #include "town.h"
    00027 #include "vehicle_base.h"
    00028
    00029 #include "newgrf_airporttiles.h"
    00030 #include "newgrf_debug.h"
    00031 #include "newgrf_object.h"
    00032 #include "newgrf_spritegroup.h"
    00033 #include "newgrf_station.h"
    00034 #include "newgrf_town.h"
    00035 #include "newgrf_railtype.h"
    00036 #include "newgrf_industries.h"
    00037 #include "newgrf_industrytiles.h"
    00038
    00039 #include "widgets/newgrf_debug_widget.h"
    00040
    00041 #include "table/strings.h"
    00042
    00044 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
    00045
    00051 static inline uint GetFeatureIndex(uint window_number)
    00052 {
    00053   return GB(window_number, 0, 24);
    00054 }
    00055
    00063 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
    00064 {
    00065   assert((index >> 24) == 0);
    00066   return (feature << 24) | index;
    00067 }
    00068
    00073 enum NIType {
    00074   NIT_INT,
    00075   NIT_CARGO,
    00076 };
    00077
    00079 struct NIProperty {
    00080   const char *name;
    00081   ptrdiff_t offset;
    00082   byte read_size;
    00083   byte prop;
    00084   byte type;
    00085 };
    00086
    00087
    00092 struct NICallback {
    00093   const char *name;
    00094   ptrdiff_t offset;
    00095   byte read_size;
    00096   byte cb_bit;
    00097   uint16 cb_id;
    00098 };
    00100 static const int CBM_NO_BIT = UINT8_MAX;
    00101
    00103 struct NIVariable {
    00104   const char *name;
    00105   byte var;
    00106 };
    00107
    00109 class NIHelper {
    00110 public:
    00112   virtual ~NIHelper() {}
    00113
    00119   virtual bool IsInspectable(uint index) const = 0;
    00120
    00126   virtual uint GetParent(uint index) const = 0;
    00127
    00133   virtual const void *GetInstance(uint index) const = 0;
    00134
    00140   virtual const void *GetSpec(uint index) const = 0;
    00141
    00146   virtual void SetStringParameters(uint index) const = 0;
    00147
    00153   virtual uint32 GetGRFID(uint index) const = 0;
    00154
    00163   virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
    00164
    00169   virtual bool PSAWithParameter() const
    00170   {
    00171     return false;
    00172   }
    00173
    00180   virtual uint GetPSASize(uint index, uint32 grfid) const
    00181   {
    00182     return 0;
    00183   }
    00184
    00191   virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
    00192   {
    00193     return NULL;
    00194   }
    00195
    00196 protected:
    00202   void SetSimpleStringParameters(StringID string, uint32 index) const
    00203   {
    00204     SetDParam(0, string);
    00205     SetDParam(1, index);
    00206   }
    00207
    00208
    00215   void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
    00216   {
    00217     SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
    00218     SetDParam(1, string);
    00219     SetDParam(2, index);
    00220     SetDParam(3, tile);
    00221   }
    00222 };
    00223
    00224
    00226 struct NIFeature {
    00227   const NIProperty *properties;
    00228   const NICallback *callbacks;
    00229   const NIVariable *variables;
    00230   const NIHelper   *helper;
    00231 };
    00232
    00233 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
    00234 #include "table/newgrf_debug_data.h"
    00235
    00241 static inline GrfSpecFeature GetFeatureNum(uint window_number)
    00242 {
    00243   return (GrfSpecFeature)GB(window_number, 24, 8);
    00244 }
    00245
    00251 static inline const NIFeature *GetFeature(uint window_number)
    00252 {
    00253   GrfSpecFeature idx = GetFeatureNum(window_number);
    00254   return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
    00255 }
    00256
    00263 static inline const NIHelper *GetFeatureHelper(uint window_number)
    00264 {
    00265   return GetFeature(window_number)->helper;
    00266 }
    00267
    00269 struct NewGRFInspectWindow : Window {
    00270   static const int LEFT_OFFSET   = 5;
    00271   static const int RIGHT_OFFSET  = 5;
    00272   static const int TOP_OFFSET    = 5;
    00273   static const int BOTTOM_OFFSET = 5;
    00274
    00276   static uint32 var60params[GSF_FAKE_END][0x20];
    00277
    00279   uint32 caller_grfid;
    00280
    00282   byte current_edit_param;
    00283
    00284   Scrollbar *vscroll;
    00285
    00291   static bool HasVariableParameter(uint variable)
    00292   {
    00293     return IsInsideBS(variable, 0x60, 0x20);
    00294   }
    00295
    00300   void SetCallerGRFID(uint32 grfid)
    00301   {
    00302     this->caller_grfid = grfid;
    00303     this->SetDirty();
    00304   }
    00305
    00306   NewGRFInspectWindow(const WindowDesc *desc, WindowNumber wno) : Window()
    00307   {
    00308     this->CreateNestedTree(desc);
    00309     this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
    00310     this->FinishInitNested(desc, wno);
    00311
    00312     this->vscroll->SetCount(0);
    00313     this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(GetFeatureIndex(this->window_number)) == UINT32_MAX);
    00314   }
    00315
    00316   virtual void SetStringParameters(int widget) const
    00317   {
    00318     if (widget != WID_NGRFI_CAPTION) return;
    00319
    00320     GetFeatureHelper(this->window_number)->SetStringParameters(GetFeatureIndex(this->window_number));
    00321   }
    00322
    00323   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
    00324   {
    00325     if (widget != WID_NGRFI_MAINPANEL) return;
    00326
    00327     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
    00328     resize->width  = 1;
    00329
    00330     size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
    00331   }
    00332
    00339   void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
    00340   {
    00341     char buf[1024];
    00342
    00343     va_list va;
    00344     va_start(va, format);
    00345     vsnprintf(buf, lengthof(buf), format, va);
    00346     va_end(va);
    00347
    00348     offset -= this->vscroll->GetPosition();
    00349     if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
    00350
    00351 		::DrawString(r.left + LEFT_OFFSET, r.right + RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
    00352   }
    00353
    00354   virtual void DrawWidget(const Rect &r, int widget) const
    00355   {
    00356     if (widget != WID_NGRFI_MAINPANEL) return;
    00357
    00358     uint index = GetFeatureIndex(this->window_number);
    00359     const NIFeature *nif  = GetFeature(this->window_number);
    00360     const NIHelper *nih   = nif->helper;
    00361     const void *base      = nih->GetInstance(index);
    00362     const void *base_spec = nih->GetSpec(index);
    00363
    00364     uint i = 0;
    00365     if (nif->variables != NULL) {
    00366       this->DrawString(r, i++, "Variables:");
    00367       for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
    00368         bool avail = true;
    00369         uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
    00370         uint value = nih->Resolve(index, niv->var, param, &avail);
    00371
    00372         if (!avail) continue;
    00373
    00374         if (HasVariableParameter(niv->var)) {
    00375           this->DrawString(r, i++, "  %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
    00376         } else {
    00377           this->DrawString(r, i++, "  %02x: %08x (%s)", niv->var, value, niv->name);
    00378         }
    00379       }
    00380     }
    00381
    00382     uint psa_size = nih->GetPSASize(index, this->caller_grfid);
    00383     const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
    00384     if (psa_size != 0 && psa != NULL) {
    00385       if (nih->PSAWithParameter()) {
    00386         this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
    00387       } else {
    00388         this->DrawString(r, i++, "Persistent storage:");
    00389       }
    00390       assert(psa_size % 4 == 0);
    00391       for (uint j = 0; j < psa_size; j += 4, psa += 4) {
    00392         this->DrawString(r, i++, "  %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
    00393       }
    00394     }
    00395
    00396     if (nif->properties != NULL) {
    00397       this->DrawString(r, i++, "Properties:");
    00398       for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
    00399         const void *ptr = (const byte *)base + nip->offset;
    00400         uint value;
    00401         switch (nip->read_size) {
    00402           case 1: value = *(const uint8  *)ptr; break;
    00403           case 2: value = *(const uint16 *)ptr; break;
    00404           case 4: value = *(const uint32 *)ptr; break;
    00405           default: NOT_REACHED();
    00406         }
    00407
    00408         StringID string;
    00409         SetDParam(0, value);
    00410         switch (nip->type) {
    00411           case NIT_INT:
    00412             string = STR_JUST_INT;
    00413             break;
    00414
    00415           case NIT_CARGO:
    00416             string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
    00417             break;
    00418
    00419           default:
    00420             NOT_REACHED();
    00421         }
    00422
    00423         char buffer[64];
    00424         GetString(buffer, string, lastof(buffer));
    00425         this->DrawString(r, i++, "  %02x: %s (%s)", nip->prop, buffer, nip->name);
    00426       }
    00427     }
    00428
    00429     if (nif->callbacks != NULL) {
    00430       this->DrawString(r, i++, "Callbacks:");
    00431       for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
    00432         if (nic->cb_bit != CBM_NO_BIT) {
    00433           const void *ptr = (const byte *)base_spec + nic->offset;
    00434           uint value;
    00435           switch (nic->read_size) {
    00436             case 1: value = *(const uint8  *)ptr; break;
    00437             case 2: value = *(const uint16 *)ptr; break;
    00438             case 4: value = *(const uint32 *)ptr; break;
    00439             default: NOT_REACHED();
    00440           }
    00441
    00442           if (!HasBit(value, nic->cb_bit)) continue;
    00443           this->DrawString(r, i++, "  %03x: %s", nic->cb_id, nic->name);
    00444         } else {
    00445           this->DrawString(r, i++, "  %03x: %s (unmasked)", nic->cb_id, nic->name);
    00446         }
    00447       }
    00448     }
    00449
    00450     /* Not nice and certainly a hack, but it beats duplicating
    00451      * this whole function just to count the actual number of
    00452      * elements. Especially because they need to be redrawn. */
    00453     const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
    00454   }
    00455
    00456   virtual void OnClick(Point pt, int widget, int click_count)
    00457   {
    00458     switch (widget) {
    00459       case WID_NGRFI_PARENT: {
    00460         const NIHelper *nih   = GetFeatureHelper(this->window_number);
    00461         uint index = nih->GetParent(GetFeatureIndex(this->window_number));
    00462 				::ShowNewGRFInspectWindow((GrfSpecFeature)GB(index, 24, 8), GetFeatureIndex(index), nih->GetGRFID(GetFeatureIndex(this->window_number)));
    00463         break;
    00464       }
    00465
    00466       case WID_NGRFI_MAINPANEL: {
    00467         /* Does this feature have variables? */
    00468         const NIFeature *nif  = GetFeature(this->window_number);
    00469         if (nif->variables == NULL) return;
    00470
    00471         /* Get the line, make sure it's within the boundaries. */
    00472         int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
    00473         if (line == INT_MAX) return;
    00474
    00475         /* Find the variable related to the line */
    00476         for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
    00477           if (line != 1) continue; // 1 because of the "Variables:" line
    00478
    00479           if (!HasVariableParameter(niv->var)) break;
    00480
    00481           this->current_edit_param = niv->var;
    00482           ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
    00483         }
    00484       }
    00485     }
    00486   }
    00487
    00488   virtual void OnQueryTextFinished(char *str)
    00489   {
    00490     if (StrEmpty(str)) return;
    00491
    00492     NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
    00493     this->SetDirty();
    00494   }
    00495
    00496   virtual void OnResize()
    00497   {
    00498     this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
    00499   }
    00500 };
    00501
    00502 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
    00503
    00504 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
    00505   NWidget(NWID_HORIZONTAL),
    00506     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
    00507     NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
    00508     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
    00509     NWidget(WWT_SHADEBOX, COLOUR_GREY),
    00510     NWidget(WWT_STICKYBOX, COLOUR_GREY),
    00511   EndContainer(),
    00512   NWidget(NWID_HORIZONTAL),
    00513     NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
    00514     NWidget(NWID_VERTICAL),
    00515       NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
    00516       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
    00517     EndContainer(),
    00518   EndContainer(),
    00519 };
    00520
    00521 static const WindowDesc _newgrf_inspect_desc(
    00522   WDP_AUTO, 400, 300,
    00523   WC_NEWGRF_INSPECT, WC_NONE,
    00524   0,
    00525   _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
    00526 );
    00527
    00537 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
    00538 {
    00539   if (!IsNewGRFInspectable(feature, index)) return;
    00540
    00541   WindowNumber wno = GetInspectWindowNumber(feature, index);
    00542   NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(&_newgrf_inspect_desc, wno);
    00543   if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
    00544   w->SetCallerGRFID(grfid);
    00545 }
    00546
    00555 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
    00556 {
    00557   if (feature == GSF_INVALID) return;
    00558
    00559   WindowNumber wno = GetInspectWindowNumber(feature, index);
    00560   DeleteWindowById(WC_NEWGRF_INSPECT, wno);
    00561
    00562   /* Reinitialise the land information window to remove the "debug" sprite if needed.
    00563    * Note: Since we might be called from a command here, it is important to not execute
    00564    * the invalidation immediately. The landinfo window tests commands itself. */
    00565   InvalidateWindowData(WC_LAND_INFO, 0, 1);
    00566 }
    00567
    00577 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
    00578 {
    00579   const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
    00580   if (nif == NULL) return false;
    00581   return nif->helper->IsInspectable(index);
    00582 }
    00583
    00589 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
    00590 {
    00591   switch (GetTileType(tile)) {
    00592     default:              return GSF_INVALID;
    00593     case MP_RAILWAY:      return GSF_RAILTYPES;
    00594     case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
    00595     case MP_HOUSE:        return GSF_HOUSES;
    00596     case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
    00597     case MP_OBJECT:       return GSF_OBJECTS;
    00598
    00599     case MP_STATION:
    00600       switch (GetStationType(tile)) {
    00601         case STATION_RAIL:    return GSF_STATIONS;
    00602         case STATION_AIRPORT: return GSF_AIRPORTTILES;
    00603         default:              return GSF_INVALID;
    00604       }
    00605   }
    00606 }
    00607
    00613 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
    00614 {
    00615   switch (type) {
    00616     case VEH_TRAIN:    return GSF_TRAINS;
    00617     case VEH_ROAD:     return GSF_ROADVEHICLES;
    00618     case VEH_SHIP:     return GSF_SHIPS;
    00619     case VEH_AIRCRAFT: return GSF_AIRCRAFT;
    00620     default:           return GSF_INVALID;
    00621   }
    00622 }
    00623
    00624
    00625
    00626 /**** Sprite Aligner ****/
    00627
    00629 struct SpriteAlignerWindow : Window {
    00630   SpriteID current_sprite;
    00631   Scrollbar *vscroll;
    00632
    00633   SpriteAlignerWindow(const WindowDesc *desc, WindowNumber wno) : Window()
    00634   {
    00635     this->CreateNestedTree(desc);
    00636     this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
    00637     this->FinishInitNested(desc, wno);
    00638
    00639     /* Oh yes, we assume there is at least one normal sprite! */
    00640     while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
    00641   }
    00642
    00643   virtual void SetStringParameters(int widget) const
    00644   {
    00645     switch (widget) {
    00646       case WID_SA_CAPTION:
    00647         SetDParam(0, this->current_sprite);
    00648         SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
    00649         break;
    00650
    00651       case WID_SA_OFFSETS: {
    00652         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
    00653         SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
    00654         SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
    00655         break;
    00656       }
    00657
    00658       default:
    00659         break;
    00660     }
    00661   }
    00662
    00663   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
    00664   {
    00665     if (widget != WID_SA_LIST) return;
    00666
    00667     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
    00668     resize->width  = 1;
    00669
    00670     /* Resize to about 200 pixels (for the preview) */
    00671     size->height = (1 + 200 / resize->height) * resize->height;
    00672   }
    00673
    00674   virtual void DrawWidget(const Rect &r, int widget) const
    00675   {
    00676     switch (widget) {
    00677       case WID_SA_SPRITE: {
    00678         /* Center the sprite ourselves */
    00679         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
    00680         int width  = r.right  - r.left + 1;
    00681         int height = r.bottom - r.top  + 1;
    00682         int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width  - spr->width / ZOOM_LVL_BASE) / 2;
    00683         int y = r.top  - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
    00684
    00685         /* And draw only the part within the sprite area */
    00686         SubSprite subspr = {
    00687           spr->x_offs + (spr->width  - width  * ZOOM_LVL_BASE) / 2 + 1,
    00688           spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
    00689           spr->x_offs + (spr->width  + width  * ZOOM_LVL_BASE) / 2 - 1,
    00690           spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
    00691         };
    00692
    00693         DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
    00694         break;
    00695       }
    00696
    00697       case WID_SA_LIST: {
    00698         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
    00699         int step_size = nwid->resize_y;
    00700
    00701         SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
    00702         int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
    00703
    00704         int y = r.top + WD_FRAMERECT_TOP;
    00705         for (int i = this->vscroll->GetPosition(); i < max; i++) {
    00706           SetDParam(0, list[i]);
    00707           DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
    00708           y += step_size;
    00709         }
    00710         break;
    00711       }
    00712     }
    00713   }
    00714
    00715   virtual void OnClick(Point pt, int widget, int click_count)
    00716   {
    00717     switch (widget) {
    00718       case WID_SA_PREVIOUS:
    00719         do {
    00720           this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() :  this->current_sprite) - 1;
    00721         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
    00722         this->SetDirty();
    00723         break;
    00724
    00725       case WID_SA_GOTO:
    00726         ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
    00727         break;
    00728
    00729       case WID_SA_NEXT:
    00730         do {
    00731           this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
    00732         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
    00733         this->SetDirty();
    00734         break;
    00735
    00736       case WID_SA_PICKER:
    00737         this->LowerWidget(WID_SA_PICKER);
    00738         _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
    00739         this->SetDirty();
    00740         break;
    00741
    00742       case WID_SA_LIST: {
    00743         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
    00744         int step_size = nwid->resize_y;
    00745
    00746         uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
    00747         if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
    00748           SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
    00749           if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
    00750         }
    00751         this->SetDirty();
    00752         break;
    00753       }
    00754
    00755       case WID_SA_UP:
    00756       case WID_SA_DOWN:
    00757       case WID_SA_LEFT:
    00758       case WID_SA_RIGHT: {
    00759         /*
    00760          * Yes... this is a hack.
    00761          *
    00762          * No... I don't think it is useful to make this less of a hack.
    00763          *
    00764          * If you want to align sprites, you just need the number. Generally
    00765          * the sprite caches are big enough to not remove the sprite from the
    00766          * cache. If that's not the case, just let the NewGRF developer
    00767          * increase the cache size instead of storing thousands of offsets
    00768          * for the incredibly small chance that it's actually going to be
    00769          * used by someone and the sprite cache isn't big enough for that
    00770          * particular NewGRF developer.
    00771          */
    00772         Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
    00773         switch (widget) {
    00774           case WID_SA_UP:    spr->y_offs -= ZOOM_LVL_BASE; break;
    00775           case WID_SA_DOWN:  spr->y_offs += ZOOM_LVL_BASE; break;
    00776           case WID_SA_LEFT:  spr->x_offs -= ZOOM_LVL_BASE; break;
    00777           case WID_SA_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
    00778         }
    00779         /* Ofcourse, we need to redraw the sprite, but where is it used?
    00780          * Everywhere is a safe bet. */
    00781         MarkWholeScreenDirty();
    00782         break;
    00783       }
    00784     }
    00785   }
    00786
    00787   virtual void OnQueryTextFinished(char *str)
    00788   {
    00789     if (StrEmpty(str)) return;
    00790
    00791     this->current_sprite = atoi(str);
    00792     if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
    00793     while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
    00794       this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
    00795     }
    00796     this->SetDirty();
    00797   }
    00798
    00804   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00805   {
    00806     if (!gui_scope) return;
    00807     if (data == 1) {
    00808       /* Sprite picker finished */
    00809       this->RaiseWidget(WID_SA_PICKER);
    00810       this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
    00811     }
    00812   }
    00813
    00814   virtual void OnResize()
    00815   {
    00816     this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
    00817     this->GetWidget<NWidgetCore>(WID_SA_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
    00818   }
    00819 };
    00820
    00821 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
    00822   NWidget(NWID_HORIZONTAL),
    00823     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
    00824     NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
    00825     NWidget(WWT_SHADEBOX, COLOUR_GREY),
    00826     NWidget(WWT_STICKYBOX, COLOUR_GREY),
    00827   EndContainer(),
    00828   NWidget(WWT_PANEL, COLOUR_GREY),
    00829     NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
    00830       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
    00831         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
    00832           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
    00833           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
    00834           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
    00835         EndContainer(),
    00836         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
    00837           NWidget(NWID_SPACER), SetFill(1, 1),
    00838           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
    00839           NWidget(NWID_SPACER), SetFill(1, 1),
    00840         EndContainer(),
    00841         NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
    00842           NWidget(NWID_VERTICAL),
    00843             NWidget(NWID_SPACER), SetFill(1, 1),
    00844             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
    00845             NWidget(NWID_SPACER), SetFill(1, 1),
    00846           EndContainer(),
    00847           NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
    00848           EndContainer(),
    00849           NWidget(NWID_VERTICAL),
    00850             NWidget(NWID_SPACER), SetFill(1, 1),
    00851             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
    00852             NWidget(NWID_SPACER), SetFill(1, 1),
    00853           EndContainer(),
    00854         EndContainer(),
    00855         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
    00856           NWidget(NWID_SPACER), SetFill(1, 1),
    00857           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
    00858           NWidget(NWID_SPACER), SetFill(1, 1),
    00859         EndContainer(),
    00860         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
    00861           NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
    00862         EndContainer(),
    00863       EndContainer(),
    00864       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
    00865         NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
    00866         NWidget(NWID_HORIZONTAL),
    00867           NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetDataTip(0x101, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
    00868           NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
    00869         EndContainer(),
    00870       EndContainer(),
    00871     EndContainer(),
    00872   EndContainer(),
    00873 };
    00874
    00875 static const WindowDesc _sprite_aligner_desc(
    00876   WDP_AUTO, 400, 300,
    00877   WC_SPRITE_ALIGNER, WC_NONE,
    00878   0,
    00879   _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
    00880 );
    00881
    00885 void ShowSpriteAlignerWindow()
    00886 {
    00887   AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
    00888 }