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(WindowDesc *desc, WindowNumber wno) : Window(desc)
00307   {
00308     this->CreateNestedTree();
00309     this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
00310     this->FinishInitNested(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_DEFSIZEBOX, COLOUR_GREY),
00511     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00512   EndContainer(),
00513   NWidget(NWID_HORIZONTAL),
00514     NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
00515     NWidget(NWID_VERTICAL),
00516       NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
00517       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00518     EndContainer(),
00519   EndContainer(),
00520 };
00521 
00522 static WindowDesc _newgrf_inspect_desc(
00523   WDP_AUTO, "newgrf_inspect", 400, 300,
00524   WC_NEWGRF_INSPECT, WC_NONE,
00525   0,
00526   _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
00527 );
00528 
00538 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
00539 {
00540   if (!IsNewGRFInspectable(feature, index)) return;
00541 
00542   WindowNumber wno = GetInspectWindowNumber(feature, index);
00543   NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(&_newgrf_inspect_desc, wno);
00544   if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
00545   w->SetCallerGRFID(grfid);
00546 }
00547 
00556 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
00557 {
00558   if (feature == GSF_INVALID) return;
00559 
00560   WindowNumber wno = GetInspectWindowNumber(feature, index);
00561   DeleteWindowById(WC_NEWGRF_INSPECT, wno);
00562 
00563   /* Reinitialise the land information window to remove the "debug" sprite if needed.
00564    * Note: Since we might be called from a command here, it is important to not execute
00565    * the invalidation immediately. The landinfo window tests commands itself. */
00566   InvalidateWindowData(WC_LAND_INFO, 0, 1);
00567 }
00568 
00578 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
00579 {
00580   const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
00581   if (nif == NULL) return false;
00582   return nif->helper->IsInspectable(index);
00583 }
00584 
00590 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
00591 {
00592   switch (GetTileType(tile)) {
00593     default:              return GSF_INVALID;
00594     case MP_RAILWAY:      return GSF_RAILTYPES;
00595     case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
00596     case MP_HOUSE:        return GSF_HOUSES;
00597     case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
00598     case MP_OBJECT:       return GSF_OBJECTS;
00599 
00600     case MP_STATION:
00601       switch (GetStationType(tile)) {
00602         case STATION_RAIL:    return GSF_STATIONS;
00603         case STATION_AIRPORT: return GSF_AIRPORTTILES;
00604         default:              return GSF_INVALID;
00605       }
00606   }
00607 }
00608 
00614 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
00615 {
00616   switch (type) {
00617     case VEH_TRAIN:    return GSF_TRAINS;
00618     case VEH_ROAD:     return GSF_ROADVEHICLES;
00619     case VEH_SHIP:     return GSF_SHIPS;
00620     case VEH_AIRCRAFT: return GSF_AIRCRAFT;
00621     default:           return GSF_INVALID;
00622   }
00623 }
00624 
00625 
00626 
00627 /**** Sprite Aligner ****/
00628 
00630 struct SpriteAlignerWindow : Window {
00631   SpriteID current_sprite; 
00632   Scrollbar *vscroll;
00633 
00634   SpriteAlignerWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
00635   {
00636     this->CreateNestedTree();
00637     this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
00638     this->FinishInitNested(wno);
00639 
00640     /* Oh yes, we assume there is at least one normal sprite! */
00641     while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
00642   }
00643 
00644   virtual void SetStringParameters(int widget) const
00645   {
00646     switch (widget) {
00647       case WID_SA_CAPTION:
00648         SetDParam(0, this->current_sprite);
00649         SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
00650         break;
00651 
00652       case WID_SA_OFFSETS: {
00653         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00654         SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
00655         SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
00656         break;
00657       }
00658 
00659       default:
00660         break;
00661     }
00662   }
00663 
00664   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00665   {
00666     if (widget != WID_SA_LIST) return;
00667 
00668     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
00669     resize->width  = 1;
00670 
00671     /* Resize to about 200 pixels (for the preview) */
00672     size->height = (1 + 200 / resize->height) * resize->height;
00673   }
00674 
00675   virtual void DrawWidget(const Rect &r, int widget) const
00676   {
00677     switch (widget) {
00678       case WID_SA_SPRITE: {
00679         /* Center the sprite ourselves */
00680         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00681         int width  = r.right  - r.left + 1;
00682         int height = r.bottom - r.top  + 1;
00683         int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width  - spr->width / ZOOM_LVL_BASE) / 2;
00684         int y = r.top  - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
00685 
00686         /* And draw only the part within the sprite area */
00687         SubSprite subspr = {
00688           spr->x_offs + (spr->width  - width  * ZOOM_LVL_BASE) / 2 + 1,
00689           spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
00690           spr->x_offs + (spr->width  + width  * ZOOM_LVL_BASE) / 2 - 1,
00691           spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
00692         };
00693 
00694         DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
00695         break;
00696       }
00697 
00698       case WID_SA_LIST: {
00699         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00700         int step_size = nwid->resize_y;
00701 
00702         SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
00703         int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
00704 
00705         int y = r.top + WD_FRAMERECT_TOP;
00706         for (int i = this->vscroll->GetPosition(); i < max; i++) {
00707           SetDParam(0, list[i]);
00708           DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
00709           y += step_size;
00710         }
00711         break;
00712       }
00713     }
00714   }
00715 
00716   virtual void OnClick(Point pt, int widget, int click_count)
00717   {
00718     switch (widget) {
00719       case WID_SA_PREVIOUS:
00720         do {
00721           this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() :  this->current_sprite) - 1;
00722         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00723         this->SetDirty();
00724         break;
00725 
00726       case WID_SA_GOTO:
00727         ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
00728         break;
00729 
00730       case WID_SA_NEXT:
00731         do {
00732           this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00733         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00734         this->SetDirty();
00735         break;
00736 
00737       case WID_SA_PICKER:
00738         this->LowerWidget(WID_SA_PICKER);
00739         _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
00740         this->SetDirty();
00741         break;
00742 
00743       case WID_SA_LIST: {
00744         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00745         int step_size = nwid->resize_y;
00746 
00747         uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
00748         if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
00749           SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
00750           if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
00751         }
00752         this->SetDirty();
00753         break;
00754       }
00755 
00756       case WID_SA_UP:
00757       case WID_SA_DOWN:
00758       case WID_SA_LEFT:
00759       case WID_SA_RIGHT: {
00760         /*
00761          * Yes... this is a hack.
00762          *
00763          * No... I don't think it is useful to make this less of a hack.
00764          *
00765          * If you want to align sprites, you just need the number. Generally
00766          * the sprite caches are big enough to not remove the sprite from the
00767          * cache. If that's not the case, just let the NewGRF developer
00768          * increase the cache size instead of storing thousands of offsets
00769          * for the incredibly small chance that it's actually going to be
00770          * used by someone and the sprite cache isn't big enough for that
00771          * particular NewGRF developer.
00772          */
00773         Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
00774         switch (widget) {
00775           case WID_SA_UP:    spr->y_offs -= ZOOM_LVL_BASE; break;
00776           case WID_SA_DOWN:  spr->y_offs += ZOOM_LVL_BASE; break;
00777           case WID_SA_LEFT:  spr->x_offs -= ZOOM_LVL_BASE; break;
00778           case WID_SA_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
00779         }
00780         /* Of course, we need to redraw the sprite, but where is it used?
00781          * Everywhere is a safe bet. */
00782         MarkWholeScreenDirty();
00783         break;
00784       }
00785     }
00786   }
00787 
00788   virtual void OnQueryTextFinished(char *str)
00789   {
00790     if (StrEmpty(str)) return;
00791 
00792     this->current_sprite = atoi(str);
00793     if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
00794     while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
00795       this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00796     }
00797     this->SetDirty();
00798   }
00799 
00805   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00806   {
00807     if (!gui_scope) return;
00808     if (data == 1) {
00809       /* Sprite picker finished */
00810       this->RaiseWidget(WID_SA_PICKER);
00811       this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
00812     }
00813   }
00814 
00815   virtual void OnResize()
00816   {
00817     this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
00818     this->GetWidget<NWidgetCore>(WID_SA_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
00819   }
00820 };
00821 
00822 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
00823   NWidget(NWID_HORIZONTAL),
00824     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00825     NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00826     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00827     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00828   EndContainer(),
00829   NWidget(WWT_PANEL, COLOUR_GREY),
00830     NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
00831       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
00832         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
00833           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
00834           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
00835           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
00836         EndContainer(),
00837         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00838           NWidget(NWID_SPACER), SetFill(1, 1),
00839           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00840           NWidget(NWID_SPACER), SetFill(1, 1),
00841         EndContainer(),
00842         NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
00843           NWidget(NWID_VERTICAL),
00844             NWidget(NWID_SPACER), SetFill(1, 1),
00845             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00846             NWidget(NWID_SPACER), SetFill(1, 1),
00847           EndContainer(),
00848           NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
00849           EndContainer(),
00850           NWidget(NWID_VERTICAL),
00851             NWidget(NWID_SPACER), SetFill(1, 1),
00852             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00853             NWidget(NWID_SPACER), SetFill(1, 1),
00854           EndContainer(),
00855         EndContainer(),
00856         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00857           NWidget(NWID_SPACER), SetFill(1, 1),
00858           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
00859           NWidget(NWID_SPACER), SetFill(1, 1),
00860         EndContainer(),
00861         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
00862           NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
00863         EndContainer(),
00864       EndContainer(),
00865       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
00866         NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
00867         NWidget(NWID_HORIZONTAL),
00868           NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetDataTip(0x101, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
00869           NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
00870         EndContainer(),
00871       EndContainer(),
00872     EndContainer(),
00873   EndContainer(),
00874 };
00875 
00876 static WindowDesc _sprite_aligner_desc(
00877   WDP_AUTO, "sprite_aligner", 400, 300,
00878   WC_SPRITE_ALIGNER, WC_NONE,
00879   0,
00880   _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
00881 );
00882 
00886 void ShowSpriteAlignerWindow()
00887 {
00888   AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
00889 }