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 
00036 #include "table/strings.h"
00037 
00039 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
00040 
00046 static inline uint GetFeatureIndex(uint window_number)
00047 {
00048   return GB(window_number, 0, 24);
00049 }
00050 
00058 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
00059 {
00060   assert((index >> 24) == 0);
00061   return (feature << 24) | index;
00062 }
00063 
00068 enum NIType {
00069   NIT_INT,   
00070   NIT_CARGO, 
00071 };
00072 
00074 struct NIProperty {
00075   const char *name;       
00076   ptrdiff_t offset;       
00077   byte read_size;         
00078   byte prop;              
00079   byte type;
00080 };
00081 
00082 
00087 struct NICallback {
00088   const char *name; 
00089   ptrdiff_t offset; 
00090   byte read_size;   
00091   byte cb_bit;      
00092   uint16 cb_id;     
00093 };
00095 static const int CBM_NO_BIT = UINT8_MAX;
00096 
00098 struct NIVariable {
00099   const char *name;
00100   byte var;
00101 };
00102 
00104 class NIHelper {
00105 public:
00107   virtual ~NIHelper() {}
00108 
00114   virtual bool IsInspectable(uint index) const = 0;
00115 
00121   virtual uint GetParent(uint index) const = 0;
00122 
00128   virtual const void *GetInstance(uint index) const = 0;
00129 
00135   virtual const void *GetSpec(uint index) const = 0;
00136 
00141   virtual void SetStringParameters(uint index) const = 0;
00142 
00148   virtual uint32 GetGRFID(uint index) const = 0;
00149 
00158   virtual uint Resolve(uint index, uint var, uint param, bool *avail) const
00159   {
00160     ResolverObject ro;
00161     memset(&ro, 0, sizeof(ro));
00162     this->Resolve(&ro, index);
00163     return ro.GetVariable(&ro, var, param, avail);
00164   }
00165 
00170   virtual bool PSAWithParameter() const
00171   {
00172     return false;
00173   }
00174 
00181   virtual uint GetPSASize(uint index, uint32 grfid) const
00182   {
00183     return 0;
00184   }
00185 
00192   virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
00193   {
00194     return NULL;
00195   }
00196 
00197 protected:
00204   virtual void Resolve(ResolverObject *ro, uint index) const {}
00205 
00211   void SetSimpleStringParameters(StringID string, uint32 index) const
00212   {
00213     SetDParam(0, string);
00214     SetDParam(1, index);
00215   }
00216 
00217 
00224   void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
00225   {
00226     SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
00227     SetDParam(1, string);
00228     SetDParam(2, index);
00229     SetDParam(3, tile);
00230   }
00231 };
00232 
00233 
00235 struct NIFeature {
00236   const NIProperty *properties; 
00237   const NICallback *callbacks;  
00238   const NIVariable *variables;  
00239   const NIHelper   *helper;     
00240 };
00241 
00242 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
00243 #include "table/newgrf_debug_data.h"
00244 
00250 static inline GrfSpecFeature GetFeatureNum(uint window_number)
00251 {
00252   return (GrfSpecFeature)GB(window_number, 24, 8);
00253 }
00254 
00260 static inline const NIFeature *GetFeature(uint window_number)
00261 {
00262   GrfSpecFeature idx = GetFeatureNum(window_number);
00263   return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
00264 }
00265 
00272 static inline const NIHelper *GetFeatureHelper(uint window_number)
00273 {
00274   return GetFeature(window_number)->helper;
00275 }
00276 
00277 
00279 enum NewGRFInspectWidgets {
00280   NIW_CAPTION,   
00281   NIW_PARENT,    
00282   NIW_MAINPANEL, 
00283   NIW_SCROLLBAR, 
00284 };
00285 
00287 struct NewGRFInspectWindow : Window {
00288   static const int LEFT_OFFSET   = 5; 
00289   static const int RIGHT_OFFSET  = 5; 
00290   static const int TOP_OFFSET    = 5; 
00291   static const int BOTTOM_OFFSET = 5; 
00292 
00294   static uint32 var60params[GSF_FAKE_END][0x20];
00295 
00297   uint32 caller_grfid;
00298 
00300   byte current_edit_param;
00301 
00302   Scrollbar *vscroll;
00303 
00309   static bool HasVariableParameter(uint variable)
00310   {
00311     return IsInsideBS(variable, 0x60, 0x20);
00312   }
00313 
00318   void SetCallerGRFID(uint32 grfid)
00319   {
00320     this->caller_grfid = grfid;
00321     this->SetDirty();
00322   }
00323 
00324   NewGRFInspectWindow(const WindowDesc *desc, WindowNumber wno) : Window()
00325   {
00326     this->CreateNestedTree(desc);
00327     this->vscroll = this->GetScrollbar(NIW_SCROLLBAR);
00328     this->FinishInitNested(desc, wno);
00329 
00330     this->vscroll->SetCount(0);
00331     this->SetWidgetDisabledState(NIW_PARENT, GetFeatureHelper(this->window_number)->GetParent(GetFeatureIndex(this->window_number)) == UINT32_MAX);
00332   }
00333 
00334   virtual void SetStringParameters(int widget) const
00335   {
00336     if (widget != NIW_CAPTION) return;
00337 
00338     GetFeatureHelper(this->window_number)->SetStringParameters(GetFeatureIndex(this->window_number));
00339   }
00340 
00341   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00342   {
00343     if (widget != NIW_MAINPANEL) return;
00344 
00345     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
00346     resize->width  = 1;
00347 
00348     size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
00349   }
00350 
00357   void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
00358   {
00359     char buf[1024];
00360 
00361     va_list va;
00362     va_start(va, format);
00363     vsnprintf(buf, lengthof(buf), format, va);
00364     va_end(va);
00365 
00366     offset -= this->vscroll->GetPosition();
00367     if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
00368 
00369 		::DrawString(r.left + LEFT_OFFSET, r.right + RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
00370   }
00371 
00372   virtual void DrawWidget(const Rect &r, int widget) const
00373   {
00374     if (widget != NIW_MAINPANEL) return;
00375 
00376     uint index = GetFeatureIndex(this->window_number);
00377     const NIFeature *nif  = GetFeature(this->window_number);
00378     const NIHelper *nih   = nif->helper;
00379     const void *base      = nih->GetInstance(index);
00380     const void *base_spec = nih->GetSpec(index);
00381 
00382     uint i = 0;
00383     if (nif->variables != NULL) {
00384       this->DrawString(r, i++, "Variables:");
00385       for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
00386         bool avail = true;
00387         uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
00388         uint value = nih->Resolve(index, niv->var, param, &avail);
00389 
00390         if (!avail) continue;
00391 
00392         if (HasVariableParameter(niv->var)) {
00393           this->DrawString(r, i++, "  %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
00394         } else {
00395           this->DrawString(r, i++, "  %02x: %08x (%s)", niv->var, value, niv->name);
00396         }
00397       }
00398     }
00399 
00400     uint psa_size = nih->GetPSASize(index, this->caller_grfid);
00401     const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
00402     if (psa_size != 0 && psa != NULL) {
00403       if (nih->PSAWithParameter()) {
00404         this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
00405       } else {
00406         this->DrawString(r, i++, "Persistent storage:");
00407       }
00408       assert(psa_size % 4 == 0);
00409       for (uint j = 0; j < psa_size; j += 4, psa += 4) {
00410         this->DrawString(r, i++, "  %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
00411       }
00412     }
00413 
00414     if (nif->properties != NULL) {
00415       this->DrawString(r, i++, "Properties:");
00416       for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
00417         const void *ptr = (const byte *)base + nip->offset;
00418         uint value;
00419         switch (nip->read_size) {
00420           case 1: value = *(const uint8  *)ptr; break;
00421           case 2: value = *(const uint16 *)ptr; break;
00422           case 4: value = *(const uint32 *)ptr; break;
00423           default: NOT_REACHED();
00424         }
00425 
00426         StringID string;
00427         SetDParam(0, value);
00428         switch (nip->type) {
00429           case NIT_INT:
00430             string = STR_JUST_INT;
00431             break;
00432 
00433           case NIT_CARGO:
00434             string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
00435             break;
00436 
00437           default:
00438             NOT_REACHED();
00439         }
00440 
00441         char buffer[64];
00442         GetString(buffer, string, lastof(buffer));
00443         this->DrawString(r, i++, "  %02x: %s (%s)", nip->prop, buffer, nip->name);
00444       }
00445     }
00446 
00447     if (nif->callbacks != NULL) {
00448       this->DrawString(r, i++, "Callbacks:");
00449       for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
00450         if (nic->cb_bit != CBM_NO_BIT) {
00451           const void *ptr = (const byte *)base_spec + nic->offset;
00452           uint value;
00453           switch (nic->read_size) {
00454             case 1: value = *(const uint8  *)ptr; break;
00455             case 2: value = *(const uint16 *)ptr; break;
00456             case 4: value = *(const uint32 *)ptr; break;
00457             default: NOT_REACHED();
00458           }
00459 
00460           if (!HasBit(value, nic->cb_bit)) continue;
00461           this->DrawString(r, i++, "  %03x: %s", nic->cb_id, nic->name);
00462         } else {
00463           this->DrawString(r, i++, "  %03x: %s (unmasked)", nic->cb_id, nic->name);
00464         }
00465       }
00466     }
00467 
00468     /* Not nice and certainly a hack, but it beats duplicating
00469      * this whole function just to count the actual number of
00470      * elements. Especially because they need to be redrawn. */
00471     const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
00472   }
00473 
00474   virtual void OnClick(Point pt, int widget, int click_count)
00475   {
00476     switch (widget) {
00477       case NIW_PARENT: {
00478         const NIHelper *nih   = GetFeatureHelper(this->window_number);
00479         uint index = nih->GetParent(GetFeatureIndex(this->window_number));
00480 				::ShowNewGRFInspectWindow((GrfSpecFeature)GB(index, 24, 8), GetFeatureIndex(index), nih->GetGRFID(GetFeatureIndex(this->window_number)));
00481         break;
00482       }
00483 
00484       case NIW_MAINPANEL: {
00485         /* Does this feature have variables? */
00486         const NIFeature *nif  = GetFeature(this->window_number);
00487         if (nif->variables == NULL) return;
00488 
00489         /* Get the line, make sure it's within the boundaries. */
00490         int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, NIW_MAINPANEL, TOP_OFFSET);
00491         if (line == INT_MAX) return;
00492 
00493         /* Find the variable related to the line */
00494         for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
00495           if (line != 1) continue; // 1 because of the "Variables:" line
00496 
00497           if (!HasVariableParameter(niv->var)) break;
00498 
00499           this->current_edit_param = niv->var;
00500           ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
00501         }
00502       }
00503     }
00504   }
00505 
00506   virtual void OnQueryTextFinished(char *str)
00507   {
00508     if (StrEmpty(str)) return;
00509 
00510     NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
00511     this->SetDirty();
00512   }
00513 
00514   virtual void OnResize()
00515   {
00516     this->vscroll->SetCapacityFromWidget(this, NIW_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
00517   }
00518 };
00519 
00520 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
00521 
00522 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
00523   NWidget(NWID_HORIZONTAL),
00524     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00525     NWidget(WWT_CAPTION, COLOUR_GREY, NIW_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00526     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, NIW_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
00527     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00528     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00529   EndContainer(),
00530   NWidget(NWID_HORIZONTAL),
00531     NWidget(WWT_PANEL, COLOUR_GREY, NIW_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(NIW_SCROLLBAR), EndContainer(),
00532     NWidget(NWID_VERTICAL),
00533       NWidget(NWID_VSCROLLBAR, COLOUR_GREY, NIW_SCROLLBAR),
00534       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00535     EndContainer(),
00536   EndContainer(),
00537 };
00538 
00539 static const WindowDesc _newgrf_inspect_desc(
00540   WDP_AUTO, 400, 300,
00541   WC_NEWGRF_INSPECT, WC_NONE,
00542   WDF_UNCLICK_BUTTONS,
00543   _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
00544 );
00545 
00555 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
00556 {
00557   if (!IsNewGRFInspectable(feature, index)) return;
00558 
00559   WindowNumber wno = GetInspectWindowNumber(feature, index);
00560   NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(&_newgrf_inspect_desc, wno);
00561   if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
00562   w->SetCallerGRFID(grfid);
00563 }
00564 
00573 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
00574 {
00575   if (feature == GSF_INVALID) return;
00576 
00577   WindowNumber wno = GetInspectWindowNumber(feature, index);
00578   DeleteWindowById(WC_NEWGRF_INSPECT, wno);
00579 
00580   /* Reinitialise the land information window to remove the "debug" sprite if needed.
00581    * Note: Since we might be called from a command here, it is important to not execute
00582    * the invalidation immediately. The landinfo window tests commands itself. */
00583   InvalidateWindowData(WC_LAND_INFO, 0, 1);
00584 }
00585 
00595 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
00596 {
00597   const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
00598   if (nif == NULL) return false;
00599   return nif->helper->IsInspectable(index);
00600 }
00601 
00607 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
00608 {
00609   switch (GetTileType(tile)) {
00610     default:              return GSF_INVALID;
00611     case MP_RAILWAY:      return GSF_RAILTYPES;
00612     case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
00613     case MP_HOUSE:        return GSF_HOUSES;
00614     case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
00615     case MP_OBJECT:       return GSF_OBJECTS;
00616 
00617     case MP_STATION:
00618       switch (GetStationType(tile)) {
00619         case STATION_RAIL:    return GSF_STATIONS;
00620         case STATION_AIRPORT: return GSF_AIRPORTTILES;
00621         default:              return GSF_INVALID;
00622       }
00623   }
00624 }
00625 
00631 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
00632 {
00633   switch (type) {
00634     case VEH_TRAIN:    return GSF_TRAINS;
00635     case VEH_ROAD:     return GSF_ROADVEHICLES;
00636     case VEH_SHIP:     return GSF_SHIPS;
00637     case VEH_AIRCRAFT: return GSF_AIRCRAFT;
00638     default:           return GSF_INVALID;
00639   }
00640 }
00641 
00642 
00643 
00644 /**** Sprite Aligner ****/
00645 
00647 enum SpriteAlignerWidgets {
00648   SAW_CAPTION,  
00649   SAW_PREVIOUS, 
00650   SAW_GOTO,     
00651   SAW_NEXT,     
00652   SAW_UP,       
00653   SAW_LEFT,     
00654   SAW_RIGHT,    
00655   SAW_DOWN,     
00656   SAW_SPRITE,   
00657   SAW_OFFSETS,  
00658   SAW_PICKER,   
00659   SAW_LIST,     
00660   SAW_SCROLLBAR,
00661 };
00662 
00664 struct SpriteAlignerWindow : Window {
00665   SpriteID current_sprite; 
00666   Scrollbar *vscroll;
00667 
00668   SpriteAlignerWindow(const WindowDesc *desc, WindowNumber wno) : Window()
00669   {
00670     this->CreateNestedTree(desc);
00671     this->vscroll = this->GetScrollbar(SAW_SCROLLBAR);
00672     this->FinishInitNested(desc, wno);
00673 
00674     /* Oh yes, we assume there is at least one normal sprite! */
00675     while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
00676   }
00677 
00678   virtual void SetStringParameters(int widget) const
00679   {
00680     switch (widget) {
00681       case SAW_CAPTION:
00682         SetDParam(0, this->current_sprite);
00683         SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
00684         break;
00685 
00686       case SAW_OFFSETS: {
00687         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00688         SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
00689         SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
00690         break;
00691       }
00692 
00693       default:
00694         break;
00695     }
00696   }
00697 
00698   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00699   {
00700     if (widget != SAW_LIST) return;
00701 
00702     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
00703     resize->width  = 1;
00704 
00705     /* Resize to about 200 pixels (for the preview) */
00706     size->height = (1 + 200 / resize->height) * resize->height;
00707   }
00708 
00709   virtual void DrawWidget(const Rect &r, int widget) const
00710   {
00711     switch (widget) {
00712       case SAW_SPRITE: {
00713         /* Center the sprite ourselves */
00714         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00715         int width  = r.right  - r.left + 1;
00716         int height = r.bottom - r.top  + 1;
00717         int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width  - spr->width / ZOOM_LVL_BASE) / 2;
00718         int y = r.top  - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
00719 
00720         /* And draw only the part within the sprite area */
00721         SubSprite subspr = {
00722           spr->x_offs + (spr->width  - width  * ZOOM_LVL_BASE) / 2 + 1,
00723           spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
00724           spr->x_offs + (spr->width  + width  * ZOOM_LVL_BASE) / 2 - 1,
00725           spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
00726         };
00727 
00728         DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
00729         break;
00730       }
00731 
00732       case SAW_LIST: {
00733         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00734         int step_size = nwid->resize_y;
00735 
00736         SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
00737         int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
00738 
00739         int y = r.top + WD_FRAMERECT_TOP;
00740         for (int i = this->vscroll->GetPosition(); i < max; i++) {
00741           SetDParam(0, list[i]);
00742           DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
00743           y += step_size;
00744         }
00745         break;
00746       }
00747     }
00748   }
00749 
00750   virtual void OnClick(Point pt, int widget, int click_count)
00751   {
00752     switch (widget) {
00753       case SAW_PREVIOUS:
00754         do {
00755           this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() :  this->current_sprite) - 1;
00756         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00757         this->SetDirty();
00758         break;
00759 
00760       case SAW_GOTO:
00761         ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
00762         break;
00763 
00764       case SAW_NEXT:
00765         do {
00766           this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00767         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00768         this->SetDirty();
00769         break;
00770 
00771       case SAW_PICKER:
00772         this->LowerWidget(SAW_PICKER);
00773         _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
00774         this->SetDirty();
00775         break;
00776 
00777       case SAW_LIST: {
00778         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00779         int step_size = nwid->resize_y;
00780 
00781         uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
00782         if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
00783           SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
00784           if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
00785         }
00786         this->SetDirty();
00787         break;
00788       }
00789 
00790       case SAW_UP:
00791       case SAW_DOWN:
00792       case SAW_LEFT:
00793       case SAW_RIGHT: {
00794         /*
00795          * Yes... this is a hack.
00796          *
00797          * No... I don't think it is useful to make this less of a hack.
00798          *
00799          * If you want to align sprites, you just need the number. Generally
00800          * the sprite caches are big enough to not remove the sprite from the
00801          * cache. If that's not the case, just let the NewGRF developer
00802          * increase the cache size instead of storing thousands of offsets
00803          * for the incredibly small chance that it's actually going to be
00804          * used by someone and the sprite cache isn't big enough for that
00805          * particular NewGRF developer.
00806          */
00807         Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
00808         switch (widget) {
00809           case SAW_UP:    spr->y_offs -= ZOOM_LVL_BASE; break;
00810           case SAW_DOWN:  spr->y_offs += ZOOM_LVL_BASE; break;
00811           case SAW_LEFT:  spr->x_offs -= ZOOM_LVL_BASE; break;
00812           case SAW_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
00813         }
00814         /* Ofcourse, we need to redraw the sprite, but where is it used?
00815          * Everywhere is a safe bet. */
00816         MarkWholeScreenDirty();
00817         break;
00818       }
00819     }
00820   }
00821 
00822   virtual void OnQueryTextFinished(char *str)
00823   {
00824     if (StrEmpty(str)) return;
00825 
00826     this->current_sprite = atoi(str);
00827     if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
00828     while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
00829       this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00830     }
00831     this->SetDirty();
00832   }
00833 
00839   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00840   {
00841     if (!gui_scope) return;
00842     if (data == 1) {
00843       /* Sprite picker finished */
00844       this->RaiseWidget(SAW_PICKER);
00845       this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
00846     }
00847   }
00848 
00849   virtual void OnResize()
00850   {
00851     this->vscroll->SetCapacityFromWidget(this, SAW_LIST);
00852     this->GetWidget<NWidgetCore>(SAW_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
00853   }
00854 };
00855 
00856 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
00857   NWidget(NWID_HORIZONTAL),
00858     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00859     NWidget(WWT_CAPTION, COLOUR_GREY, SAW_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00860     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00861     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00862   EndContainer(),
00863   NWidget(WWT_PANEL, COLOUR_GREY),
00864     NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
00865       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
00866         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
00867           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SAW_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
00868           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SAW_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
00869           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, SAW_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
00870         EndContainer(),
00871         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00872           NWidget(NWID_SPACER), SetFill(1, 1),
00873           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SAW_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00874           NWidget(NWID_SPACER), SetFill(1, 1),
00875         EndContainer(),
00876         NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
00877           NWidget(NWID_VERTICAL),
00878             NWidget(NWID_SPACER), SetFill(1, 1),
00879             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SAW_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00880             NWidget(NWID_SPACER), SetFill(1, 1),
00881           EndContainer(),
00882           NWidget(WWT_PANEL, COLOUR_DARK_BLUE, SAW_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
00883           EndContainer(),
00884           NWidget(NWID_VERTICAL),
00885             NWidget(NWID_SPACER), SetFill(1, 1),
00886             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SAW_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00887             NWidget(NWID_SPACER), SetFill(1, 1),
00888           EndContainer(),
00889         EndContainer(),
00890         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00891           NWidget(NWID_SPACER), SetFill(1, 1),
00892           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, SAW_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00893           NWidget(NWID_SPACER), SetFill(1, 1),
00894         EndContainer(),
00895         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00896           NWidget(WWT_LABEL, COLOUR_GREY, SAW_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
00897         EndContainer(),
00898       EndContainer(),
00899       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
00900         NWidget(WWT_TEXTBTN, COLOUR_GREY, SAW_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
00901         NWidget(NWID_HORIZONTAL),
00902           NWidget(WWT_MATRIX, COLOUR_GREY, SAW_LIST), SetResize(1, 1), SetDataTip(0x101, STR_NULL), SetFill(1, 1), SetScrollbar(SAW_SCROLLBAR),
00903           NWidget(NWID_VSCROLLBAR, COLOUR_GREY, SAW_SCROLLBAR),
00904         EndContainer(),
00905       EndContainer(),
00906     EndContainer(),
00907   EndContainer(),
00908 };
00909 
00910 static const WindowDesc _sprite_aligner_desc(
00911   WDP_AUTO, 400, 300,
00912   WC_SPRITE_ALIGNER, WC_NONE,
00913   WDF_UNCLICK_BUTTONS,
00914   _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
00915 );
00916 
00920 void ShowSpriteAlignerWindow()
00921 {
00922   AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
00923 }