newgrf_debug_gui.cpp

Go to the documentation of this file.
00001 /* $Id: newgrf_debug_gui.cpp 25946 2013-11-07 18:17:21Z frosch $ */
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 #include "vehicle_gui.h"
00022 
00023 #include "engine_base.h"
00024 #include "industry.h"
00025 #include "object_base.h"
00026 #include "station_base.h"
00027 #include "town.h"
00028 #include "vehicle_base.h"
00029 #include "train.h"
00030 #include "roadveh.h"
00031 
00032 #include "newgrf_airporttiles.h"
00033 #include "newgrf_debug.h"
00034 #include "newgrf_object.h"
00035 #include "newgrf_spritegroup.h"
00036 #include "newgrf_station.h"
00037 #include "newgrf_town.h"
00038 #include "newgrf_railtype.h"
00039 #include "newgrf_industries.h"
00040 #include "newgrf_industrytiles.h"
00041 
00042 #include "widgets/newgrf_debug_widget.h"
00043 
00044 #include "table/strings.h"
00045 
00047 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, NULL, 0, SmallVector<SpriteID, 256>() };
00048 
00054 static inline uint GetFeatureIndex(uint window_number)
00055 {
00056   return GB(window_number, 0, 24);
00057 }
00058 
00066 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
00067 {
00068   assert((index >> 24) == 0);
00069   return (feature << 24) | index;
00070 }
00071 
00076 enum NIType {
00077   NIT_INT,   
00078   NIT_CARGO, 
00079 };
00080 
00082 struct NIProperty {
00083   const char *name;       
00084   ptrdiff_t offset;       
00085   byte read_size;         
00086   byte prop;              
00087   byte type;
00088 };
00089 
00090 
00095 struct NICallback {
00096   const char *name; 
00097   ptrdiff_t offset; 
00098   byte read_size;   
00099   byte cb_bit;      
00100   uint16 cb_id;     
00101 };
00103 static const int CBM_NO_BIT = UINT8_MAX;
00104 
00106 struct NIVariable {
00107   const char *name;
00108   byte var;
00109 };
00110 
00112 class NIHelper {
00113 public:
00115   virtual ~NIHelper() {}
00116 
00122   virtual bool IsInspectable(uint index) const = 0;
00123 
00129   virtual uint GetParent(uint index) const = 0;
00130 
00136   virtual const void *GetInstance(uint index) const = 0;
00137 
00143   virtual const void *GetSpec(uint index) const = 0;
00144 
00149   virtual void SetStringParameters(uint index) const = 0;
00150 
00156   virtual uint32 GetGRFID(uint index) const = 0;
00157 
00166   virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
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:
00205   void SetSimpleStringParameters(StringID string, uint32 index) const
00206   {
00207     SetDParam(0, string);
00208     SetDParam(1, index);
00209   }
00210 
00211 
00218   void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
00219   {
00220     SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
00221     SetDParam(1, string);
00222     SetDParam(2, index);
00223     SetDParam(3, tile);
00224   }
00225 };
00226 
00227 
00229 struct NIFeature {
00230   const NIProperty *properties; 
00231   const NICallback *callbacks;  
00232   const NIVariable *variables;  
00233   const NIHelper   *helper;     
00234 };
00235 
00236 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
00237 #include "table/newgrf_debug_data.h"
00238 
00244 static inline GrfSpecFeature GetFeatureNum(uint window_number)
00245 {
00246   return (GrfSpecFeature)GB(window_number, 24, 8);
00247 }
00248 
00254 static inline const NIFeature *GetFeature(uint window_number)
00255 {
00256   GrfSpecFeature idx = GetFeatureNum(window_number);
00257   return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
00258 }
00259 
00266 static inline const NIHelper *GetFeatureHelper(uint window_number)
00267 {
00268   return GetFeature(window_number)->helper;
00269 }
00270 
00272 struct NewGRFInspectWindow : Window {
00273   static const int LEFT_OFFSET   = 5; 
00274   static const int RIGHT_OFFSET  = 5; 
00275   static const int TOP_OFFSET    = 5; 
00276   static const int BOTTOM_OFFSET = 5; 
00277 
00279   static uint32 var60params[GSF_FAKE_END][0x20];
00280 
00282   uint32 caller_grfid;
00283 
00285   uint chain_index;
00286 
00288   byte current_edit_param;
00289 
00290   Scrollbar *vscroll;
00291 
00297   static bool HasVariableParameter(uint variable)
00298   {
00299     return IsInsideBS(variable, 0x60, 0x20);
00300   }
00301 
00306   void SetCallerGRFID(uint32 grfid)
00307   {
00308     this->caller_grfid = grfid;
00309     this->SetDirty();
00310   }
00311 
00315   bool HasChainIndex() const
00316   {
00317     GrfSpecFeature f = GetFeatureNum(this->window_number);
00318     return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
00319   }
00320 
00325   uint GetFeatureIndex() const
00326   {
00327     uint index = ::GetFeatureIndex(this->window_number);
00328     if (this->chain_index > 0) {
00329       assert(this->HasChainIndex());
00330       const Vehicle *v = Vehicle::Get(index);
00331       v = v->Move(this->chain_index);
00332       if (v != NULL) index = v->index;
00333     }
00334     return index;
00335   }
00336 
00340   void ValidateChainIndex()
00341   {
00342     if (this->chain_index == 0) return;
00343 
00344     assert(this->HasChainIndex());
00345 
00346     const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
00347     v = v->Move(this->chain_index);
00348     if (v == NULL) this->chain_index = 0;
00349   }
00350 
00351   NewGRFInspectWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
00352   {
00353     this->CreateNestedTree();
00354     this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
00355     this->FinishInitNested(wno);
00356 
00357     this->vscroll->SetCount(0);
00358     this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
00359 
00360     this->OnInvalidateData(0, true);
00361   }
00362 
00363   virtual void SetStringParameters(int widget) const
00364   {
00365     if (widget != WID_NGRFI_CAPTION) return;
00366 
00367     GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
00368   }
00369 
00370   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00371   {
00372     switch (widget) {
00373       case WID_NGRFI_VEH_CHAIN: {
00374         assert(this->HasChainIndex());
00375         GrfSpecFeature f = GetFeatureNum(this->window_number);
00376         size->height = max(size->height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WD_BEVEL_TOP + WD_BEVEL_BOTTOM);
00377         break;
00378       }
00379 
00380       case WID_NGRFI_MAINPANEL:
00381         resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
00382         resize->width  = 1;
00383 
00384         size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
00385         break;
00386     }
00387   }
00388 
00395   void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
00396   {
00397     char buf[1024];
00398 
00399     va_list va;
00400     va_start(va, format);
00401     vsnprintf(buf, lengthof(buf), format, va);
00402     va_end(va);
00403 
00404     offset -= this->vscroll->GetPosition();
00405     if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
00406 
00407 		::DrawString(r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
00408   }
00409 
00410   virtual void DrawWidget(const Rect &r, int widget) const
00411   {
00412     switch (widget) {
00413       case WID_NGRFI_VEH_CHAIN: {
00414         const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
00415         int total_width = 0;
00416         int sel_start = 0;
00417         int sel_end = 0;
00418         for (const Vehicle *u = v->First(); u != NULL; u = u->Next()) {
00419           if (u == v) sel_start = total_width;
00420           switch (u->type) {
00421             case VEH_TRAIN: total_width += Train      ::From(u)->GetDisplayImageWidth(); break;
00422             case VEH_ROAD:  total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
00423             default: NOT_REACHED();
00424           }
00425           if (u == v) sel_end = total_width;
00426         }
00427 
00428         int width = r.right + 1 - r.left - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
00429         int skip = 0;
00430         if (total_width > width) {
00431           int sel_center = (sel_start + sel_end) / 2;
00432           if (sel_center > width / 2) skip = min(total_width - width, sel_center - width / 2);
00433         }
00434 
00435         GrfSpecFeature f = GetFeatureNum(this->window_number);
00436         int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
00437         int y = (r.top + r.bottom - h) / 2;
00438         DrawVehicleImage(v->First(), r.left + WD_BEVEL_LEFT, r.right - WD_BEVEL_RIGHT, y + 1, INVALID_VEHICLE, EIT_IN_DETAILS, skip);
00439 
00440         /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
00441         if (_current_text_dir == TD_RTL) {
00442           DrawFrameRect(r.right - sel_end   + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
00443         } else {
00444           DrawFrameRect(r.left  + sel_start - skip, y, r.left  + sel_end   - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
00445         }
00446         break;
00447       }
00448     }
00449 
00450     if (widget != WID_NGRFI_MAINPANEL) return;
00451 
00452     uint index = this->GetFeatureIndex();
00453     const NIFeature *nif  = GetFeature(this->window_number);
00454     const NIHelper *nih   = nif->helper;
00455     const void *base      = nih->GetInstance(index);
00456     const void *base_spec = nih->GetSpec(index);
00457 
00458     uint i = 0;
00459     if (nif->variables != NULL) {
00460       this->DrawString(r, i++, "Variables:");
00461       for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
00462         bool avail = true;
00463         uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
00464         uint value = nih->Resolve(index, niv->var, param, &avail);
00465 
00466         if (!avail) continue;
00467 
00468         if (HasVariableParameter(niv->var)) {
00469           this->DrawString(r, i++, "  %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
00470         } else {
00471           this->DrawString(r, i++, "  %02x: %08x (%s)", niv->var, value, niv->name);
00472         }
00473       }
00474     }
00475 
00476     uint psa_size = nih->GetPSASize(index, this->caller_grfid);
00477     const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
00478     if (psa_size != 0 && psa != NULL) {
00479       if (nih->PSAWithParameter()) {
00480         this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
00481       } else {
00482         this->DrawString(r, i++, "Persistent storage:");
00483       }
00484       assert(psa_size % 4 == 0);
00485       for (uint j = 0; j < psa_size; j += 4, psa += 4) {
00486         this->DrawString(r, i++, "  %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
00487       }
00488     }
00489 
00490     if (nif->properties != NULL) {
00491       this->DrawString(r, i++, "Properties:");
00492       for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
00493         const void *ptr = (const byte *)base + nip->offset;
00494         uint value;
00495         switch (nip->read_size) {
00496           case 1: value = *(const uint8  *)ptr; break;
00497           case 2: value = *(const uint16 *)ptr; break;
00498           case 4: value = *(const uint32 *)ptr; break;
00499           default: NOT_REACHED();
00500         }
00501 
00502         StringID string;
00503         SetDParam(0, value);
00504         switch (nip->type) {
00505           case NIT_INT:
00506             string = STR_JUST_INT;
00507             break;
00508 
00509           case NIT_CARGO:
00510             string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
00511             break;
00512 
00513           default:
00514             NOT_REACHED();
00515         }
00516 
00517         char buffer[64];
00518         GetString(buffer, string, lastof(buffer));
00519         this->DrawString(r, i++, "  %02x: %s (%s)", nip->prop, buffer, nip->name);
00520       }
00521     }
00522 
00523     if (nif->callbacks != NULL) {
00524       this->DrawString(r, i++, "Callbacks:");
00525       for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
00526         if (nic->cb_bit != CBM_NO_BIT) {
00527           const void *ptr = (const byte *)base_spec + nic->offset;
00528           uint value;
00529           switch (nic->read_size) {
00530             case 1: value = *(const uint8  *)ptr; break;
00531             case 2: value = *(const uint16 *)ptr; break;
00532             case 4: value = *(const uint32 *)ptr; break;
00533             default: NOT_REACHED();
00534           }
00535 
00536           if (!HasBit(value, nic->cb_bit)) continue;
00537           this->DrawString(r, i++, "  %03x: %s", nic->cb_id, nic->name);
00538         } else {
00539           this->DrawString(r, i++, "  %03x: %s (unmasked)", nic->cb_id, nic->name);
00540         }
00541       }
00542     }
00543 
00544     /* Not nice and certainly a hack, but it beats duplicating
00545      * this whole function just to count the actual number of
00546      * elements. Especially because they need to be redrawn. */
00547     const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
00548   }
00549 
00550   virtual void OnClick(Point pt, int widget, int click_count)
00551   {
00552     switch (widget) {
00553       case WID_NGRFI_PARENT: {
00554         const NIHelper *nih   = GetFeatureHelper(this->window_number);
00555         uint index = nih->GetParent(this->GetFeatureIndex());
00556 				::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
00557         break;
00558       }
00559 
00560       case WID_NGRFI_VEH_PREV:
00561         if (this->chain_index > 0) {
00562           this->chain_index--;
00563           this->InvalidateData();
00564         }
00565         break;
00566 
00567       case WID_NGRFI_VEH_NEXT:
00568         if (this->HasChainIndex()) {
00569           uint index = this->GetFeatureIndex();
00570           Vehicle *v = Vehicle::Get(index);
00571           if (v != NULL && v->Next() != NULL) {
00572             this->chain_index++;
00573             this->InvalidateData();
00574           }
00575         }
00576         break;
00577 
00578       case WID_NGRFI_MAINPANEL: {
00579         /* Does this feature have variables? */
00580         const NIFeature *nif  = GetFeature(this->window_number);
00581         if (nif->variables == NULL) return;
00582 
00583         /* Get the line, make sure it's within the boundaries. */
00584         int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
00585         if (line == INT_MAX) return;
00586 
00587         /* Find the variable related to the line */
00588         for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
00589           if (line != 1) continue; // 1 because of the "Variables:" line
00590 
00591           if (!HasVariableParameter(niv->var)) break;
00592 
00593           this->current_edit_param = niv->var;
00594           ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
00595         }
00596       }
00597     }
00598   }
00599 
00600   virtual void OnQueryTextFinished(char *str)
00601   {
00602     if (StrEmpty(str)) return;
00603 
00604     NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
00605     this->SetDirty();
00606   }
00607 
00608   virtual void OnResize()
00609   {
00610     this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, TOP_OFFSET + BOTTOM_OFFSET);
00611   }
00612 
00618   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00619   {
00620     if (!gui_scope) return;
00621     if (this->HasChainIndex()) {
00622       this->ValidateChainIndex();
00623       this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV, this->chain_index == 0);
00624       Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
00625       this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == NULL || v->Next() == NULL);
00626     }
00627   }
00628 };
00629 
00630 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
00631 
00632 static const NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
00633   NWidget(NWID_HORIZONTAL),
00634     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00635     NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00636     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00637     NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
00638     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00639   EndContainer(),
00640   NWidget(WWT_PANEL, COLOUR_GREY),
00641     NWidget(NWID_HORIZONTAL),
00642       NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_PREV), SetDataTip(AWV_DECREASE, STR_NULL),
00643       NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_NEXT), SetDataTip(AWV_INCREASE, STR_NULL),
00644       NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
00645     EndContainer(),
00646   EndContainer(),
00647   NWidget(NWID_HORIZONTAL),
00648     NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
00649     NWidget(NWID_VERTICAL),
00650       NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
00651       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00652     EndContainer(),
00653   EndContainer(),
00654 };
00655 
00656 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
00657   NWidget(NWID_HORIZONTAL),
00658     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00659     NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
00660     NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
00661     NWidget(WWT_SHADEBOX, COLOUR_GREY),
00662     NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
00663     NWidget(WWT_STICKYBOX, COLOUR_GREY),
00664   EndContainer(),
00665   NWidget(NWID_HORIZONTAL),
00666     NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
00667     NWidget(NWID_VERTICAL),
00668       NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
00669       NWidget(WWT_RESIZEBOX, COLOUR_GREY),
00670     EndContainer(),
00671   EndContainer(),
00672 };
00673 
00674 static WindowDesc _newgrf_inspect_chain_desc(
00675   WDP_AUTO, "newgrf_inspect_chain", 400, 300,
00676   WC_NEWGRF_INSPECT, WC_NONE,
00677   0,
00678   _nested_newgrf_inspect_chain_widgets, lengthof(_nested_newgrf_inspect_chain_widgets)
00679 );
00680 
00681 static WindowDesc _newgrf_inspect_desc(
00682   WDP_AUTO, "newgrf_inspect", 400, 300,
00683   WC_NEWGRF_INSPECT, WC_NONE,
00684   0,
00685   _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
00686 );
00687 
00697 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
00698 {
00699   if (!IsNewGRFInspectable(feature, index)) return;
00700 
00701   WindowNumber wno = GetInspectWindowNumber(feature, index);
00702   NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(feature == GSF_TRAINS || feature == GSF_ROADVEHICLES ? &_newgrf_inspect_chain_desc : &_newgrf_inspect_desc, wno);
00703   if (w == NULL) w = (NewGRFInspectWindow *)FindWindowById(WC_NEWGRF_INSPECT, wno);
00704   w->SetCallerGRFID(grfid);
00705 }
00706 
00715 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
00716 {
00717   if (feature == GSF_INVALID) return;
00718 
00719   WindowNumber wno = GetInspectWindowNumber(feature, index);
00720   InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
00721 }
00722 
00731 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
00732 {
00733   if (feature == GSF_INVALID) return;
00734 
00735   WindowNumber wno = GetInspectWindowNumber(feature, index);
00736   DeleteWindowById(WC_NEWGRF_INSPECT, wno);
00737 
00738   /* Reinitialise the land information window to remove the "debug" sprite if needed.
00739    * Note: Since we might be called from a command here, it is important to not execute
00740    * the invalidation immediately. The landinfo window tests commands itself. */
00741   InvalidateWindowData(WC_LAND_INFO, 0, 1);
00742 }
00743 
00753 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
00754 {
00755   const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
00756   if (nif == NULL) return false;
00757   return nif->helper->IsInspectable(index);
00758 }
00759 
00765 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
00766 {
00767   switch (GetTileType(tile)) {
00768     default:              return GSF_INVALID;
00769     case MP_RAILWAY:      return GSF_RAILTYPES;
00770     case MP_ROAD:         return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
00771     case MP_HOUSE:        return GSF_HOUSES;
00772     case MP_INDUSTRY:     return GSF_INDUSTRYTILES;
00773     case MP_OBJECT:       return GSF_OBJECTS;
00774 
00775     case MP_STATION:
00776       switch (GetStationType(tile)) {
00777         case STATION_RAIL:    return GSF_STATIONS;
00778         case STATION_AIRPORT: return GSF_AIRPORTTILES;
00779         default:              return GSF_INVALID;
00780       }
00781   }
00782 }
00783 
00789 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
00790 {
00791   switch (type) {
00792     case VEH_TRAIN:    return GSF_TRAINS;
00793     case VEH_ROAD:     return GSF_ROADVEHICLES;
00794     case VEH_SHIP:     return GSF_SHIPS;
00795     case VEH_AIRCRAFT: return GSF_AIRCRAFT;
00796     default:           return GSF_INVALID;
00797   }
00798 }
00799 
00800 
00801 
00802 /**** Sprite Aligner ****/
00803 
00805 struct SpriteAlignerWindow : Window {
00806   SpriteID current_sprite; 
00807   Scrollbar *vscroll;
00808 
00809   SpriteAlignerWindow(WindowDesc *desc, WindowNumber wno) : Window(desc)
00810   {
00811     this->CreateNestedTree();
00812     this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
00813     this->FinishInitNested(wno);
00814 
00815     /* Oh yes, we assume there is at least one normal sprite! */
00816     while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
00817   }
00818 
00819   virtual void SetStringParameters(int widget) const
00820   {
00821     switch (widget) {
00822       case WID_SA_CAPTION:
00823         SetDParam(0, this->current_sprite);
00824         SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite)));
00825         break;
00826 
00827       case WID_SA_OFFSETS: {
00828         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00829         SetDParam(0, spr->x_offs / ZOOM_LVL_BASE);
00830         SetDParam(1, spr->y_offs / ZOOM_LVL_BASE);
00831         break;
00832       }
00833 
00834       default:
00835         break;
00836     }
00837   }
00838 
00839   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
00840   {
00841     if (widget != WID_SA_LIST) return;
00842 
00843     resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
00844     resize->width  = 1;
00845 
00846     /* Resize to about 200 pixels (for the preview) */
00847     size->height = (1 + 200 / resize->height) * resize->height;
00848   }
00849 
00850   virtual void DrawWidget(const Rect &r, int widget) const
00851   {
00852     switch (widget) {
00853       case WID_SA_SPRITE: {
00854         /* Center the sprite ourselves */
00855         const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
00856         int width  = r.right  - r.left + 1;
00857         int height = r.bottom - r.top  + 1;
00858         int x = r.left - spr->x_offs / ZOOM_LVL_BASE + (width  - spr->width / ZOOM_LVL_BASE) / 2;
00859         int y = r.top  - spr->y_offs / ZOOM_LVL_BASE + (height - spr->height / ZOOM_LVL_BASE) / 2;
00860 
00861         /* And draw only the part within the sprite area */
00862         SubSprite subspr = {
00863           spr->x_offs + (spr->width  - width  * ZOOM_LVL_BASE) / 2 + 1,
00864           spr->y_offs + (spr->height - height * ZOOM_LVL_BASE) / 2 + 1,
00865           spr->x_offs + (spr->width  + width  * ZOOM_LVL_BASE) / 2 - 1,
00866           spr->y_offs + (spr->height + height * ZOOM_LVL_BASE) / 2 - 1,
00867         };
00868 
00869         DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
00870         break;
00871       }
00872 
00873       case WID_SA_LIST: {
00874         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00875         int step_size = nwid->resize_y;
00876 
00877         SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
00878         int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
00879 
00880         int y = r.top + WD_FRAMERECT_TOP;
00881         for (int i = this->vscroll->GetPosition(); i < max; i++) {
00882           SetDParam(0, list[i]);
00883           DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
00884           y += step_size;
00885         }
00886         break;
00887       }
00888     }
00889   }
00890 
00891   virtual void OnClick(Point pt, int widget, int click_count)
00892   {
00893     switch (widget) {
00894       case WID_SA_PREVIOUS:
00895         do {
00896           this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() :  this->current_sprite) - 1;
00897         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00898         this->SetDirty();
00899         break;
00900 
00901       case WID_SA_GOTO:
00902         ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
00903         break;
00904 
00905       case WID_SA_NEXT:
00906         do {
00907           this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00908         } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
00909         this->SetDirty();
00910         break;
00911 
00912       case WID_SA_PICKER:
00913         this->LowerWidget(WID_SA_PICKER);
00914         _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
00915         this->SetDirty();
00916         break;
00917 
00918       case WID_SA_LIST: {
00919         const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
00920         int step_size = nwid->resize_y;
00921 
00922         uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
00923         if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
00924           SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
00925           if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
00926         }
00927         this->SetDirty();
00928         break;
00929       }
00930 
00931       case WID_SA_UP:
00932       case WID_SA_DOWN:
00933       case WID_SA_LEFT:
00934       case WID_SA_RIGHT: {
00935         /*
00936          * Yes... this is a hack.
00937          *
00938          * No... I don't think it is useful to make this less of a hack.
00939          *
00940          * If you want to align sprites, you just need the number. Generally
00941          * the sprite caches are big enough to not remove the sprite from the
00942          * cache. If that's not the case, just let the NewGRF developer
00943          * increase the cache size instead of storing thousands of offsets
00944          * for the incredibly small chance that it's actually going to be
00945          * used by someone and the sprite cache isn't big enough for that
00946          * particular NewGRF developer.
00947          */
00948         Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
00949         switch (widget) {
00950           case WID_SA_UP:    spr->y_offs -= ZOOM_LVL_BASE; break;
00951           case WID_SA_DOWN:  spr->y_offs += ZOOM_LVL_BASE; break;
00952           case WID_SA_LEFT:  spr->x_offs -= ZOOM_LVL_BASE; break;
00953           case WID_SA_RIGHT: spr->x_offs += ZOOM_LVL_BASE; break;
00954         }
00955         /* Of course, we need to redraw the sprite, but where is it used?
00956          * Everywhere is a safe bet. */
00957         MarkWholeScreenDirty();
00958         break;
00959       }
00960     }
00961   }
00962 
00963   virtual void OnQueryTextFinished(char *str)
00964   {
00965     if (StrEmpty(str)) return;
00966 
00967     this->current_sprite = atoi(str);
00968     if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
00969     while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
00970       this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
00971     }
00972     this->SetDirty();
00973   }
00974 
00980   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
00981   {
00982     if (!gui_scope) return;
00983     if (data == 1) {
00984       /* Sprite picker finished */
00985       this->RaiseWidget(WID_SA_PICKER);
00986       this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
00987     }
00988   }
00989 
00990   virtual void OnResize()
00991   {
00992     this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
00993   }
00994 };
00995 
00996 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
00997   NWidget(NWID_HORIZONTAL),
00998     NWidget(WWT_CLOSEBOX, COLOUR_GREY),
00999     NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
01000     NWidget(WWT_SHADEBOX, COLOUR_GREY),
01001     NWidget(WWT_STICKYBOX, COLOUR_GREY),
01002   EndContainer(),
01003   NWidget(WWT_PANEL, COLOUR_GREY),
01004     NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
01005       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
01006         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 5, 10),
01007           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
01008           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
01009           NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
01010         EndContainer(),
01011         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
01012           NWidget(NWID_SPACER), SetFill(1, 1),
01013           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
01014           NWidget(NWID_SPACER), SetFill(1, 1),
01015         EndContainer(),
01016         NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
01017           NWidget(NWID_VERTICAL),
01018             NWidget(NWID_SPACER), SetFill(1, 1),
01019             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
01020             NWidget(NWID_SPACER), SetFill(1, 1),
01021           EndContainer(),
01022           NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
01023           EndContainer(),
01024           NWidget(NWID_VERTICAL),
01025             NWidget(NWID_SPACER), SetFill(1, 1),
01026             NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
01027             NWidget(NWID_SPACER), SetFill(1, 1),
01028           EndContainer(),
01029         EndContainer(),
01030         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
01031           NWidget(NWID_SPACER), SetFill(1, 1),
01032           NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
01033           NWidget(NWID_SPACER), SetFill(1, 1),
01034         EndContainer(),
01035         NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
01036           NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
01037         EndContainer(),
01038       EndContainer(),
01039       NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
01040         NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
01041         NWidget(NWID_HORIZONTAL),
01042           NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
01043           NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
01044         EndContainer(),
01045       EndContainer(),
01046     EndContainer(),
01047   EndContainer(),
01048 };
01049 
01050 static WindowDesc _sprite_aligner_desc(
01051   WDP_AUTO, "sprite_aligner", 400, 300,
01052   WC_SPRITE_ALIGNER, WC_NONE,
01053   0,
01054   _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
01055 );
01056 
01060 void ShowSpriteAlignerWindow()
01061 {
01062   AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
01063 }