OpenTTD
build_vehicle_gui.cpp
Go to the documentation of this file.
1 /* $Id: build_vehicle_gui.cpp 27799 2017-03-18 20:46:15Z 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 "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 
731 static int DrawAircraftPurchaseInfo(int left, int right, int y, EngineID engine_number, bool refittable)
732 {
733  const Engine *e = Engine::Get(engine_number);
734  CargoID cargo = e->GetDefaultCargoType();
735 
736  /* Purchase cost - Max speed */
737  SetDParam(0, e->GetCost());
738  SetDParam(1, e->GetDisplayMaxSpeed());
739  DrawString(left, right, y, STR_PURCHASE_INFO_COST_SPEED);
740  y += FONT_HEIGHT_NORMAL;
741 
742  /* Cargo capacity */
743  uint16 mail_capacity;
744  uint capacity = e->GetDisplayDefaultCapacity(&mail_capacity);
745  if (mail_capacity > 0) {
746  SetDParam(0, cargo);
747  SetDParam(1, capacity);
748  SetDParam(2, CT_MAIL);
749  SetDParam(3, mail_capacity);
750  DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY);
751  } else {
752  /* Note, if the default capacity is selected by the refit capacity
753  * callback, then the capacity shown is likely to be incorrect. */
754  SetDParam(0, cargo);
755  SetDParam(1, capacity);
756  SetDParam(2, refittable ? STR_PURCHASE_INFO_REFITTABLE : STR_EMPTY);
757  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
758  }
759  y += FONT_HEIGHT_NORMAL;
760 
761  /* Running cost */
762  SetDParam(0, e->GetRunningCost());
763  DrawString(left, right, y, STR_PURCHASE_INFO_RUNNINGCOST);
764  y += FONT_HEIGHT_NORMAL;
765 
766  /* Aircraft type */
768  DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_TYPE);
769  y += FONT_HEIGHT_NORMAL;
770 
771  /* Aircraft range, if available. */
772  uint16 range = e->GetRange();
773  if (range != 0) {
774  SetDParam(0, range);
775  DrawString(left, right, y, STR_PURCHASE_INFO_AIRCRAFT_RANGE);
776  y += FONT_HEIGHT_NORMAL;
777  }
778 
779  return y;
780 }
781 
790 static uint ShowAdditionalText(int left, int right, int y, EngineID engine)
791 {
792  uint16 callback = GetVehicleCallback(CBID_VEHICLE_ADDITIONAL_TEXT, 0, 0, engine, NULL);
793  if (callback == CALLBACK_FAILED || callback == 0x400) return y;
794  const GRFFile *grffile = Engine::Get(engine)->GetGRF();
795  if (callback > 0x400) {
796  ErrorUnknownCallbackResult(grffile->grfid, CBID_VEHICLE_ADDITIONAL_TEXT, callback);
797  return y;
798  }
799 
800  StartTextRefStackUsage(grffile, 6);
801  uint result = DrawStringMultiLine(left, right, y, INT32_MAX, GetGRFStringID(grffile->grfid, 0xD000 + callback), TC_BLACK);
803  return result;
804 }
805 
812 int DrawVehiclePurchaseInfo(int left, int right, int y, EngineID engine_number)
813 {
814  const Engine *e = Engine::Get(engine_number);
815  YearMonthDay ymd;
816  ConvertDateToYMD(e->intro_date, &ymd);
817  bool refittable = IsArticulatedVehicleRefittable(engine_number);
818  bool articulated_cargo = false;
819 
820  switch (e->type) {
821  default: NOT_REACHED();
822  case VEH_TRAIN:
823  if (e->u.rail.railveh_type == RAILVEH_WAGON) {
824  y = DrawRailWagonPurchaseInfo(left, right, y, engine_number, &e->u.rail);
825  } else {
826  y = DrawRailEnginePurchaseInfo(left, right, y, engine_number, &e->u.rail);
827  }
828  articulated_cargo = true;
829  break;
830 
831  case VEH_ROAD:
832  y = DrawRoadVehPurchaseInfo(left, right, y, engine_number);
833  articulated_cargo = true;
834  break;
835 
836  case VEH_SHIP:
837  y = DrawShipPurchaseInfo(left, right, y, engine_number, refittable);
838  break;
839 
840  case VEH_AIRCRAFT:
841  y = DrawAircraftPurchaseInfo(left, right, y, engine_number, refittable);
842  break;
843  }
844 
845  if (articulated_cargo) {
846  /* Cargo type + capacity, or N/A */
847  int new_y = DrawCargoCapacityInfo(left, right, y, engine_number);
848 
849  if (new_y == y) {
850  SetDParam(0, CT_INVALID);
851  SetDParam(2, STR_EMPTY);
852  DrawString(left, right, y, STR_PURCHASE_INFO_CAPACITY);
853  y += FONT_HEIGHT_NORMAL;
854  } else {
855  y = new_y;
856  }
857  }
858 
859  /* Draw details that apply to all types except rail wagons. */
860  if (e->type != VEH_TRAIN || e->u.rail.railveh_type != RAILVEH_WAGON) {
861  /* Design date - Life length */
862  SetDParam(0, ymd.year);
864  DrawString(left, right, y, STR_PURCHASE_INFO_DESIGNED_LIFE);
865  y += FONT_HEIGHT_NORMAL;
866 
867  /* Reliability */
869  DrawString(left, right, y, STR_PURCHASE_INFO_RELIABILITY);
870  y += FONT_HEIGHT_NORMAL;
871  }
872 
873  if (refittable) y = ShowRefitOptionsList(left, right, y, engine_number);
874 
875  /* Additional text from NewGRF */
876  y = ShowAdditionalText(left, right, y, engine_number);
877 
878  return y;
879 }
880 
894 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)
895 {
896  static const int sprite_y_offsets[] = { -1, -1, -2, -2 };
897 
898  /* Obligatory sanity checks! */
899  assert(max <= eng_list->Length());
900 
901  bool rtl = _current_text_dir == TD_RTL;
902  int step_size = GetEngineListHeight(type);
903  int sprite_left = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_left;
904  int sprite_right = GetVehicleImageCellSize(type, EIT_PURCHASE).extend_right;
905  int sprite_width = sprite_left + sprite_right;
906 
907  int sprite_x = rtl ? r - sprite_right - 1 : l + sprite_left + 1;
908  int sprite_y_offset = sprite_y_offsets[type] + step_size / 2;
909 
910  Dimension replace_icon = {0, 0};
911  int count_width = 0;
912  if (show_count) {
913  replace_icon = GetSpriteSize(SPR_GROUP_REPLACE_ACTIVE);
915  count_width = GetStringBoundingBox(STR_TINY_BLACK_COMA).width;
916  }
917 
918  int text_left = l + (rtl ? WD_FRAMERECT_LEFT + replace_icon.width + 8 + count_width : sprite_width + WD_FRAMETEXT_LEFT);
919  int text_right = r - (rtl ? sprite_width + WD_FRAMETEXT_RIGHT : WD_FRAMERECT_RIGHT + replace_icon.width + 8 + count_width);
920  int replace_icon_left = rtl ? l + WD_FRAMERECT_LEFT : r - WD_FRAMERECT_RIGHT - replace_icon.width;
921  int count_left = l;
922  int count_right = rtl ? text_left : r - WD_FRAMERECT_RIGHT - replace_icon.width - 8;
923 
924  int normal_text_y_offset = (step_size - FONT_HEIGHT_NORMAL) / 2;
925  int small_text_y_offset = step_size - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1;
926  int replace_icon_y_offset = (step_size - replace_icon.height) / 2 - 1;
927 
928  for (; min < max; min++, y += step_size) {
929  const EngineID engine = (*eng_list)[min];
930  /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */
931  const uint num_engines = GetGroupNumEngines(_local_company, selected_group, engine);
932 
933  const Engine *e = Engine::Get(engine);
934  bool hidden = HasBit(e->company_hidden, _local_company);
935  StringID str = hidden ? STR_HIDDEN_ENGINE_NAME : STR_ENGINE_NAME;
936  TextColour tc = (engine == selected_id) ? TC_WHITE : (TC_NO_SHADE | (hidden ? TC_GREY : TC_BLACK));
937 
938  SetDParam(0, engine);
939  DrawString(text_left, text_right, y + normal_text_y_offset, str, tc);
940  DrawVehicleEngine(l, r, sprite_x, y + sprite_y_offset, engine, (show_count && num_engines == 0) ? PALETTE_CRASH : GetEnginePalette(engine, _local_company), EIT_PURCHASE);
941  if (show_count) {
942  SetDParam(0, num_engines);
943  DrawString(count_left, count_right, y + small_text_y_offset, STR_TINY_BLACK_COMA, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
944  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);
945  }
946  }
947 }
948 
956 void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button)
957 {
958  uint32 hidden_mask = 0;
959  /* Disable sorting by power or tractive effort when the original acceleration model for road vehicles is being used. */
960  if (vehicle_type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model == AM_ORIGINAL) {
961  SetBit(hidden_mask, 3); // power
962  SetBit(hidden_mask, 4); // tractive effort
963  SetBit(hidden_mask, 8); // power by running costs
964  }
965  /* Disable sorting by tractive effort when the original acceleration model for trains is being used. */
966  if (vehicle_type == VEH_TRAIN && _settings_game.vehicle.train_acceleration_model == AM_ORIGINAL) {
967  SetBit(hidden_mask, 4); // tractive effort
968  }
969  ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask);
970 }
971 
975  union {
978  } filter;
985  GUIEngineList eng_list;
986  CargoID cargo_filter[NUM_CARGO + 2];
987  StringID cargo_filter_texts[NUM_CARGO + 3];
990  Scrollbar *vscroll;
991 
992  BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc)
993  {
994  this->vehicle_type = type;
995  this->window_number = tile == INVALID_TILE ? (int)type : tile;
996 
997  this->sel_engine = INVALID_ENGINE;
998 
1002 
1003  switch (type) {
1004  default: NOT_REACHED();
1005  case VEH_TRAIN:
1006  this->filter.railtype = (tile == INVALID_TILE) ? RAILTYPE_END : GetRailType(tile);
1007  break;
1008  case VEH_ROAD:
1009  this->filter.roadtypes = (tile == INVALID_TILE) ? ROADTYPES_ALL : GetRoadTypes(tile);
1010  case VEH_SHIP:
1011  case VEH_AIRCRAFT:
1012  break;
1013  }
1014 
1015  this->listview_mode = (this->window_number <= VEH_END);
1016 
1017  this->CreateNestedTree();
1018 
1019  this->vscroll = this->GetScrollbar(WID_BV_SCROLLBAR);
1020 
1021  /* If we are just viewing the list of vehicles, we do not need the Build button.
1022  * So we just hide it, and enlarge the Rename button by the now vacant place. */
1023  if (this->listview_mode) this->GetWidget<NWidgetStacked>(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE);
1024 
1025  /* disable renaming engines in network games if you are not the server */
1027 
1028  NWidgetCore *widget = this->GetWidget<NWidgetCore>(WID_BV_LIST);
1029  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
1030 
1031  widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDE);
1032  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_HIDE_SHOW_TOGGLE_TOOLTIP + type;
1033 
1034  widget = this->GetWidget<NWidgetCore>(WID_BV_BUILD);
1035  widget->widget_data = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_BUTTON + type;
1036  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_BUY_VEHICLE_TOOLTIP + type;
1037 
1038  widget = this->GetWidget<NWidgetCore>(WID_BV_RENAME);
1039  widget->widget_data = STR_BUY_VEHICLE_TRAIN_RENAME_BUTTON + type;
1040  widget->tool_tip = STR_BUY_VEHICLE_TRAIN_RENAME_TOOLTIP + type;
1041 
1042  widget = this->GetWidget<NWidgetCore>(WID_BV_SHOW_HIDDEN_ENGINES);
1043  widget->widget_data = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN + type;
1044  widget->tool_tip = STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP + type;
1045  widget->SetLowered(this->show_hidden_engines);
1046 
1048 
1049  this->FinishInitNested(tile == INVALID_TILE ? (int)type : tile);
1050 
1051  this->owner = (tile != INVALID_TILE) ? GetTileOwner(tile) : _local_company;
1052 
1053  this->eng_list.ForceRebuild();
1054  this->GenerateBuildList(); // generate the list, since we need it in the next line
1055  /* Select the first engine in the list as default when opening the window */
1056  if (this->eng_list.Length() > 0) this->sel_engine = this->eng_list[0];
1057  }
1058 
1061  {
1062  uint filter_items = 0;
1063 
1064  /* Add item for disabling filtering. */
1065  this->cargo_filter[filter_items] = CF_ANY;
1066  this->cargo_filter_texts[filter_items] = STR_PURCHASE_INFO_ALL_TYPES;
1067  filter_items++;
1068 
1069  /* Add item for vehicles not carrying anything, e.g. train engines.
1070  * This could also be useful for eyecandy vehicles of other types, but is likely too confusing for joe, */
1071  if (this->vehicle_type == VEH_TRAIN) {
1072  this->cargo_filter[filter_items] = CF_NONE;
1073  this->cargo_filter_texts[filter_items] = STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE;
1074  filter_items++;
1075  }
1076 
1077  /* Collect available cargo types for filtering. */
1078  const CargoSpec *cs;
1080  this->cargo_filter[filter_items] = cs->Index();
1081  this->cargo_filter_texts[filter_items] = cs->name;
1082  filter_items++;
1083  }
1084 
1085  /* Terminate the filter list. */
1086  this->cargo_filter_texts[filter_items] = INVALID_STRING_ID;
1087 
1088  /* If not found, the cargo criteria will be set to all cargoes. */
1089  this->cargo_filter_criteria = 0;
1090 
1091  /* Find the last cargo filter criteria. */
1092  for (uint i = 0; i < filter_items; i++) {
1094  this->cargo_filter_criteria = i;
1095  break;
1096  }
1097  }
1098 
1099  this->eng_list.SetFilterFuncs(_filter_funcs);
1100  this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1101  }
1102 
1103  void OnInit()
1104  {
1105  this->SetCargoFilterArray();
1106  }
1107 
1110  {
1111  this->eng_list.Filter(this->cargo_filter[this->cargo_filter_criteria]);
1112  if (0 == this->eng_list.Length()) { // no engine passed through the filter, invalidate the previously selected engine
1113  this->sel_engine = INVALID_ENGINE;
1114  } else if (!this->eng_list.Contains(this->sel_engine)) { // previously selected engine didn't pass the filter, select the first engine of the list
1115  this->sel_engine = this->eng_list[0];
1116  }
1117  }
1118 
1121  {
1122  CargoID filter_type = this->cargo_filter[this->cargo_filter_criteria];
1123  return (filter_type == CF_ANY || CargoFilter(&eid, filter_type));
1124  }
1125 
1126  /* Figure out what train EngineIDs to put in the list */
1127  void GenerateBuildTrainList()
1128  {
1129  EngineID sel_id = INVALID_ENGINE;
1130  int num_engines = 0;
1131  int num_wagons = 0;
1132 
1133  this->filter.railtype = (this->listview_mode) ? RAILTYPE_END : GetRailType(this->window_number);
1134 
1135  this->eng_list.Clear();
1136 
1137  /* Make list of all available train engines and wagons.
1138  * Also check to see if the previously selected engine is still available,
1139  * and if not, reset selection to INVALID_ENGINE. This could be the case
1140  * when engines become obsolete and are removed */
1141  const Engine *e;
1142  FOR_ALL_ENGINES_OF_TYPE(e, VEH_TRAIN) {
1143  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1144  EngineID eid = e->index;
1145  const RailVehicleInfo *rvi = &e->u.rail;
1146 
1147  if (this->filter.railtype != RAILTYPE_END && !HasPowerOnRail(rvi->railtype, this->filter.railtype)) continue;
1148  if (!IsEngineBuildable(eid, VEH_TRAIN, _local_company)) continue;
1149 
1150  /* Filter now! So num_engines and num_wagons is valid */
1151  if (!FilterSingleEngine(eid)) continue;
1152 
1153  *this->eng_list.Append() = eid;
1154 
1155  if (rvi->railveh_type != RAILVEH_WAGON) {
1156  num_engines++;
1157  } else {
1158  num_wagons++;
1159  }
1160 
1161  if (eid == this->sel_engine) sel_id = eid;
1162  }
1163 
1164  this->sel_engine = sel_id;
1165 
1166  /* make engines first, and then wagons, sorted by selected sort_criteria */
1167  _engine_sort_direction = false;
1168  EngList_Sort(&this->eng_list, TrainEnginesThenWagonsSorter);
1169 
1170  /* and then sort engines */
1172  EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], 0, num_engines);
1173 
1174  /* and finally sort wagons */
1175  EngList_SortPartial(&this->eng_list, _engine_sort_functions[0][this->sort_criteria], num_engines, num_wagons);
1176  }
1177 
1178  /* Figure out what road vehicle EngineIDs to put in the list */
1179  void GenerateBuildRoadVehList()
1180  {
1181  EngineID sel_id = INVALID_ENGINE;
1182 
1183  this->eng_list.Clear();
1184 
1185  const Engine *e;
1186  FOR_ALL_ENGINES_OF_TYPE(e, VEH_ROAD) {
1187  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1188  EngineID eid = e->index;
1189  if (!IsEngineBuildable(eid, VEH_ROAD, _local_company)) continue;
1190  if (!HasBit(this->filter.roadtypes, HasBit(EngInfo(eid)->misc_flags, EF_ROAD_TRAM) ? ROADTYPE_TRAM : ROADTYPE_ROAD)) continue;
1191  *this->eng_list.Append() = eid;
1192 
1193  if (eid == this->sel_engine) sel_id = eid;
1194  }
1195  this->sel_engine = sel_id;
1196  }
1197 
1198  /* Figure out what ship EngineIDs to put in the list */
1199  void GenerateBuildShipList()
1200  {
1201  EngineID sel_id = INVALID_ENGINE;
1202  this->eng_list.Clear();
1203 
1204  const Engine *e;
1205  FOR_ALL_ENGINES_OF_TYPE(e, VEH_SHIP) {
1206  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1207  EngineID eid = e->index;
1208  if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue;
1209  *this->eng_list.Append() = eid;
1210 
1211  if (eid == this->sel_engine) sel_id = eid;
1212  }
1213  this->sel_engine = sel_id;
1214  }
1215 
1216  /* Figure out what aircraft EngineIDs to put in the list */
1217  void GenerateBuildAircraftList()
1218  {
1219  EngineID sel_id = INVALID_ENGINE;
1220 
1221  this->eng_list.Clear();
1222 
1223  const Station *st = this->listview_mode ? NULL : Station::GetByTile(this->window_number);
1224 
1225  /* Make list of all available planes.
1226  * Also check to see if the previously selected plane is still available,
1227  * and if not, reset selection to INVALID_ENGINE. This could be the case
1228  * when planes become obsolete and are removed */
1229  const Engine *e;
1230  FOR_ALL_ENGINES_OF_TYPE(e, VEH_AIRCRAFT) {
1231  if (!this->show_hidden_engines && e->IsHidden(_local_company)) continue;
1232  EngineID eid = e->index;
1233  if (!IsEngineBuildable(eid, VEH_AIRCRAFT, _local_company)) continue;
1234  /* First VEH_END window_numbers are fake to allow a window open for all different types at once */
1235  if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue;
1236 
1237  *this->eng_list.Append() = eid;
1238  if (eid == this->sel_engine) sel_id = eid;
1239  }
1240 
1241  this->sel_engine = sel_id;
1242  }
1243 
1244  /* Generate the list of vehicles */
1245  void GenerateBuildList()
1246  {
1247  if (!this->eng_list.NeedRebuild()) return;
1248  switch (this->vehicle_type) {
1249  default: NOT_REACHED();
1250  case VEH_TRAIN:
1251  this->GenerateBuildTrainList();
1252  this->eng_list.Compact();
1253  this->eng_list.RebuildDone();
1254  return; // trains should not reach the last sorting
1255  case VEH_ROAD:
1256  this->GenerateBuildRoadVehList();
1257  break;
1258  case VEH_SHIP:
1259  this->GenerateBuildShipList();
1260  break;
1261  case VEH_AIRCRAFT:
1262  this->GenerateBuildAircraftList();
1263  break;
1264  }
1265 
1266  this->FilterEngineList();
1267 
1269  EngList_Sort(&this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]);
1270 
1271  this->eng_list.Compact();
1272  this->eng_list.RebuildDone();
1273  }
1274 
1275  void OnClick(Point pt, int widget, int click_count)
1276  {
1277  switch (widget) {
1279  this->descending_sort_order ^= true;
1281  this->eng_list.ForceRebuild();
1282  this->SetDirty();
1283  break;
1284 
1286  this->show_hidden_engines ^= true;
1288  this->eng_list.ForceRebuild();
1289  this->SetWidgetLoweredState(widget, this->show_hidden_engines);
1290  this->SetDirty();
1291  break;
1292 
1293  case WID_BV_LIST: {
1294  uint i = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_BV_LIST);
1295  size_t num_items = this->eng_list.Length();
1296  this->sel_engine = (i < num_items) ? this->eng_list[i] : INVALID_ENGINE;
1297  this->SetDirty();
1298  if (_ctrl_pressed) {
1299  this->OnClick(pt, WID_BV_SHOW_HIDE, 1);
1300  } else if (click_count > 1 && !this->listview_mode) {
1301  this->OnClick(pt, WID_BV_BUILD, 1);
1302  }
1303  break;
1304  }
1305 
1306  case WID_BV_SORT_DROPDOWN: // Select sorting criteria dropdown menu
1308  break;
1309 
1310  case WID_BV_CARGO_FILTER_DROPDOWN: // Select cargo filtering criteria dropdown menu
1312  break;
1313 
1314  case WID_BV_SHOW_HIDE: {
1315  const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine);
1316  if (e != NULL) {
1317  DoCommandP(0, 0, this->sel_engine | (e->IsHidden(_current_company) ? 0 : (1u << 31)), CMD_SET_VEHICLE_VISIBILITY);
1318  }
1319  break;
1320  }
1321 
1322  case WID_BV_BUILD: {
1323  EngineID sel_eng = this->sel_engine;
1324  if (sel_eng != INVALID_ENGINE) {
1325  CommandCallback *callback = (this->vehicle_type == VEH_TRAIN && RailVehInfo(sel_eng)->railveh_type == RAILVEH_WAGON) ? CcBuildWagon : CcBuildPrimaryVehicle;
1326  DoCommandP(this->window_number, sel_eng, 0, GetCmdBuildVeh(this->vehicle_type), callback);
1327  }
1328  break;
1329  }
1330 
1331  case WID_BV_RENAME: {
1332  EngineID sel_eng = this->sel_engine;
1333  if (sel_eng != INVALID_ENGINE) {
1334  this->rename_engine = sel_eng;
1335  SetDParam(0, sel_eng);
1336  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);
1337  }
1338  break;
1339  }
1340  }
1341  }
1342 
1348  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1349  {
1350  if (!gui_scope) return;
1351  /* When switching to original acceleration model for road vehicles, clear the selected sort criteria if it is not available now. */
1352  if (this->vehicle_type == VEH_ROAD &&
1354  this->sort_criteria > 7) {
1355  this->sort_criteria = 0;
1357  }
1358  this->eng_list.ForceRebuild();
1359  }
1360 
1361  virtual void SetStringParameters(int widget) const
1362  {
1363  switch (widget) {
1364  case WID_BV_CAPTION:
1365  if (this->vehicle_type == VEH_TRAIN && !this->listview_mode) {
1366  const RailtypeInfo *rti = GetRailTypeInfo(this->filter.railtype);
1367  SetDParam(0, rti->strings.build_caption);
1368  } else {
1369  SetDParam(0, (this->listview_mode ? STR_VEHICLE_LIST_AVAILABLE_TRAINS : STR_BUY_VEHICLE_TRAIN_ALL_CAPTION) + this->vehicle_type);
1370  }
1371  break;
1372 
1373  case WID_BV_SORT_DROPDOWN:
1375  break;
1376 
1379  break;
1380 
1381  case WID_BV_SHOW_HIDE: {
1382  const Engine *e = (this->sel_engine == INVALID_ENGINE) ? NULL : Engine::Get(this->sel_engine);
1383  if (e != NULL && e->IsHidden(_local_company)) {
1384  SetDParam(0, STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type);
1385  } else {
1386  SetDParam(0, STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1387  }
1388  break;
1389  }
1390  }
1391  }
1392 
1393  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1394  {
1395  switch (widget) {
1396  case WID_BV_LIST:
1397  resize->height = GetEngineListHeight(this->vehicle_type);
1398  size->height = 3 * resize->height;
1399  size->width = max(size->width, GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_left + GetVehicleImageCellSize(this->vehicle_type, EIT_PURCHASE).extend_right + 165);
1400  break;
1401 
1402  case WID_BV_PANEL:
1403  size->height = this->details_height;
1404  break;
1405 
1407  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1408  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1409  d.height += padding.height;
1410  *size = maxdim(*size, d);
1411  break;
1412  }
1413 
1414  case WID_BV_SHOW_HIDE:
1415  *size = GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_HIDE_TOGGLE_BUTTON + this->vehicle_type);
1416  *size = maxdim(*size, GetStringBoundingBox(STR_BUY_VEHICLE_TRAIN_SHOW_TOGGLE_BUTTON + this->vehicle_type));
1417  size->width += padding.width;
1418  size->height += padding.height;
1419  break;
1420  }
1421  }
1422 
1423  virtual void DrawWidget(const Rect &r, int widget) const
1424  {
1425  switch (widget) {
1426  case WID_BV_LIST:
1427  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);
1428  break;
1429 
1432  break;
1433  }
1434  }
1435 
1436  virtual void OnPaint()
1437  {
1438  this->GenerateBuildList();
1439  this->vscroll->SetCount(this->eng_list.Length());
1440 
1442 
1443  this->DrawWidgets();
1444 
1445  if (!this->IsShaded()) {
1446  int needed_height = this->details_height;
1447  /* Draw details panels. */
1448  if (this->sel_engine != INVALID_ENGINE) {
1449  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_BV_PANEL);
1451  nwi->pos_y + WD_FRAMERECT_TOP, this->sel_engine);
1452  needed_height = max(needed_height, text_end - (int)nwi->pos_y + WD_FRAMERECT_BOTTOM);
1453  }
1454  if (needed_height != this->details_height) { // Details window are not high enough, enlarge them.
1455  int resize = needed_height - this->details_height;
1456  this->details_height = needed_height;
1457  this->ReInit(0, resize);
1458  return;
1459  }
1460  }
1461  }
1462 
1463  virtual void OnQueryTextFinished(char *str)
1464  {
1465  if (str == NULL) return;
1466 
1467  DoCommandP(0, this->rename_engine, 0, CMD_RENAME_ENGINE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN_TYPE + this->vehicle_type), NULL, str);
1468  }
1469 
1470  virtual void OnDropdownSelect(int widget, int index)
1471  {
1472  switch (widget) {
1473  case WID_BV_SORT_DROPDOWN:
1474  if (this->sort_criteria != index) {
1475  this->sort_criteria = index;
1477  this->eng_list.ForceRebuild();
1478  }
1479  break;
1480 
1481  case WID_BV_CARGO_FILTER_DROPDOWN: // Select a cargo filter criteria
1482  if (this->cargo_filter_criteria != index) {
1483  this->cargo_filter_criteria = index;
1485  /* deactivate filter if criteria is 'Show All', activate it otherwise */
1486  this->eng_list.SetFilterState(this->cargo_filter[this->cargo_filter_criteria] != CF_ANY);
1487  this->eng_list.ForceRebuild();
1488  }
1489  break;
1490  }
1491  this->SetDirty();
1492  }
1493 
1494  virtual void OnResize()
1495  {
1496  this->vscroll->SetCapacityFromWidget(this, WID_BV_LIST);
1497  }
1498 };
1499 
1500 static WindowDesc _build_vehicle_desc(
1501  WDP_AUTO, "build_vehicle", 240, 268,
1504  _nested_build_vehicle_widgets, lengthof(_nested_build_vehicle_widgets)
1505 );
1506 
1507 void ShowBuildVehicleWindow(TileIndex tile, VehicleType type)
1508 {
1509  /* We want to be able to open both Available Train as Available Ships,
1510  * so if tile == INVALID_TILE (Available XXX Window), use 'type' as unique number.
1511  * As it always is a low value, it won't collide with any real tile
1512  * number. */
1513  uint num = (tile == INVALID_TILE) ? (int)type : tile;
1514 
1515  assert(IsCompanyBuildableVehicleType(type));
1516 
1518 
1519  new BuildVehicleWindow(&_build_vehicle_desc, tile, type);
1520 }