OpenTTD
build_vehicle_gui.cpp
Go to the documentation of this file.
1 /* $Id: build_vehicle_gui.cpp 26960 2014-10-05 11:20:02Z peter1138 $ */
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 "engine_base.h"
14 #include "engine_func.h"
15 #include "station_base.h"
16 #include "network/network.h"
17 #include "articulated_vehicles.h"
18 #include "textbuf_gui.h"
19 #include "command_func.h"
20 #include "company_func.h"
21 #include "vehicle_gui.h"
22 #include "newgrf_engine.h"
23 #include "newgrf_text.h"
24 #include "group.h"
25 #include "string_func.h"
26 #include "strings_func.h"
27 #include "window_func.h"
28 #include "date_func.h"
29 #include "vehicle_func.h"
30 #include "widgets/dropdown_func.h"
31 #include "engine_gui.h"
32 #include "cargotype.h"
33 #include "core/geometry_func.hpp"
34 #include "autoreplace_func.h"
35 
37 
38 #include "table/strings.h"
39 
40 #include "safeguards.h"
41 
48 {
50 }
51 
52 static const NWidgetPart _nested_build_vehicle_widgets[] = {
54  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
55  NWidget(WWT_CAPTION, COLOUR_GREY, WID_BV_CAPTION), SetDataTip(STR_WHITE_STRING, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
56  NWidget(WWT_SHADEBOX, COLOUR_GREY),
57  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
58  NWidget(WWT_STICKYBOX, COLOUR_GREY),
59  EndContainer(),
60  NWidget(WWT_PANEL, COLOUR_GREY),
63  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
64  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
65  EndContainer(),
68  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_BV_CARGO_FILTER_DROPDOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_FILTER_CRITERIA),
69  EndContainer(),
70  EndContainer(),
71  EndContainer(),
72  /* Vehicle list. */
74  NWidget(WWT_MATRIX, COLOUR_GREY, WID_BV_LIST), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_BV_SCROLLBAR),
76  EndContainer(),
77  /* Panel with details. */
78  NWidget(WWT_PANEL, COLOUR_GREY, WID_BV_PANEL), SetMinimalSize(240, 122), SetResize(1, 0), EndContainer(),
79  /* Build/rename buttons, resize button. */
81  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BV_BUILD_SEL),
82  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_BUILD), SetResize(1, 0), SetFill(1, 0),
83  EndContainer(),
84  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_SHOW_HIDE), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_JUST_STRING, STR_NULL),
85  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_BV_RENAME), SetResize(1, 0), SetFill(1, 0),
86  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
87  EndContainer(),
88 };
89 
91 static const CargoID CF_ANY = CT_NO_REFIT;
92 static const CargoID CF_NONE = CT_INVALID;
93 
95 byte _engine_sort_last_criteria[] = {0, 0, 0, 0};
96 bool _engine_sort_last_order[] = {false, false, false, false};
97 bool _engine_sort_show_hidden_engines[] = {false, false, false, false};
99 
106 static int CDECL EngineNumberSorter(const EngineID *a, const EngineID *b)
107 {
108  int r = Engine::Get(*a)->list_position - Engine::Get(*b)->list_position;
109 
110  return _engine_sort_direction ? -r : r;
111 }
112 
119 static int CDECL EngineIntroDateSorter(const EngineID *a, const EngineID *b)
120 {
121  const int va = Engine::Get(*a)->intro_date;
122  const int vb = Engine::Get(*b)->intro_date;
123  const int r = va - vb;
124 
125  /* Use EngineID to sort instead since we want consistent sorting */
126  if (r == 0) return EngineNumberSorter(a, b);
127  return _engine_sort_direction ? -r : r;
128 }
129 
136 static int CDECL EngineNameSorter(const EngineID *a, const EngineID *b)
137 {
138  static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE };
139  static char last_name[2][64] = { "\0", "\0" };
140 
141  const EngineID va = *a;
142  const EngineID vb = *b;
143 
144  if (va != last_engine[0]) {
145  last_engine[0] = va;
146  SetDParam(0, va);
147  GetString(last_name[0], STR_ENGINE_NAME, lastof(last_name[0]));
148  }
149 
150  if (vb != last_engine[1]) {
151  last_engine[1] = vb;
152  SetDParam(0, vb);
153  GetString(last_name[1], STR_ENGINE_NAME, lastof(last_name[1]));
154  }
155 
156  int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
157 
158  /* Use EngineID to sort instead since we want consistent sorting */
159  if (r == 0) return EngineNumberSorter(a, b);
160  return _engine_sort_direction ? -r : r;
161 }
162 
169 static int CDECL EngineReliabilitySorter(const EngineID *a, const EngineID *b)
170 {
171  const int va = Engine::Get(*a)->reliability;
172  const int vb = Engine::Get(*b)->reliability;
173  const int r = va - vb;
174 
175  /* Use EngineID to sort instead since we want consistent sorting */
176  if (r == 0) return EngineNumberSorter(a, b);
177  return _engine_sort_direction ? -r : r;
178 }
179 
186 static int CDECL EngineCostSorter(const EngineID *a, const EngineID *b)
187 {
188  Money va = Engine::Get(*a)->GetCost();
189  Money vb = Engine::Get(*b)->GetCost();
190  int r = ClampToI32(va - vb);
191 
192  /* Use EngineID to sort instead since we want consistent sorting */
193  if (r == 0) return EngineNumberSorter(a, b);
194  return _engine_sort_direction ? -r : r;
195 }
196 
203 static int CDECL EngineSpeedSorter(const EngineID *a, const EngineID *b)
204 {
205  int va = Engine::Get(*a)->GetDisplayMaxSpeed();
206  int vb = Engine::Get(*b)->GetDisplayMaxSpeed();
207  int r = va - vb;
208 
209  /* Use EngineID to sort instead since we want consistent sorting */
210  if (r == 0) return EngineNumberSorter(a, b);
211  return _engine_sort_direction ? -r : r;
212 }
213 
220 static int CDECL EnginePowerSorter(const EngineID *a, const EngineID *b)
221 {
222  int va = Engine::Get(*a)->GetPower();
223  int vb = Engine::Get(*b)->GetPower();
224  int r = va - vb;
225 
226  /* Use EngineID to sort instead since we want consistent sorting */
227  if (r == 0) return EngineNumberSorter(a, b);
228  return _engine_sort_direction ? -r : r;
229 }
230 
237 static int CDECL EngineTractiveEffortSorter(const EngineID *a, const EngineID *b)
238 {
239  int va = Engine::Get(*a)->GetDisplayMaxTractiveEffort();
240  int vb = Engine::Get(*b)->GetDisplayMaxTractiveEffort();
241  int r = va - vb;
242 
243  /* Use EngineID to sort instead since we want consistent sorting */
244  if (r == 0) return EngineNumberSorter(a, b);
245  return _engine_sort_direction ? -r : r;
246 }
247 
254 static int CDECL EngineRunningCostSorter(const EngineID *a, const EngineID *b)
255 {
256  Money va = Engine::Get(*a)->GetRunningCost();
257  Money vb = Engine::Get(*b)->GetRunningCost();
258  int r = ClampToI32(va - vb);
259 
260  /* Use EngineID to sort instead since we want consistent sorting */
261  if (r == 0) return EngineNumberSorter(a, b);
262  return _engine_sort_direction ? -r : r;
263 }
264 
271 static int CDECL EnginePowerVsRunningCostSorter(const EngineID *a, const EngineID *b)
272 {
273  const Engine *e_a = Engine::Get(*a);
274  const Engine *e_b = Engine::Get(*b);
275 
276  /* Here we are using a few tricks to get the right sort.
277  * We want power/running cost, but since we usually got higher running cost than power and we store the result in an int,
278  * we will actually calculate cunning cost/power (to make it more than 1).
279  * Because of this, the return value have to be reversed as well and we return b - a instead of a - b.
280  * Another thing is that both power and running costs should be doubled for multiheaded engines.
281  * Since it would be multiplying with 2 in both numerator and denominator, it will even themselves out and we skip checking for multiheaded. */
282  Money va = (e_a->GetRunningCost()) / max(1U, (uint)e_a->GetPower());
283  Money vb = (e_b->GetRunningCost()) / max(1U, (uint)e_b->GetPower());
284  int r = ClampToI32(vb - va);
285 
286  /* Use EngineID to sort instead since we want consistent sorting */
287  if (r == 0) return EngineNumberSorter(a, b);
288  return _engine_sort_direction ? -r : r;
289 }
290 
291 /* Train sorting functions */
292 
299 static int CDECL TrainEngineCapacitySorter(const EngineID *a, const EngineID *b)
300 {
301  const RailVehicleInfo *rvi_a = RailVehInfo(*a);
302  const RailVehicleInfo *rvi_b = RailVehInfo(*b);
303 
304  int va = GetTotalCapacityOfArticulatedParts(*a) * (rvi_a->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
305  int vb = GetTotalCapacityOfArticulatedParts(*b) * (rvi_b->railveh_type == RAILVEH_MULTIHEAD ? 2 : 1);
306  int r = va - vb;
307 
308  /* Use EngineID to sort instead since we want consistent sorting */
309  if (r == 0) return EngineNumberSorter(a, b);
310  return _engine_sort_direction ? -r : r;
311 }
312 
319 static int CDECL TrainEnginesThenWagonsSorter(const EngineID *a, const EngineID *b)
320 {
321  int val_a = (RailVehInfo(*a)->railveh_type == RAILVEH_WAGON ? 1 : 0);
322  int val_b = (RailVehInfo(*b)->railveh_type == RAILVEH_WAGON ? 1 : 0);
323  int r = val_a - val_b;
324 
325  /* Use EngineID to sort instead since we want consistent sorting */
326  if (r == 0) return EngineNumberSorter(a, b);
327  return _engine_sort_direction ? -r : r;
328 }
329 
330 /* Road vehicle sorting functions */
331 
338 static int CDECL RoadVehEngineCapacitySorter(const EngineID *a, const EngineID *b)
339 {
342  int r = va - vb;
343 
344  /* Use EngineID to sort instead since we want consistent sorting */
345  if (r == 0) return EngineNumberSorter(a, b);
346  return _engine_sort_direction ? -r : r;
347 }
348 
349 /* Ship vehicle sorting functions */
350 
357 static int CDECL ShipEngineCapacitySorter(const EngineID *a, const EngineID *b)
358 {
359  const Engine *e_a = Engine::Get(*a);
360  const Engine *e_b = Engine::Get(*b);
361 
362  int va = e_a->GetDisplayDefaultCapacity();
363  int vb = e_b->GetDisplayDefaultCapacity();
364  int r = va - vb;
365 
366  /* Use EngineID to sort instead since we want consistent sorting */
367  if (r == 0) return EngineNumberSorter(a, b);
368  return _engine_sort_direction ? -r : r;
369 }
370 
371 /* Aircraft sorting functions */
372 
379 static int CDECL AircraftEngineCargoSorter(const EngineID *a, const EngineID *b)
380 {
381  const Engine *e_a = Engine::Get(*a);
382  const Engine *e_b = Engine::Get(*b);
383 
384  uint16 mail_a, mail_b;
385  int va = e_a->GetDisplayDefaultCapacity(&mail_a);
386  int vb = e_b->GetDisplayDefaultCapacity(&mail_b);
387  int r = va - vb;
388 
389  if (r == 0) {
390  /* The planes have the same passenger capacity. Check mail capacity instead */
391  r = mail_a - mail_b;
392 
393  if (r == 0) {
394  /* Use EngineID to sort instead since we want consistent sorting */
395  return EngineNumberSorter(a, b);
396  }
397  }
398  return _engine_sort_direction ? -r : r;
399 }
400 
407 static int CDECL AircraftRangeSorter(const EngineID *a, const EngineID *b)
408 {
409  uint16 r_a = Engine::Get(*a)->GetRange();
410  uint16 r_b = Engine::Get(*b)->GetRange();
411 
412  int r = r_a - r_b;
413 
414  /* Use EngineID to sort instead since we want consistent sorting */
415  if (r == 0) return EngineNumberSorter(a, b);
416  return _engine_sort_direction ? -r : r;
417 }
418 
421  /* Trains */
433 }, {
434  /* Road vehicles */
446 }, {
447  /* Ships */
456 }, {
457  /* Aircraft */
467 }};
468 
471  /* Trains */
472  STR_SORT_BY_ENGINE_ID,
473  STR_SORT_BY_COST,
474  STR_SORT_BY_MAX_SPEED,
475  STR_SORT_BY_POWER,
476  STR_SORT_BY_TRACTIVE_EFFORT,
477  STR_SORT_BY_INTRO_DATE,
478  STR_SORT_BY_NAME,
479  STR_SORT_BY_RUNNING_COST,
480  STR_SORT_BY_POWER_VS_RUNNING_COST,
481  STR_SORT_BY_RELIABILITY,
482  STR_SORT_BY_CARGO_CAPACITY,
484 }, {
485  /* Road vehicles */
486  STR_SORT_BY_ENGINE_ID,
487  STR_SORT_BY_COST,
488  STR_SORT_BY_MAX_SPEED,
489  STR_SORT_BY_POWER,
490  STR_SORT_BY_TRACTIVE_EFFORT,
491  STR_SORT_BY_INTRO_DATE,
492  STR_SORT_BY_NAME,
493  STR_SORT_BY_RUNNING_COST,
494  STR_SORT_BY_POWER_VS_RUNNING_COST,
495  STR_SORT_BY_RELIABILITY,
496  STR_SORT_BY_CARGO_CAPACITY,
498 }, {
499  /* Ships */
500  STR_SORT_BY_ENGINE_ID,
501  STR_SORT_BY_COST,
502  STR_SORT_BY_MAX_SPEED,
503  STR_SORT_BY_INTRO_DATE,
504  STR_SORT_BY_NAME,
505  STR_SORT_BY_RUNNING_COST,
506  STR_SORT_BY_RELIABILITY,
507  STR_SORT_BY_CARGO_CAPACITY,
509 }, {
510  /* Aircraft */
511  STR_SORT_BY_ENGINE_ID,
512  STR_SORT_BY_COST,
513  STR_SORT_BY_MAX_SPEED,
514  STR_SORT_BY_INTRO_DATE,
515  STR_SORT_BY_NAME,
516  STR_SORT_BY_RUNNING_COST,
517  STR_SORT_BY_RELIABILITY,
518  STR_SORT_BY_CARGO_CAPACITY,
519  STR_SORT_BY_RANGE,
521 }};
522 
524 static bool CDECL CargoFilter(const EngineID *eid, const CargoID cid)
525 {
526  if (cid == CF_ANY) return true;
527  uint32 refit_mask = GetUnionOfArticulatedRefitMasks(*eid, true) & _standard_cargo_mask;
528  return (cid == CF_NONE ? refit_mask == 0 : HasBit(refit_mask, cid));
529 }
530 
531 static GUIEngineList::FilterFunction * const _filter_funcs[] = {
532  &CargoFilter,
533 };
534 
535 static int DrawCargoCapacityInfo(int left, int right, int y, EngineID engine)
536 {
537  CargoArray cap;
538  uint32 refits;
539  GetArticulatedVehicleCargoesAndRefits(engine, &cap, &refits);
540 
541  for (CargoID c = 0; c < NUM_CARGO; c++) {
542  if (cap[c] == 0) continue;
543 
544  SetDParam(0, c);
545  SetDParam(1, cap[c]);
546  SetDParam(2, HasBit(refits, c) ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
547  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
548  y += FONT_HEIGHT_NORMAL;
549  }
550 
551  return y;
552 }
553 
554 /* Draw rail wagon specific details */
555 static int DrawRailWagonPurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
556 {
557  const Engine *e = Engine::Get(engine_number);
558 
559  /* Purchase cost */
560  SetDParam(0, e->GetCost());
561  DrawString(left, right, y, STR_PURCHASE_INFO_COST);
562  y += FONT_HEIGHT_NORMAL;
563 
564  /* Wagon weight - (including cargo) */
565  uint weight = e->GetDisplayWeight();
566  SetDParam(0, weight);
567  uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
568  SetDParam(1, cargo_weight + weight);
569  DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
570  y += FONT_HEIGHT_NORMAL;
571 
572  /* Wagon speed limit, displayed if above zero */
574  uint max_speed = e->GetDisplayMaxSpeed();
575  if (max_speed > 0) {
576  SetDParam(0, max_speed);
577  DrawString(left, right, y, STR_PURCHASE_INFO_SPEED);
578  y += FONT_HEIGHT_NORMAL;
579  }
580  }
581 
582  /* Running cost */
583  if (rvi->running_cost_class != INVALID_PRICE) {
584  SetDParam(0, e->GetRunningCost());
585  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
586  y += FONT_HEIGHT_NORMAL;
587  }
588 
589  return y;
590 }
591 
592 /* Draw locomotive specific details */
593 static int DrawRailEnginePurchaseInfo(int left, int right, int y, EngineID engine_number, const RailVehicleInfo *rvi)
594 {
595  const Engine *e = Engine::Get(engine_number);
596 
597  /* Purchase Cost - Engine weight */
598  SetDParam(0, e->GetCost());
599  SetDParam(1, e->GetDisplayWeight());
600  DrawString(left, right, y, STR_PURCHASE_INFO_COST_WEIGHT);
601  y += FONT_HEIGHT_NORMAL;
602 
603  /* Max speed - Engine power */
604  SetDParam(0, e->GetDisplayMaxSpeed());
605  SetDParam(1, e->GetPower());
606  DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
607  y += FONT_HEIGHT_NORMAL;
608 
609  /* Max tractive effort - not applicable if old acceleration or maglev */
610  if (_settings_game.vehicle.train_acceleration_model != AM_ORIGINAL && GetRailTypeInfo(rvi->railtype)->acceleration_type != 2) {
612  DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
613  y += FONT_HEIGHT_NORMAL;
614  }
615 
616  /* Running cost */
617  if (rvi->running_cost_class != INVALID_PRICE) {
618  SetDParam(0, e->GetRunningCost());
619  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
620  y += FONT_HEIGHT_NORMAL;
621  }
622 
623  /* Powered wagons power - Powered wagons extra weight */
624  if (rvi->pow_wag_power != 0) {
625  SetDParam(0, rvi->pow_wag_power);
626  SetDParam(1, rvi->pow_wag_weight);
627  DrawString(left, right, y, STR_PURCHASE_INFO_PWAGPOWER_PWAGWEIGHT);
628  y += FONT_HEIGHT_NORMAL;
629  }
630 
631  return y;
632 }
633 
634 /* Draw road vehicle specific details */
635 static int DrawRoadVehPurchaseInfo(int left, int right, int y, EngineID engine_number)
636 {
637  const Engine *e = Engine::Get(engine_number);
638 
640  /* Purchase Cost */
641  SetDParam(0, e->GetCost());
642  DrawString(left, right, y, STR_PURCHASE_INFO_COST);
643  y += FONT_HEIGHT_NORMAL;
644 
645  /* Road vehicle weight - (including cargo) */
646  int16 weight = e->GetDisplayWeight();
647  SetDParam(0, weight);
648  uint cargo_weight = (e->CanCarryCargo() ? CargoSpec::Get(e->GetDefaultCargoType())->weight * GetTotalCapacityOfArticulatedParts(engine_number) / 16 : 0);
649  SetDParam(1, cargo_weight + weight);
650  DrawString(left, right, y, STR_PURCHASE_INFO_WEIGHT_CWEIGHT);
651  y += FONT_HEIGHT_NORMAL;
652 
653  /* Max speed - Engine power */
654  SetDParam(0, e->GetDisplayMaxSpeed());
655  SetDParam(1, e->GetPower());
656  DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_POWER);
657  y += FONT_HEIGHT_NORMAL;
658 
659  /* Max tractive effort */
661  DrawString(left, right, y, STR_PURCHASE_INFO_MAX_TE);
662  y += FONT_HEIGHT_NORMAL;
663  } else {
664  /* Purchase cost - Max speed */
665  SetDParam(0, e->GetCost());
666  SetDParam(1, e->GetDisplayMaxSpeed());
667  DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
668  y += FONT_HEIGHT_NORMAL;
669  }
670 
671  /* Running cost */
672  SetDParam(0, e->GetRunningCost());
673  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
674  y += FONT_HEIGHT_NORMAL;
675 
676  return y;
677 }
678 
679 /* Draw ship specific details */
680 static int DrawShipPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
681 {
682  const Engine *e = Engine::Get(engine_number);
683 
684  /* Purchase cost - Max speed */
685  uint raw_speed = e->GetDisplayMaxSpeed();
686  uint ocean_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, true);
687  uint canal_speed = e->u.ship.ApplyWaterClassSpeedFrac(raw_speed, false);
688 
689  SetDParam(0, e->GetCost());
690  if (ocean_speed == canal_speed) {
691  SetDParam(1, ocean_speed);
692  DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
693  y += FONT_HEIGHT_NORMAL;
694  } else {
695  DrawString(left, right, y, STR_PURCHASE_INFO_COST);
696  y += FONT_HEIGHT_NORMAL;
697 
698  SetDParam(0, ocean_speed);
699  DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_OCEAN);
700  y += FONT_HEIGHT_NORMAL;
701 
702  SetDParam(0, canal_speed);
703  DrawString(left, right, y, STR_PURCHASE_INFO_SPEED_CANAL);
704  y += FONT_HEIGHT_NORMAL;
705  }
706 
707  /* Cargo type + capacity */
710  SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
711  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
712  y += FONT_HEIGHT_NORMAL;
713 
714  /* Running cost */
715  SetDParam(0, e->GetRunningCost());
716  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
717  y += FONT_HEIGHT_NORMAL;
718 
719  return y;
720 }
721 
722 /* Draw aircraft specific details */
723 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
724 {
725  const Engine *e = Engine::Get(engine_number);
726  CargoID cargo = e->GetDefaultCargoType();
727 
728  /* Purchase cost - Max speed */
729  SetDParam(0, e->GetCost());
730  SetDParam(1, e->GetDisplayMaxSpeed());
731  DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
732  y += FONT_HEIGHT_NORMAL;
733 
734  /* Cargo capacity */
735  uint16 mail_capacity;
736  uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
737  if (mail_capacity > 0) {
738  SetDParam(0, cargo);
739  SetDParam(1, capacity);
740  SetDParam(2, CT_MAIL);
741  SetDParam(3, mail_capacity);
742  DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
743  } else {
744  /* Note, if the default capacity is selected by the refit capacity
745  * callback, then the capacity shown is likely to be incorrect. */
746  SetDParam(0, cargo);
747  SetDParam(1, capacity);
748  SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
749  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
750  }
751  y += FONT_HEIGHT_NORMAL;
752 
753  /* Running cost */
754  SetDParam(0, e->GetRunningCost());
755  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
756  y += FONT_HEIGHT_NORMAL;
757 
758  uint16 range = e->GetRange();
759  if (range != 0) {
760  SetDParam(0, range);
761  DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
762  y += FONT_HEIGHT_NORMAL;
763  }
764 
765  return y;
766 }
767 
776 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
777 {
778  uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, NULL);
779  if (callback == CALLBACK_FAILED || callback == 0x400) return y;
780  const GRFFile *grffile = Engine::Get(engine)->GetGRF();
781  if (callback > 0x400) {
782  ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback);
783  return y;
784  }
785 
786  StartTextRefStackUsage(grffile, 6);
787  uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(grffile->grfid, 0xD000 + callback), TC_BLACK);
789  return result;
790 }
791 
798 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number)
799 {
800  const Engine *e = Engine::Get(engine_number);
801  YearMonthDay ymd;
802  ConvertDateToYMD(e->intro_date, &ymd);
803  bool refittable = IsArticulatedVehicleRefittable(engine_number);
804  bool articulated_cargo = false;
805 
806  switch (e->type) {
807  default: NOT_REACHED();
808  case VEH_TRAIN:
809  if (e->u.rail.railveh_type == RAILVEH_WAGON) {
810  y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail);
811  } else {
812  y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail);
813  }
814  articulated_cargo = true;
815  break;
816 
817  case VEH_ROAD:
818  y = DrawRoadVehPurchaseInfo(left, right, y, engine_number);
819  articulated_cargo = true;
820  break;
821 
822  case VEH_SHIP:
823  y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable);
824  break;
825 
826  case VEH_AIRCRAFT:
827  y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable);
828  break;
829  }
830 
831  if (articulated_cargo) {
832  /* Cargo type + capacity, or N/A */
833  int new_y = DrawCargoCapacityInfo(left, right, y, engine_number);
834 
835  if (new_y == y) {
836  SetDParam(0, CT_INVALID);
837  SetDParam(2, STR_EMPTY);
838  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
839  y += FONT_HEIGHT_NORMAL;
840  } else {
841  y = new_y;
842  }
843  }
844 
845  /* Draw details that apply to all types except rail wagons. */
846  if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
847  /* Design date - Life length */
848  SetDParam(0, ymd.year);
850  DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
851  y += FONT_HEIGHT_NORMAL;
852 
853  /* Reliability */
855  DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
856  y += FONT_HEIGHT_NORMAL;
857  }
858 
859  if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
860 
861  /* Additional text from NewGRF */
862  y = ShowAdditionalText(left, right, y, engine_number);
863 
864  return y;
865 }
866 
880 void DrawEngineList(VehicleType type, int l, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group)
881 {
882  static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
883 
884  /* Obligatory sanity checks! */
885  assert(max <= eng_list->Length());
886 
887  bool rtl = _current_text_dir == TD_RTL;
888  int step_size = GetEngineListHeight(type);
889  int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
890  int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
891  int sprite_width = sprite_left + sprite_right;
892 
893  int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
894  int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
895 
896  Dimension replace_icon = {0, 0};
897  int count_width = 0;
898  if (show_count) {
899  replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
901  count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
902  }
903 
904  int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
905  int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
906  int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
907  int count_left = l;
908  int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
909 
910  int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
911  int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
912  int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
913 
914  for (; min < max; min++, y += step_size) {
915  const EngineID engine = (*eng_list)[min];
916  /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
917  const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
918 
919  const Engine *e = Engine::Get(engine);
920  bool hidden = HasBit(e->company_hidden, _local_company);
921  StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
922  TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
923 
924  SetDParam(0, engine);
925  DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
926  DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
927  if (show_count) {
928  SetDParam(0, num_engines);
929  DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
930  if (EngineHasReplacementForCompany(Company::Get(_local_company), engine, selected_group)) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, num_engines == 0 ? PALETTE_CRASH : PAL_NONE, replace_icon_left, y + replace_icon_y_offset);
931  }
932  }
933 }
934 
942 void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button)
943 {
944  uint32 hidden_mask = 0;
945  /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
946  if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
947  SetBit(hidden_mask, 3); // power
948  SetBit(hidden_mask, 4); // tractive effort
949  SetBit(hidden_mask, 8); // power by running costs
950  }
951  /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
952  if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
953  SetBit(hidden_mask, 4); // tractive effort
954  }
955  ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
956 }
957 
961  union {
964  } filter;
971  GUIEngineList eng_list;
972  CargoID cargo_filter[NUM_CARGO + 2];
973  StringID cargo_filter_texts[NUM_CARGO + 3];
976  Scrollbar *vscroll;
977 
978  BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
979  {
980  this->vehicle_type = type;
981  this->window_number = tile == INVALID_TILE ? (int)type : tile;
982 
983  this->sel_engine = INVALID_ENGINE;
984 
988 
989  switch (type) {
990  default: NOT_REACHED();
991  case VEH_TRAIN:
992  this->filter.railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
993  break;
994  case VEH_ROAD:
995  this->filter.roadtypes = (tile == INVALID_TILE) ? ROADTYPES_ALL : GetRoadTypes(tile);
996  case VEH_SHIP:
997  case VEH_AIRCRAFT:
998  break;
999  }
1000 
1001  this->listview_mode = (this->window_number <= VEH_END);
1002 
1003  this->CreateNestedTree();
1004 
1005  this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
1006 
1007  /* If we are just viewing the list of vehicles, we do not need the Build button.
1008  * So we just hide it, and enlarge the Rename button by the now vacant place. */
1009  if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
1010 
1011  /* disable renaming engines in network games if you are not the server */
1013 
1014  NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
1015  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
1016 
1017  widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE);
1018  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
1019 
1020  widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
1021  widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + type;
1022  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + type;
1023 
1024  widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
1025  widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
1026  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
1027 
1028  widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_ENGINES);
1029  widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
1030  widget->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
1031  widget->SetLowered(this->show_hidden_engines);
1032 
1034 
1035  this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
1036 
1037  this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
1038 
1039  this->eng_list.ForceRebuild();
1040  this->GenerateBuildList(); // generate the list, since we need it in the next line
1041  /* Select the first engine in the list as default when opening the window */
1042  if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
1043  }
1044 
1047  {
1048  uint filter_items = 0;
1049 
1050  /* Add item for disabling filtering. */
1051  this->cargo_filter[filter_items] = CF_ANY;
1052  this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1053  filter_items++;
1054 
1055  /* Add item for vehicles not carrying anything, e.g. train engines.
1056  * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1057  if (this->vehicle_type == VEH_TRAIN) {
1058  this->cargo_filter[filter_items] = CF_NONE;
1059  this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
1060  filter_items++;
1061  }
1062 
1063  /* Collect available cargo types for filtering. */
1064  const CargoSpec *cs;
1066  this->cargo_filter[filter_items] = cs->Index();
1067  this->cargo_filter_texts[filter_items] = cs->name;
1068  filter_items++;
1069  }
1070 
1071  /* Terminate the filter list. */
1072  this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1073 
1074  /* If not found, the cargo criteria will be set to all cargoes. */
1075  this->cargo_filter_criteria = 0;
1076 
1077  /* Find the last cargo filter criteria. */
1078  for (uint i = 0; i < filter_items; i++) {
1080  this->cargo_filter_criteria = i;
1081  break;
1082  }
1083  }
1084 
1085  this->eng_list.SetFilterFuncs(_filter_funcs);
1086  this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1087  }
1088 
1089  void OnInit()
1090  {
1091  this->SetCargoFilterArray();
1092  }
1093 
1096  {
1097  this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1098  if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
1099  this->sel_engine = INVALID_ENGINE;
1100  } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
1101  this->sel_engine = this->eng_list[0];
1102  }
1103  }
1104 
1107  {
1108  CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1109  return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
1110  }
1111 
1112  /* Figure out what train EngineIDs to put in the list */
1113  void GenerateBuildTrainList()
1114  {
1115  EngineID sel_id = INVALID_ENGINE;
1116  int num_engines = 0;
1117  int num_wagons = 0;
1118 
1119  this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number);
1120 
1121  this->eng_list.Clear();
1122 
1123  /* Make list of all available train engines and wagons.
1124  * Also check to see if the previously selected engine is still available,
1125  * and if not, reset selection to INVALID_ENGINE. This could be the case
1126  * when engines become obsolete and are removed */
1127  const Engine *e;
1128  FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) {
1129  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1130  EngineID eid = e->index;
1131  const RailVehicleInfo *rvi = &e->u.rail;
1132 
1133  if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1134  if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1135 
1136  /* Filter now! So num_engines and num_wagons is valid */
1137  if (!FilterSingleEngine(eid)) continue;
1138 
1139  *this->eng_list.Append() = eid;
1140 
1141  if (rvi->railveh_type != RAILVEH_WAGON) {
1142  num_engines++;
1143  } else {
1144  num_wagons++;
1145  }
1146 
1147  if (eid == this->sel_engine) sel_id = eid;
1148  }
1149 
1150  this->sel_engine = sel_id;
1151 
1152  /* make engines first, and then wagons, sorted by selected sort_criteria */
1153  _engine_sort_direction = false;
1154  EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1155 
1156  /* and then sort engines */
1158  EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
1159 
1160  /* and finally sort wagons */
1161  EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
1162  }
1163 
1164  /* Figure out what road vehicle EngineIDs to put in the list */
1165  void GenerateBuildRoadVehList()
1166  {
1167  EngineID sel_id = INVALID_ENGINE;
1168 
1169  this->eng_list.Clear();
1170 
1171  const Engine *e;
1172  FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) {
1173  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1174  EngineID eid = e->index;
1175  if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1176  if (!HasBit(this->filter.roadtypes, HasBit(EngInfo(eid)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD)) continue;
1177  *this->eng_list.Append() = eid;
1178 
1179  if (eid == this->sel_engine) sel_id = eid;
1180  }
1181  this->sel_engine = sel_id;
1182  }
1183 
1184  /* Figure out what ship EngineIDs to put in the list */
1185  void GenerateBuildShipList()
1186  {
1187  EngineID sel_id = INVALID_ENGINE;
1188  this->eng_list.Clear();
1189 
1190  const Engine *e;
1191  FOR_ALL_ENGINES_OF_TYPE(e, VEH_SHIP) {
1192  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1193  EngineID eid = e->index;
1194  if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1195  *this->eng_list.Append() = eid;
1196 
1197  if (eid == this->sel_engine) sel_id = eid;
1198  }
1199  this->sel_engine = sel_id;
1200  }
1201 
1202  /* Figure out what aircraft EngineIDs to put in the list */
1203  void GenerateBuildAircraftList()
1204  {
1205  EngineID sel_id = INVALID_ENGINE;
1206 
1207  this->eng_list.Clear();
1208 
1209  const Station *st = this->listview_mode ? NULL : Station::GetByTile(this->window_number);
1210 
1211  /* Make list of all available planes.
1212  * Also check to see if the previously selected plane is still available,
1213  * and if not, reset selection to INVALID_ENGINE. This could be the case
1214  * when planes become obsolete and are removed */
1215  const Engine *e;
1216  FOR_ALL_ENGINES_OF_TYPE(e, VEH_AIRCRAFT) {
1217  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1218  EngineID eid = e->index;
1219  if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1220  /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1221  if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1222 
1223  *this->eng_list.Append() = eid;
1224  if (eid == this->sel_engine) sel_id = eid;
1225  }
1226 
1227  this->sel_engine = sel_id;
1228  }
1229 
1230  /* Generate the list of vehicles */
1231  void GenerateBuildList()
1232  {
1233  if (!this->eng_list.NeedRebuild()) return;
1234  switch (this->vehicle_type) {
1235  default: NOT_REACHED();
1236  case VEH_TRAIN:
1237  this->GenerateBuildTrainList();
1238  this->eng_list.Compact();
1239  this->eng_list.RebuildDone();
1240  return; // trains should not reach the last sorting
1241  case VEH_ROAD:
1242  this->GenerateBuildRoadVehList();
1243  break;
1244  case VEH_SHIP:
1245  this->GenerateBuildShipList();
1246  break;
1247  case VEH_AIRCRAFT:
1248  this->GenerateBuildAircraftList();
1249  break;
1250  }
1251 
1252  this->FilterEngineList();
1253 
1255  EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
1256 
1257  this->eng_list.Compact();
1258  this->eng_list.RebuildDone();
1259  }
1260 
1261  void OnClick(Point pt, int widget, int click_count)
1262  {
1263  switch (widget) {
1265  this->descending_sort_order ^= true;
1267  this->eng_list.ForceRebuild();
1268  this->SetDirty();
1269  break;
1270 
1272  this->show_hidden_engines ^= true;
1274  this->eng_list.ForceRebuild();
1275  this->SetWidgetLoweredState(widget, this->show_hidden_engines);
1276  this->SetDirty();
1277  break;
1278 
1279  case WID_BV_LIST: {
1280  uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1281  size_t num_items = this->eng_list.Length();
1282  this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE;
1283  this->SetDirty();
1284  if (_ctrl_pressed) {
1285  this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
1286  } else if (click_count > 1 && !this->listview_mode) {
1287  this->OnClick(pt, WID_BV_BUILD, 1);
1288  }
1289  break;
1290  }
1291 
1292  case WID_BV_SORT_DROPDOWN: // Select sorting criteria dropdown menu
1294  break;
1295 
1296  case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1298  break;
1299 
1300  case WID_BV_SHOW_HIDE: {
1301  const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine);
1302  if (e != NULL) {
1303  DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
1304  }
1305  break;
1306  }
1307 
1308  case WID_BV_BUILD: {
1309  EngineID sel_eng = this->sel_engine;
1310  if (sel_eng != INVALID_ENGINE) {
1311  CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1312  DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
1313  }
1314  break;
1315  }
1316 
1317  case WID_BV_RENAME: {
1318  EngineID sel_eng = this->sel_engine;
1319  if (sel_eng != INVALID_ENGINE) {
1320  this->rename_engine = sel_eng;
1321  SetDParam(0, sel_eng);
1322  ShowQueryString(STR_ENGINE_NAME, STR_QUERY_RENAME_TRAIN_TYPE_CAPTION + this->vehicle_type, MAX_LENGTH_ENGINE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
1323  }
1324  break;
1325  }
1326  }
1327  }
1328 
1334  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1335  {
1336  if (!gui_scope) return;
1337  /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1338  if (this->vehicle_type == VEH_ROAD &&
1340  this->sort_criteria > 7) {
1341  this->sort_criteria = 0;
1343  }
1344  this->eng_list.ForceRebuild();
1345  }
1346 
1347  virtual void SetStringParameters(int widget) const
1348  {
1349  switch (widget) {
1350  case WID_BV_CAPTION:
1351  if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1352  const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1353  SetDParam(0, rti->strings.build_caption);
1354  } else {
1355  SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1356  }
1357  break;
1358 
1359  case WID_BV_SORT_DROPDOWN:
1361  break;
1362 
1365  break;
1366 
1367  case WID_BV_SHOW_HIDE: {
1368  const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine);
1369  if (e != NULL && e->IsHidden(_local_company)) {
1370  SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
1371  } else {
1372  SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1373  }
1374  break;
1375  }
1376  }
1377  }
1378 
1379  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1380  {
1381  switch (widget) {
1382  case WID_BV_LIST:
1383  resize->height = GetEngineListHeight(this->vehicle_type);
1384  size->height = 3 * resize->height;
1385  size->width = max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
1386  break;
1387 
1388  case WID_BV_PANEL:
1389  size->height = this->details_height;
1390  break;
1391 
1393  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1394  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1395  d.height += padding.height;
1396  *size = maxdim(*size, d);
1397  break;
1398  }
1399 
1400  case WID_BV_SHOW_HIDE:
1401  *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1402  *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
1403  size->width += padding.width;
1404  size->height += padding.height;
1405  break;
1406  }
1407  }
1408 
1409  virtual void DrawWidget(const Rect &r, int widget) const
1410  {
1411  switch (widget) {
1412  case WID_BV_LIST:
1413  DrawEngineList(this->vehicle_type, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, &this->eng_list, this->vscroll->GetPosition(), min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->eng_list.Length()), this->sel_engine, false, DEFAULT_GROUP);
1414  break;
1415 
1418  break;
1419  }
1420  }
1421 
1422  virtual void OnPaint()
1423  {
1424  this->GenerateBuildList();
1425  this->vscroll->SetCount(this->eng_list.Length());
1426 
1428 
1429  this->DrawWidgets();
1430 
1431  if (!this->IsShaded()) {
1432  int needed_height = this->details_height;
1433  /* Draw details panels. */
1434  if (this->sel_engine != INVALID_ENGINE) {
1435  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1437  nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine);
1438  needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
1439  }
1440  if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1441  int resize = needed_height - this->details_height;
1442  this->details_height = needed_height;
1443  this->ReInit(0, resize);
1444  return;
1445  }
1446  }
1447  }
1448 
1449  virtual void OnQueryTextFinished(char *str)
1450  {
1451  if (str == NULL) return;
1452 
1453  DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), NULL, str);
1454  }
1455 
1456  virtual void OnDropdownSelect(int widget, int index)
1457  {
1458  switch (widget) {
1459  case WID_BV_SORT_DROPDOWN:
1460  if (this->sort_criteria != index) {
1461  this->sort_criteria = index;
1463  this->eng_list.ForceRebuild();
1464  }
1465  break;
1466 
1467  case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1468  if (this->cargo_filter_criteria != index) {
1469  this->cargo_filter_criteria = index;
1471  /* deactivate filter if criteria is 'Show All', activate it otherwise */
1472  this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1473  this->eng_list.ForceRebuild();
1474  }
1475  break;
1476  }
1477  this->SetDirty();
1478  }
1479 
1480  virtual void OnResize()
1481  {
1482  this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
1483  }
1484 };
1485 
1486 static WindowDesc _build_vehicle_desc(
1487  WDP_AUTO, "build_vehicle", 240, 268,
1490  _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
1491 );
1492 
1493 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
1494 {
1495  /* We want to be able to open both Available Train as Available Ships,
1496  * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1497  * As it always is a low value, it won't collide with any real tile
1498  * number. */
1499  uint num = (tile == INVALID_TILE) ? (int)type : tile;
1500 
1501  assert(IsCompanyBuildableVehicleType(type));
1502 
1504 
1505  new BuildVehicleWindow(&_build_vehicle_desc, tile, type);
1506 }