OpenTTD
newgrf_debug_gui.cpp
Go to the documentation of this file.
1 /* $Id: newgrf_debug_gui.cpp 27086 2014-12-18 18:22:23Z alberth $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "stdafx.h"
13 #include <stdarg.h>
14 #include "window_gui.h"
15 #include "window_func.h"
16 #include "fileio_func.h"
17 #include "spritecache.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "textbuf_gui.h"
21 #include "vehicle_gui.h"
22 #include "zoom_func.h"
23 
24 #include "engine_base.h"
25 #include "industry.h"
26 #include "object_base.h"
27 #include "station_base.h"
28 #include "town.h"
29 #include "vehicle_base.h"
30 #include "train.h"
31 #include "roadveh.h"
32 
33 #include "newgrf_airporttiles.h"
34 #include "newgrf_debug.h"
35 #include "newgrf_object.h"
36 #include "newgrf_spritegroup.h"
37 #include "newgrf_station.h"
38 #include "newgrf_town.h"
39 #include "newgrf_railtype.h"
40 #include "newgrf_industries.h"
41 #include "newgrf_industrytiles.h"
42 
44 
45 #include "table/strings.h"
46 
47 #include "safeguards.h"
48 
51 
57 static inline uint GetFeatureIndex(uint window_number)
58 {
59  return GB(window_number, 0, 24);
60 }
61 
69 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
70 {
71  assert((index >> 24) == 0);
72  return (feature << 24) | index;
73 }
74 
79 enum NIType {
82 };
83 
85 struct NIProperty {
86  const char *name;
87  ptrdiff_t offset;
88  byte read_size;
89  byte prop;
90  byte type;
91 };
92 
93 
98 struct NICallback {
99  const char *name;
100  ptrdiff_t offset;
101  byte read_size;
102  byte cb_bit;
103  uint16 cb_id;
104 };
106 static const int CBM_NO_BIT = UINT8_MAX;
107 
109 struct NIVariable {
110  const char *name;
111  byte var;
112 };
113 
115 class NIHelper {
116 public:
118  virtual ~NIHelper() {}
119 
125  virtual bool IsInspectable(uint index) const = 0;
126 
132  virtual uint GetParent(uint index) const = 0;
133 
139  virtual const void *GetInstance(uint index) const = 0;
140 
146  virtual const void *GetSpec(uint index) const = 0;
147 
152  virtual void SetStringParameters(uint index) const = 0;
153 
159  virtual uint32 GetGRFID(uint index) const = 0;
160 
169  virtual uint Resolve(uint index, uint var, uint param, bool *avail) const = 0;
170 
175  virtual bool PSAWithParameter() const
176  {
177  return false;
178  }
179 
186  virtual uint GetPSASize(uint index, uint32 grfid) const
187  {
188  return 0;
189  }
190 
197  virtual const int32 *GetPSAFirstPosition(uint index, uint32 grfid) const
198  {
199  return NULL;
200  }
201 
202 protected:
208  void SetSimpleStringParameters(StringID string, uint32 index) const
209  {
210  SetDParam(0, string);
211  SetDParam(1, index);
212  }
213 
214 
221  void SetObjectAtStringParameters(StringID string, uint32 index, TileIndex tile) const
222  {
223  SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
224  SetDParam(1, string);
225  SetDParam(2, index);
226  SetDParam(3, tile);
227  }
228 };
229 
230 
232 struct NIFeature {
236  const NIHelper *helper;
237 };
238 
239 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
240 #include "table/newgrf_debug_data.h"
241 
247 static inline GrfSpecFeature GetFeatureNum(uint window_number)
248 {
249  return (GrfSpecFeature)GB(window_number, 24, 8);
250 }
251 
257 static inline const NIFeature *GetFeature(uint window_number)
258 {
259  GrfSpecFeature idx = GetFeatureNum(window_number);
260  return idx < GSF_FAKE_END ? _nifeatures[idx] : NULL;
261 }
262 
269 static inline const NIHelper *GetFeatureHelper(uint window_number)
270 {
271  return GetFeature(window_number)->helper;
272 }
273 
276  static const int LEFT_OFFSET = 5;
277  static const int RIGHT_OFFSET = 5;
278  static const int TOP_OFFSET = 5;
279  static const int BOTTOM_OFFSET = 5;
280 
282  static uint32 var60params[GSF_FAKE_END][0x20];
283 
285  uint32 caller_grfid;
286 
289 
292 
293  Scrollbar *vscroll;
294 
300  static bool HasVariableParameter(uint variable)
301  {
302  return IsInsideBS(variable, 0x60, 0x20);
303  }
304 
309  void SetCallerGRFID(uint32 grfid)
310  {
311  this->caller_grfid = grfid;
312  this->SetDirty();
313  }
314 
318  bool HasChainIndex() const
319  {
321  return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
322  }
323 
328  uint GetFeatureIndex() const
329  {
330  uint index = ::GetFeatureIndex(this->window_number);
331  if (this->chain_index > 0) {
332  assert(this->HasChainIndex());
333  const Vehicle *v = Vehicle::Get(index);
334  v = v->Move(this->chain_index);
335  if (v != NULL) index = v->index;
336  }
337  return index;
338  }
339 
344  {
345  if (this->chain_index == 0) return;
346 
347  assert(this->HasChainIndex());
348 
349  const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
350  v = v->Move(this->chain_index);
351  if (v == NULL) this->chain_index = 0;
352  }
353 
355  {
356  this->CreateNestedTree();
357  this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
358  this->FinishInitNested(wno);
359 
360  this->vscroll->SetCount(0);
361  this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
362 
363  this->OnInvalidateData(0, true);
364  }
365 
366  virtual void SetStringParameters(int widget) const
367  {
368  if (widget != WID_NGRFI_CAPTION) return;
369 
371  }
372 
373  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
374  {
375  switch (widget) {
376  case WID_NGRFI_VEH_CHAIN: {
377  assert(this->HasChainIndex());
379  size->height = max(size->height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WD_BEVEL_TOP + WD_BEVEL_BOTTOM);
380  break;
381  }
382 
383  case WID_NGRFI_MAINPANEL:
384  resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
385  resize->width = 1;
386 
387  size->height = 5 * resize->height + TOP_OFFSET + BOTTOM_OFFSET;
388  break;
389  }
390  }
391 
398  void WARN_FORMAT(4, 5) DrawString(const Rect &r, int offset, const char *format, ...) const
399  {
400  char buf[1024];
401 
402  va_list va;
403  va_start(va, format);
404  vseprintf(buf, lastof(buf), format, va);
405  va_end(va);
406 
407  offset -= this->vscroll->GetPosition();
408  if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
409 
410  ::DrawString(r.left + LEFT_OFFSET, r.right - RIGHT_OFFSET, r.top + TOP_OFFSET + (offset * this->resize.step_height), buf, TC_BLACK);
411  }
412 
413  virtual void DrawWidget(const Rect &r, int widget) const
414  {
415  switch (widget) {
416  case WID_NGRFI_VEH_CHAIN: {
417  const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
418  int total_width = 0;
419  int sel_start = 0;
420  int sel_end = 0;
421  for (const Vehicle *u = v->First(); u != NULL; u = u->Next()) {
422  if (u == v) sel_start = total_width;
423  switch (u->type) {
424  case VEH_TRAIN: total_width += Train ::From(u)->GetDisplayImageWidth(); break;
425  case VEH_ROAD: total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
426  default: NOT_REACHED();
427  }
428  if (u == v) sel_end = total_width;
429  }
430 
431  int width = r.right + 1 - r.left - WD_BEVEL_LEFT - WD_BEVEL_RIGHT;
432  int skip = 0;
433  if (total_width > width) {
434  int sel_center = (sel_start + sel_end) / 2;
435  if (sel_center > width / 2) skip = min(total_width - width, sel_center - width / 2);
436  }
437 
439  int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
440  int y = (r.top + r.bottom - h) / 2;
441  DrawVehicleImage(v->First(), r.left + WD_BEVEL_LEFT, r.right - WD_BEVEL_RIGHT, y + 1, INVALID_VEHICLE, EIT_IN_DETAILS, skip);
442 
443  /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
444  if (_current_text_dir == TD_RTL) {
445  DrawFrameRect(r.right - sel_end + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
446  } else {
447  DrawFrameRect(r.left + sel_start - skip, y, r.left + sel_end - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
448  }
449  break;
450  }
451  }
452 
453  if (widget != WID_NGRFI_MAINPANEL) return;
454 
455  uint index = this->GetFeatureIndex();
456  const NIFeature *nif = GetFeature(this->window_number);
457  const NIHelper *nih = nif->helper;
458  const void *base = nih->GetInstance(index);
459  const void *base_spec = nih->GetSpec(index);
460 
461  uint i = 0;
462  if (nif->variables != NULL) {
463  this->DrawString(r, i++, "Variables:");
464  for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++) {
465  bool avail = true;
466  uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
467  uint value = nih->Resolve(index, niv->var, param, &avail);
468 
469  if (!avail) continue;
470 
471  if (HasVariableParameter(niv->var)) {
472  this->DrawString(r, i++, " %02x[%02x]: %08x (%s)", niv->var, param, value, niv->name);
473  } else {
474  this->DrawString(r, i++, " %02x: %08x (%s)", niv->var, value, niv->name);
475  }
476  }
477  }
478 
479  uint psa_size = nih->GetPSASize(index, this->caller_grfid);
480  const int32 *psa = nih->GetPSAFirstPosition(index, this->caller_grfid);
481  if (psa_size != 0 && psa != NULL) {
482  if (nih->PSAWithParameter()) {
483  this->DrawString(r, i++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid));
484  } else {
485  this->DrawString(r, i++, "Persistent storage:");
486  }
487  assert(psa_size % 4 == 0);
488  for (uint j = 0; j < psa_size; j += 4, psa += 4) {
489  this->DrawString(r, i++, " %i: %i %i %i %i", j, psa[0], psa[1], psa[2], psa[3]);
490  }
491  }
492 
493  if (nif->properties != NULL) {
494  this->DrawString(r, i++, "Properties:");
495  for (const NIProperty *nip = nif->properties; nip->name != NULL; nip++) {
496  const void *ptr = (const byte *)base + nip->offset;
497  uint value;
498  switch (nip->read_size) {
499  case 1: value = *(const uint8 *)ptr; break;
500  case 2: value = *(const uint16 *)ptr; break;
501  case 4: value = *(const uint32 *)ptr; break;
502  default: NOT_REACHED();
503  }
504 
505  StringID string;
506  SetDParam(0, value);
507  switch (nip->type) {
508  case NIT_INT:
509  string = STR_JUST_INT;
510  break;
511 
512  case NIT_CARGO:
513  string = value != INVALID_CARGO ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
514  break;
515 
516  default:
517  NOT_REACHED();
518  }
519 
520  char buffer[64];
521  GetString(buffer, string, lastof(buffer));
522  this->DrawString(r, i++, " %02x: %s (%s)", nip->prop, buffer, nip->name);
523  }
524  }
525 
526  if (nif->callbacks != NULL) {
527  this->DrawString(r, i++, "Callbacks:");
528  for (const NICallback *nic = nif->callbacks; nic->name != NULL; nic++) {
529  if (nic->cb_bit != CBM_NO_BIT) {
530  const void *ptr = (const byte *)base_spec + nic->offset;
531  uint value;
532  switch (nic->read_size) {
533  case 1: value = *(const uint8 *)ptr; break;
534  case 2: value = *(const uint16 *)ptr; break;
535  case 4: value = *(const uint32 *)ptr; break;
536  default: NOT_REACHED();
537  }
538 
539  if (!HasBit(value, nic->cb_bit)) continue;
540  this->DrawString(r, i++, " %03x: %s", nic->cb_id, nic->name);
541  } else {
542  this->DrawString(r, i++, " %03x: %s (unmasked)", nic->cb_id, nic->name);
543  }
544  }
545  }
546 
547  /* Not nice and certainly a hack, but it beats duplicating
548  * this whole function just to count the actual number of
549  * elements. Especially because they need to be redrawn. */
550  const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
551  }
552 
553  virtual void OnClick(Point pt, int widget, int click_count)
554  {
555  switch (widget) {
556  case WID_NGRFI_PARENT: {
557  const NIHelper *nih = GetFeatureHelper(this->window_number);
558  uint index = nih->GetParent(this->GetFeatureIndex());
559  ::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
560  break;
561  }
562 
563  case WID_NGRFI_VEH_PREV:
564  if (this->chain_index > 0) {
565  this->chain_index--;
566  this->InvalidateData();
567  }
568  break;
569 
570  case WID_NGRFI_VEH_NEXT:
571  if (this->HasChainIndex()) {
572  uint index = this->GetFeatureIndex();
573  Vehicle *v = Vehicle::Get(index);
574  if (v != NULL && v->Next() != NULL) {
575  this->chain_index++;
576  this->InvalidateData();
577  }
578  }
579  break;
580 
581  case WID_NGRFI_MAINPANEL: {
582  /* Does this feature have variables? */
583  const NIFeature *nif = GetFeature(this->window_number);
584  if (nif->variables == NULL) return;
585 
586  /* Get the line, make sure it's within the boundaries. */
587  int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, TOP_OFFSET);
588  if (line == INT_MAX) return;
589 
590  /* Find the variable related to the line */
591  for (const NIVariable *niv = nif->variables; niv->name != NULL; niv++, line--) {
592  if (line != 1) continue; // 1 because of the "Variables:" line
593 
594  if (!HasVariableParameter(niv->var)) break;
595 
596  this->current_edit_param = niv->var;
597  ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
598  }
599  }
600  }
601  }
602 
603  virtual void OnQueryTextFinished(char *str)
604  {
605  if (StrEmpty(str)) return;
606 
607  NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = strtol(str, NULL, 16);
608  this->SetDirty();
609  }
610 
611  virtual void OnResize()
612  {
614  }
615 
621  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
622  {
623  if (!gui_scope) return;
624  if (this->HasChainIndex()) {
625  this->ValidateChainIndex();
627  Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
628  this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == NULL || v->Next() == NULL);
629  }
630  }
631 };
632 
633 /* static */ uint32 NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
634 
635 static const NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
637  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
638  NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
639  NWidget(WWT_SHADEBOX, COLOUR_GREY),
640  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
641  NWidget(WWT_STICKYBOX, COLOUR_GREY),
642  EndContainer(),
643  NWidget(WWT_PANEL, COLOUR_GREY),
647  NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
648  EndContainer(),
649  EndContainer(),
654  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
655  EndContainer(),
656  EndContainer(),
657 };
658 
659 static const NWidgetPart _nested_newgrf_inspect_widgets[] = {
661  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
662  NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
663  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
664  NWidget(WWT_SHADEBOX, COLOUR_GREY),
665  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
666  NWidget(WWT_STICKYBOX, COLOUR_GREY),
667  EndContainer(),
672  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
673  EndContainer(),
674  EndContainer(),
675 };
676 
677 static WindowDesc _newgrf_inspect_chain_desc(
678  WDP_AUTO, "newgrf_inspect_chain", 400, 300,
680  0,
681  _nested_newgrf_inspect_chain_widgets, lengthof(_nested_newgrf_inspect_chain_widgets)
682 );
683 
684 static WindowDesc _newgrf_inspect_desc(
685  WDP_AUTO, "newgrf_inspect", 400, 300,
687  0,
688  _nested_newgrf_inspect_widgets, lengthof(_nested_newgrf_inspect_widgets)
689 );
690 
700 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32 grfid)
701 {
702  if (!IsNewGRFInspectable(feature, index)) return;
703 
704  WindowNumber wno = GetInspectWindowNumber(feature, index);
705  WindowDesc *desc = (feature == GSF_TRAINS || feature == GSF_ROADVEHICLES) ? &_newgrf_inspect_chain_desc : &_newgrf_inspect_desc;
706  NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(desc, wno, true);
707  w->SetCallerGRFID(grfid);
708 }
709 
719 {
720  if (feature == GSF_INVALID) return;
721 
722  WindowNumber wno = GetInspectWindowNumber(feature, index);
724 }
725 
734 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
735 {
736  if (feature == GSF_INVALID) return;
737 
738  WindowNumber wno = GetInspectWindowNumber(feature, index);
740 
741  /* Reinitialise the land information window to remove the "debug" sprite if needed.
742  * Note: Since we might be called from a command here, it is important to not execute
743  * the invalidation immediately. The landinfo window tests commands itself. */
745 }
746 
756 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
757 {
758  const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
759  if (nif == NULL) return false;
760  return nif->helper->IsInspectable(index);
761 }
762 
769 {
770  switch (GetTileType(tile)) {
771  default: return GSF_INVALID;
772  case MP_RAILWAY: return GSF_RAILTYPES;
773  case MP_ROAD: return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_INVALID;
774  case MP_HOUSE: return GSF_HOUSES;
775  case MP_INDUSTRY: return GSF_INDUSTRYTILES;
776  case MP_OBJECT: return GSF_OBJECTS;
777 
778  case MP_STATION:
779  switch (GetStationType(tile)) {
780  case STATION_RAIL: return GSF_STATIONS;
781  case STATION_AIRPORT: return GSF_AIRPORTTILES;
782  default: return GSF_INVALID;
783  }
784  }
785 }
786 
793 {
794  switch (type) {
795  case VEH_TRAIN: return GSF_TRAINS;
796  case VEH_ROAD: return GSF_ROADVEHICLES;
797  case VEH_SHIP: return GSF_SHIPS;
798  case VEH_AIRCRAFT: return GSF_AIRCRAFT;
799  default: return GSF_INVALID;
800  }
801 }
802 
803 
804 
805 /**** Sprite Aligner ****/
806 
810  Scrollbar *vscroll;
811 
813  {
814  this->CreateNestedTree();
815  this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
816  this->FinishInitNested(wno);
817 
818  /* Oh yes, we assume there is at least one normal sprite! */
819  while (GetSpriteType(this->current_sprite) != ST_NORMAL) this->current_sprite++;
820  }
821 
822  virtual void SetStringParameters(int widget) const
823  {
824  switch (widget) {
825  case WID_SA_CAPTION:
826  SetDParam(0, this->current_sprite);
828  break;
829 
830  case WID_SA_OFFSETS: {
831  const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
832  SetDParam(0, spr->x_offs);
833  SetDParam(1, spr->y_offs);
834  break;
835  }
836 
837  default:
838  break;
839  }
840  }
841 
842  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
843  {
844  if (widget != WID_SA_LIST) return;
845 
846  resize->height = max(11, FONT_HEIGHT_NORMAL + 1);
847  resize->width = 1;
848 
849  /* Resize to about 200 pixels (for the preview) */
850  size->height = (1 + 200 / resize->height) * resize->height;
851  }
852 
853  virtual void DrawWidget(const Rect &r, int widget) const
854  {
855  switch (widget) {
856  case WID_SA_SPRITE: {
857  /* Center the sprite ourselves */
858  const Sprite *spr = GetSprite(this->current_sprite, ST_NORMAL);
859  int width = r.right - r.left + 1;
860  int height = r.bottom - r.top + 1;
861  int x = r.left - UnScaleByZoom(spr->x_offs, ZOOM_LVL_GUI) + (width - UnScaleByZoom(spr->width, ZOOM_LVL_GUI)) / 2;
862  int y = r.top - UnScaleByZoom(spr->y_offs, ZOOM_LVL_GUI) + (height - UnScaleByZoom(spr->height, ZOOM_LVL_GUI)) / 2;
863 
864  /* And draw only the part within the sprite area */
865  SubSprite subspr = {
866  spr->x_offs + (spr->width - ScaleByZoom(width, ZOOM_LVL_GUI)) / 2 + 1,
867  spr->y_offs + (spr->height - ScaleByZoom(height, ZOOM_LVL_GUI)) / 2 + 1,
868  spr->x_offs + (spr->width + ScaleByZoom(width, ZOOM_LVL_GUI)) / 2 - 1,
869  spr->y_offs + (spr->height + ScaleByZoom(height, ZOOM_LVL_GUI)) / 2 - 1,
870  };
871 
872  DrawSprite(this->current_sprite, PAL_NONE, x, y, &subspr, ZOOM_LVL_GUI);
873  break;
874  }
875 
876  case WID_SA_LIST: {
877  const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
878  int step_size = nwid->resize_y;
879 
880  SmallVector<SpriteID, 256> &list = _newgrf_debug_sprite_picker.sprites;
881  int max = min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), list.Length());
882 
883  int y = r.top + WD_FRAMERECT_TOP;
884  for (int i = this->vscroll->GetPosition(); i < max; i++) {
885  SetDParam(0, list[i]);
886  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_BLACK_COMMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
887  y += step_size;
888  }
889  break;
890  }
891  }
892  }
893 
894  virtual void OnClick(Point pt, int widget, int click_count)
895  {
896  switch (widget) {
897  case WID_SA_PREVIOUS:
898  do {
899  this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
900  } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
901  this->SetDirty();
902  break;
903 
904  case WID_SA_GOTO:
905  ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
906  break;
907 
908  case WID_SA_NEXT:
909  do {
910  this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
911  } while (GetSpriteType(this->current_sprite) != ST_NORMAL);
912  this->SetDirty();
913  break;
914 
915  case WID_SA_PICKER:
916  this->LowerWidget(WID_SA_PICKER);
917  _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
918  this->SetDirty();
919  break;
920 
921  case WID_SA_LIST: {
922  const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
923  int step_size = nwid->resize_y;
924 
925  uint i = this->vscroll->GetPosition() + (pt.y - nwid->pos_y) / step_size;
926  if (i < _newgrf_debug_sprite_picker.sprites.Length()) {
927  SpriteID spr = _newgrf_debug_sprite_picker.sprites[i];
928  if (GetSpriteType(spr) == ST_NORMAL) this->current_sprite = spr;
929  }
930  this->SetDirty();
931  break;
932  }
933 
934  case WID_SA_UP:
935  case WID_SA_DOWN:
936  case WID_SA_LEFT:
937  case WID_SA_RIGHT: {
938  /*
939  * Yes... this is a hack.
940  *
941  * No... I don't think it is useful to make this less of a hack.
942  *
943  * If you want to align sprites, you just need the number. Generally
944  * the sprite caches are big enough to not remove the sprite from the
945  * cache. If that's not the case, just let the NewGRF developer
946  * increase the cache size instead of storing thousands of offsets
947  * for the incredibly small chance that it's actually going to be
948  * used by someone and the sprite cache isn't big enough for that
949  * particular NewGRF developer.
950  */
951  Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, ST_NORMAL));
952  switch (widget) {
953  case WID_SA_UP: spr->y_offs--; break;
954  case WID_SA_DOWN: spr->y_offs++; break;
955  case WID_SA_LEFT: spr->x_offs--; break;
956  case WID_SA_RIGHT: spr->x_offs++; break;
957  }
958  /* Of course, we need to redraw the sprite, but where is it used?
959  * Everywhere is a safe bet. */
961  break;
962  }
963  }
964  }
965 
966  virtual void OnQueryTextFinished(char *str)
967  {
968  if (StrEmpty(str)) return;
969 
970  this->current_sprite = atoi(str);
971  if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
972  while (GetSpriteType(this->current_sprite) != ST_NORMAL) {
973  this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
974  }
975  this->SetDirty();
976  }
977 
983  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
984  {
985  if (!gui_scope) return;
986  if (data == 1) {
987  /* Sprite picker finished */
988  this->RaiseWidget(WID_SA_PICKER);
989  this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.Length());
990  }
991  }
992 
993  virtual void OnResize()
994  {
995  this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
996  }
997 };
998 
999 static const NWidgetPart _nested_sprite_aligner_widgets[] = {
1001  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1002  NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_SPRITE_ALIGNER_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1003  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1004  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1005  EndContainer(),
1006  NWidget(WWT_PANEL, COLOUR_GREY),
1007  NWidget(NWID_HORIZONTAL), SetPIP(0, 0, 10),
1008  NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1010  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0),
1011  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0),
1012  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0),
1013  EndContainer(),
1014  NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1015  NWidget(NWID_SPACER), SetFill(1, 1),
1016  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1017  NWidget(NWID_SPACER), SetFill(1, 1),
1018  EndContainer(),
1019  NWidget(NWID_HORIZONTAL_LTR), SetPIP(10, 5, 10),
1021  NWidget(NWID_SPACER), SetFill(1, 1),
1022  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1023  NWidget(NWID_SPACER), SetFill(1, 1),
1024  EndContainer(),
1025  NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP),
1026  EndContainer(),
1028  NWidget(NWID_SPACER), SetFill(1, 1),
1029  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1030  NWidget(NWID_SPACER), SetFill(1, 1),
1031  EndContainer(),
1032  EndContainer(),
1033  NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1034  NWidget(NWID_SPACER), SetFill(1, 1),
1035  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0),
1036  NWidget(NWID_SPACER), SetFill(1, 1),
1037  EndContainer(),
1038  NWidget(NWID_HORIZONTAL), SetPIP(10, 5, 10),
1039  NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS, STR_NULL), SetFill(1, 0),
1040  EndContainer(),
1041  EndContainer(),
1042  NWidget(NWID_VERTICAL), SetPIP(10, 5, 10),
1043  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
1045  NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
1046  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
1047  EndContainer(),
1048  EndContainer(),
1049  EndContainer(),
1050  EndContainer(),
1051 };
1052 
1053 static WindowDesc _sprite_aligner_desc(
1054  WDP_AUTO, "sprite_aligner", 400, 300,
1056  0,
1057  _nested_sprite_aligner_widgets, lengthof(_nested_sprite_aligner_widgets)
1058 );
1059 
1064 {
1065  AllocateWindowDescFront<SpriteAlignerWindow>(&_sprite_aligner_desc, 0);
1066 }