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