newgrf_debug_gui.cpp

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