OpenTTD
vehicle_gui.cpp
Go to the documentation of this file.
1 /* $Id: vehicle_gui.cpp 27677 2016-11-05 19:16:59Z frosch $ */
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 "debug.h"
14 #include "company_func.h"
15 #include "gui.h"
16 #include "textbuf_gui.h"
17 #include "command_func.h"
18 #include "vehicle_gui_base.h"
19 #include "viewport_func.h"
20 #include "newgrf_text.h"
21 #include "newgrf_debug.h"
22 #include "roadveh.h"
23 #include "train.h"
24 #include "aircraft.h"
25 #include "depot_map.h"
26 #include "group_gui.h"
27 #include "strings_func.h"
28 #include "vehicle_func.h"
29 #include "autoreplace_gui.h"
30 #include "string_func.h"
31 #include "widgets/dropdown_func.h"
32 #include "timetable.h"
33 #include "articulated_vehicles.h"
34 #include "spritecache.h"
35 #include "core/geometry_func.hpp"
36 #include "company_base.h"
37 #include "engine_func.h"
38 #include "station_base.h"
39 #include "tilehighlight_func.h"
40 #include "zoom_func.h"
41 
42 #include "safeguards.h"
43 
44 
45 Sorting _sorting;
46 
60 
61 GUIVehicleList::SortFunction * const BaseVehicleListWindow::vehicle_sorter_funcs[] = {
75 };
76 
77 const StringID BaseVehicleListWindow::vehicle_sorter_names[] = {
78  STR_SORT_BY_NUMBER,
79  STR_SORT_BY_NAME,
80  STR_SORT_BY_AGE,
81  STR_SORT_BY_PROFIT_THIS_YEAR,
82  STR_SORT_BY_PROFIT_LAST_YEAR,
83  STR_SORT_BY_TOTAL_CAPACITY_PER_CARGOTYPE,
84  STR_SORT_BY_RELIABILITY,
85  STR_SORT_BY_MAX_SPEED,
86  STR_SORT_BY_MODEL,
87  STR_SORT_BY_VALUE,
88  STR_SORT_BY_LENGTH,
89  STR_SORT_BY_LIFE_TIME,
90  STR_SORT_BY_TIMETABLE_DELAY,
92 };
93 
94 const StringID BaseVehicleListWindow::vehicle_depot_name[] = {
95  STR_VEHICLE_LIST_SEND_TRAIN_TO_DEPOT,
96  STR_VEHICLE_LIST_SEND_ROAD_VEHICLE_TO_DEPOT,
97  STR_VEHICLE_LIST_SEND_SHIP_TO_DEPOT,
98  STR_VEHICLE_LIST_SEND_AIRCRAFT_TO_HANGAR
99 };
100 
107 {
108  uint unitnumber = 0;
109  for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
110  unitnumber = max<uint>(unitnumber, (*v)->unitnumber);
111  }
112 
113  if (unitnumber >= 10000) return 5;
114  if (unitnumber >= 1000) return 4;
115  if (unitnumber >= 100) return 3;
116 
117  /*
118  * When the smallest unit number is less than 10, it is
119  * quite likely that it will expand to become more than
120  * 10 quite soon.
121  */
122  return 2;
123 }
124 
125 void BaseVehicleListWindow::BuildVehicleList()
126 {
127  if (!this->vehicles.NeedRebuild()) return;
128 
129  DEBUG(misc, 3, "Building vehicle list type %d for company %d given index %d", this->vli.type, this->vli.company, this->vli.index);
130 
131  GenerateVehicleSortList(&this->vehicles, this->vli);
132 
134 
135  this->vehicles.RebuildDone();
136  this->vscroll->SetCount(this->vehicles.Length());
137 }
138 
145 Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bool show_group)
146 {
147  Dimension d = {0, 0};
148 
149  if (show_autoreplace) d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_REPLACE_VEHICLES));
150  d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_SEND_FOR_SERVICING));
151  d = maxdim(d, GetStringBoundingBox(this->vehicle_depot_name[this->vli.vtype]));
152 
153  if (show_group) {
154  d = maxdim(d, GetStringBoundingBox(STR_GROUP_ADD_SHARED_VEHICLE));
155  d = maxdim(d, GetStringBoundingBox(STR_GROUP_REMOVE_ALL_VEHICLES));
156  }
157 
158  return d;
159 }
160 
167 DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autoreplace, bool show_group)
168 {
169  DropDownList *list = new DropDownList();
170 
171  if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_REPLACE, false);
172  *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING, ADI_SERVICE, false);
173  *list->Append() = new DropDownListStringItem(this->vehicle_depot_name[this->vli.vtype], ADI_DEPOT, false);
174 
175  if (show_group) {
176  *list->Append() = new DropDownListStringItem(STR_GROUP_ADD_SHARED_VEHICLE, ADI_ADD_SHARED, false);
177  *list->Append() = new DropDownListStringItem(STR_GROUP_REMOVE_ALL_VEHICLES, ADI_REMOVE_ALL, false);
178  }
179 
180  return list;
181 }
182 
183 /* cached values for VehicleNameSorter to spare many GetString() calls */
184 static const Vehicle *_last_vehicle[2] = { NULL, NULL };
185 
186 void BaseVehicleListWindow::SortVehicleList()
187 {
188  if (this->vehicles.Sort()) return;
189 
190  /* invalidate cached values for name sorter - vehicle names could change */
191  _last_vehicle[0] = _last_vehicle[1] = NULL;
192 }
193 
194 void DepotSortList(VehicleList *list)
195 {
196  if (list->Length() < 2) return;
197  QSortT(list->Begin(), list->Length(), &VehicleNumberSorter);
198 }
199 
201 static void DrawVehicleProfitButton(const Vehicle *v, int x, int y)
202 {
203  SpriteID spr;
204 
205  /* draw profit-based coloured icons */
206  if (v->age <= VEHICLE_PROFIT_MIN_AGE) {
207  spr = SPR_PROFIT_NA;
208  } else if (v->GetDisplayProfitLastYear() < 0) {
209  spr = SPR_PROFIT_NEGATIVE;
211  spr = SPR_PROFIT_SOME;
212  } else {
213  spr = SPR_PROFIT_LOT;
214  }
215  DrawSprite(spr, PAL_NONE, x, y);
216 }
217 
219 static const uint MAX_REFIT_CYCLE = 256;
220 
230 byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type)
231 {
232  v_from = v_from->GetFirstEnginePart();
233  v_for = v_for->GetFirstEnginePart();
234 
235  /* Create a list of subtypes used by the various parts of v_for */
236  static SmallVector<StringID, 4> subtypes;
237  subtypes.Clear();
238  for (; v_from != NULL; v_from = v_from->HasArticulatedPart() ? v_from->GetNextArticulatedPart() : NULL) {
239  const Engine *e_from = v_from->GetEngine();
240  if (!e_from->CanCarryCargo() || !HasBit(e_from->info.callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) continue;
241  subtypes.Include(GetCargoSubtypeText(v_from));
242  }
243 
244  byte ret_refit_cyc = 0;
245  bool success = false;
246  if (subtypes.Length() > 0) {
247  /* Check whether any articulated part is refittable to 'dest_cargo_type' with a subtype listed in 'subtypes' */
248  for (Vehicle *v = v_for; v != NULL; v = v->HasArticulatedPart() ? v->GetNextArticulatedPart() : NULL) {
249  const Engine *e = v->GetEngine();
250  if (!e->CanCarryCargo() || !HasBit(e->info.callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) continue;
251  if (!HasBit(e->info.refit_mask, dest_cargo_type) && v->cargo_type != dest_cargo_type) continue;
252 
253  CargoID old_cargo_type = v->cargo_type;
254  byte old_cargo_subtype = v->cargo_subtype;
255 
256  /* Set the 'destination' cargo */
257  v->cargo_type = dest_cargo_type;
258 
259  /* Cycle through the refits */
260  for (uint refit_cyc = 0; refit_cyc < MAX_REFIT_CYCLE; refit_cyc++) {
261  v->cargo_subtype = refit_cyc;
262 
263  /* Make sure we don't pick up anything cached. */
264  v->First()->InvalidateNewGRFCache();
265  v->InvalidateNewGRFCache();
266 
267  StringID subtype = GetCargoSubtypeText(v);
268  if (subtype == STR_EMPTY) break;
269 
270  if (!subtypes.Contains(subtype)) continue;
271 
272  /* We found something matching. */
273  ret_refit_cyc = refit_cyc;
274  success = true;
275  break;
276  }
277 
278  /* Reset the vehicle's cargo type */
279  v->cargo_type = old_cargo_type;
280  v->cargo_subtype = old_cargo_subtype;
281 
282  /* Make sure we don't taint the vehicle. */
283  v->First()->InvalidateNewGRFCache();
284  v->InvalidateNewGRFCache();
285 
286  if (success) break;
287  }
288  }
289 
290  return ret_refit_cyc;
291 }
292 
294 struct RefitOption {
296  byte subtype;
298 
304  inline bool operator != (const RefitOption &other) const
305  {
306  return other.cargo != this->cargo || other.string != this->string;
307  }
308 
314  inline bool operator == (const RefitOption &other) const
315  {
316  return other.cargo == this->cargo && other.string == this->string;
317  }
318 };
319 
321 
331 static void DrawVehicleRefitWindow(const SubtypeList list[NUM_CARGO], const int sel[2], uint pos, uint rows, uint delta, const Rect &r)
332 {
333  uint y = r.top + WD_MATRIX_TOP;
334  uint current = 0;
335 
336  bool rtl = _current_text_dir == TD_RTL;
337  uint iconwidth = max(GetSpriteSize(SPR_CIRCLE_FOLDED).width, GetSpriteSize(SPR_CIRCLE_UNFOLDED).width);
338  uint iconheight = GetSpriteSize(SPR_CIRCLE_FOLDED).height;
339  int linecolour = _colour_gradient[COLOUR_ORANGE][4];
340 
341  int iconleft = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth : r.left + WD_MATRIX_LEFT;
342  int iconcenter = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth / 2 : r.left + WD_MATRIX_LEFT + iconwidth / 2;
343  int iconinner = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth : r.left + WD_MATRIX_LEFT + iconwidth;
344 
345  int textleft = r.left + WD_MATRIX_LEFT + (rtl ? 0 : iconwidth + 4);
346  int textright = r.right - WD_MATRIX_RIGHT - (rtl ? iconwidth + 4 : 0);
347 
348  /* Draw the list of subtypes for each cargo, and find the selected refit option (by its position). */
349  for (uint i = 0; current < pos + rows && i < NUM_CARGO; i++) {
350  for (uint j = 0; current < pos + rows && j < list[i].Length(); j++) {
351  const RefitOption &refit = list[i][j];
352 
353  /* Hide subtypes if sel[0] does not match */
354  if (sel[0] != (int)i && refit.subtype != 0xFF) continue;
355 
356  /* Refit options with a position smaller than pos don't have to be drawn. */
357  if (current < pos) {
358  current++;
359  continue;
360  }
361 
362  if (list[i].Length() > 1) {
363  if (refit.subtype != 0xFF) {
364  /* Draw tree lines */
365  int ycenter = y + FONT_HEIGHT_NORMAL / 2;
366  GfxDrawLine(iconcenter, y - WD_MATRIX_TOP, iconcenter, j == list[i].Length() - 1 ? ycenter : y - WD_MATRIX_TOP + delta - 1, linecolour);
367  GfxDrawLine(iconcenter, ycenter, iconinner, ycenter, linecolour);
368  } else {
369  /* Draw expand/collapse icon */
370  DrawSprite(sel[0] == (int)i ? SPR_CIRCLE_UNFOLDED : SPR_CIRCLE_FOLDED, PAL_NONE, iconleft, y + (FONT_HEIGHT_NORMAL - iconheight) / 2);
371  }
372  }
373 
374  TextColour colour = (sel[0] == (int)i && (uint)sel[1] == j) ? TC_WHITE : TC_BLACK;
375  /* Get the cargo name. */
376  SetDParam(0, CargoSpec::Get(refit.cargo)->name);
377  SetDParam(1, refit.string);
378  DrawString(textleft, textright, y, STR_JUST_STRING_STRING, colour);
379 
380  y += delta;
381  current++;
382  }
383  }
384 }
385 
387 struct RefitWindow : public Window {
388  int sel[2];
390  SubtypeList list[NUM_CARGO];
399  int click_x;
401  uint8 num_vehicles;
402  bool auto_refit;
403 
408  {
409  for (uint i = 0; i < NUM_CARGO; i++) this->list[i].Clear();
410  Vehicle *v = Vehicle::Get(this->window_number);
411 
412  /* Check only the selected vehicles. */
413  VehicleSet vehicles_to_refit;
414  GetVehicleSet(vehicles_to_refit, Vehicle::Get(this->selected_vehicle), this->num_vehicles);
415 
416  do {
417  if (v->type == VEH_TRAIN && !vehicles_to_refit.Contains(v->index)) continue;
418  const Engine *e = v->GetEngine();
419  uint32 cmask = e->info.refit_mask;
420  byte callback_mask = e->info.callback_mask;
421 
422  /* Skip this engine if it does not carry anything */
423  if (!e->CanCarryCargo()) continue;
424  /* Skip this engine if we build the list for auto-refitting and engine doesn't allow it. */
425  if (this->auto_refit && !HasBit(e->info.misc_flags, EF_AUTO_REFIT)) continue;
426 
427  /* Loop through all cargoes in the refit mask */
428  int current_index = 0;
429  const CargoSpec *cs;
431  CargoID cid = cs->Index();
432  /* Skip cargo type if it's not listed */
433  if (!HasBit(cmask, cid)) {
434  current_index++;
435  continue;
436  }
437 
438  bool first_vehicle = this->list[current_index].Length() == 0;
439  if (first_vehicle) {
440  /* Keeping the current subtype is always an option. It also serves as the option in case of no subtypes */
441  RefitOption *option = this->list[current_index].Append();
442  option->cargo = cid;
443  option->subtype = 0xFF;
444  option->string = STR_EMPTY;
445  }
446 
447  /* Check the vehicle's callback mask for cargo suffixes.
448  * This is not supported for ordered refits, since subtypes only have a meaning
449  * for a specific vehicle at a specific point in time, which conflicts with shared orders,
450  * autoreplace, autorenew, clone, order restoration, ... */
451  if (this->order == INVALID_VEH_ORDER_ID && HasBit(callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
452  /* Make a note of the original cargo type. It has to be
453  * changed to test the cargo & subtype... */
454  CargoID temp_cargo = v->cargo_type;
455  byte temp_subtype = v->cargo_subtype;
456 
457  v->cargo_type = cid;
458 
459  for (uint refit_cyc = 0; refit_cyc < MAX_REFIT_CYCLE; refit_cyc++) {
460  v->cargo_subtype = refit_cyc;
461 
462  /* Make sure we don't pick up anything cached. */
465 
466  StringID subtype = GetCargoSubtypeText(v);
467 
468  if (first_vehicle) {
469  /* Append new subtype (don't add duplicates though) */
470  if (subtype == STR_EMPTY) break;
471 
472  RefitOption option;
473  option.cargo = cid;
474  option.subtype = refit_cyc;
475  option.string = subtype;
476  this->list[current_index].Include(option);
477  } else {
478  /* Intersect the subtypes of earlier vehicles with the subtypes of this vehicle */
479  if (subtype == STR_EMPTY) {
480  /* No more subtypes for this vehicle, delete all subtypes >= refit_cyc */
481  SubtypeList &l = this->list[current_index];
482  /* 0xFF item is in front, other subtypes are sorted. So just truncate the list in the right spot */
483  for (uint i = 1; i < l.Length(); i++) {
484  if (l[i].subtype >= refit_cyc) {
485  l.Resize(i);
486  break;
487  }
488  }
489  break;
490  } else {
491  /* Check whether the subtype matches with the subtype of earlier vehicles. */
492  uint pos = 1;
493  SubtypeList &l = this->list[current_index];
494  while (pos < l.Length() && l[pos].subtype != refit_cyc) pos++;
495  if (pos < l.Length() && l[pos].string != subtype) {
496  /* String mismatch, remove item keeping the order */
497  l.ErasePreservingOrder(pos);
498  }
499  }
500  }
501  }
502 
503  /* Reset the vehicle's cargo type */
504  v->cargo_type = temp_cargo;
505  v->cargo_subtype = temp_subtype;
506 
507  /* And make sure we haven't tainted the cache */
510  }
511  current_index++;
512  }
513  } while (v->IsGroundVehicle() && (v = v->Next()) != NULL);
514  }
515 
520  {
521  uint scroll_row = 0;
522  uint row = 0;
523 
524  for (uint i = 0; i < NUM_CARGO; i++) {
525  for (uint j = 0; j < this->list[i].Length(); j++) {
526  const RefitOption &refit = this->list[i][j];
527 
528  /* Hide subtypes if sel[0] does not match */
529  if (this->sel[0] != (int)i && refit.subtype != 0xFF) continue;
530 
531  if (this->sel[0] == (int)i && (uint)this->sel[1] == j) scroll_row = row;
532 
533  row++;
534  }
535  }
536 
537  this->vscroll->SetCount(row);
538  if (scroll_row < row) this->vscroll->ScrollTowards(scroll_row);
539  }
540 
545  void SetSelection(uint click_row)
546  {
547  uint row = 0;
548 
549  for (uint i = 0; i < NUM_CARGO; i++) {
550  for (uint j = 0; j < this->list[i].Length(); j++) {
551  const RefitOption &refit = this->list[i][j];
552 
553  /* Hide subtypes if sel[0] does not match */
554  if (this->sel[0] != (int)i && refit.subtype != 0xFF) continue;
555 
556  if (row == click_row) {
557  this->sel[0] = i;
558  this->sel[1] = j;
559  return;
560  }
561 
562  row++;
563  }
564  }
565 
566  this->sel[0] = -1;
567  this->sel[1] = 0;
568  }
569 
575  {
576  if (this->sel[0] < 0) return NULL;
577 
578  SubtypeList &l = this->list[this->sel[0]];
579  if ((uint)this->sel[1] >= l.Length()) return NULL;
580 
581  return &l[this->sel[1]];
582  }
583 
584  RefitWindow(WindowDesc *desc, const Vehicle *v, VehicleOrderID order, bool auto_refit) : Window(desc)
585  {
586  this->sel[0] = -1;
587  this->sel[1] = 0;
588  this->auto_refit = auto_refit;
589  this->order = order;
590  this->CreateNestedTree();
591 
592  this->vscroll = this->GetScrollbar(WID_VR_SCROLLBAR);
593  this->hscroll = (v->IsGroundVehicle() ? this->GetScrollbar(WID_VR_HSCROLLBAR) : NULL);
594  this->GetWidget<NWidgetCore>(WID_VR_SELECT_HEADER)->tool_tip = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
595  this->GetWidget<NWidgetCore>(WID_VR_MATRIX)->tool_tip = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
596  NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_VR_REFIT);
597  nwi->widget_data = STR_REFIT_TRAIN_REFIT_BUTTON + v->type;
598  nwi->tool_tip = STR_REFIT_TRAIN_REFIT_TOOLTIP + v->type;
599  this->GetWidget<NWidgetStacked>(WID_VR_SHOW_HSCROLLBAR)->SetDisplayedPlane(v->IsGroundVehicle() ? 0 : SZSP_HORIZONTAL);
600  this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY)->tool_tip = (v->type == VEH_TRAIN) ? STR_REFIT_SELECT_VEHICLES_TOOLTIP : STR_NULL;
601 
602  this->FinishInitNested(v->index);
603  this->owner = v->owner;
604 
605  this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0);
606  }
607 
608  virtual void OnInit()
609  {
610  if (this->cargo != NULL) {
611  /* Store the RefitOption currently in use. */
612  RefitOption current_refit_option = *(this->cargo);
613 
614  /* Rebuild the refit list */
615  this->BuildRefitList();
616  this->sel[0] = -1;
617  this->sel[1] = 0;
618  this->cargo = NULL;
619  for (uint i = 0; this->cargo == NULL && i < NUM_CARGO; i++) {
620  for (uint j = 0; j < list[i].Length(); j++) {
621  if (list[i][j] == current_refit_option) {
622  this->sel[0] = i;
623  this->sel[1] = j;
624  this->cargo = &list[i][j];
625  break;
626  }
627  }
628  }
629 
630  this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0);
631  this->RefreshScrollbar();
632  } else {
633  /* Rebuild the refit list */
635  }
636  }
637 
638  virtual void OnPaint()
639  {
640  /* Determine amount of items for scroller. */
641  if (this->hscroll != NULL) this->hscroll->SetCount(this->vehicle_width);
642 
643  /* Calculate sprite position. */
644  NWidgetCore *vehicle_panel_display = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY);
645  int sprite_width = max(0, ((int)vehicle_panel_display->current_x - this->vehicle_width) / 2);
646  this->sprite_left = vehicle_panel_display->pos_x;
647  this->sprite_right = vehicle_panel_display->pos_x + vehicle_panel_display->current_x - 1;
648  if (_current_text_dir == TD_RTL) {
649  this->sprite_right -= sprite_width;
650  this->vehicle_margin = vehicle_panel_display->current_x - sprite_right;
651  } else {
652  this->sprite_left += sprite_width;
653  this->vehicle_margin = sprite_left;
654  }
655 
656  this->DrawWidgets();
657  }
658 
659  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
660  {
661  switch (widget) {
662  case WID_VR_MATRIX:
664  size->height = resize->height * 8;
665  break;
666 
668  size->height = ScaleGUITrad(GetVehicleHeight(Vehicle::Get(this->window_number)->type));
669  break;
670 
671  case WID_VR_INFO:
673  break;
674  }
675  }
676 
677  virtual void SetStringParameters(int widget) const
678  {
679  if (widget == WID_VR_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
680  }
681 
689  {
690  assert(_current_company == _local_company);
691  Vehicle *v = Vehicle::Get(this->window_number);
692  CommandCost cost = DoCommand(v->tile, this->selected_vehicle, option->cargo | (int)this->auto_refit << 6 | option->subtype << 8 |
693  this->num_vehicles << 16, DC_QUERY_COST, GetCmdRefitVeh(v->type));
694 
695  if (cost.Failed()) return INVALID_STRING_ID;
696 
697  SetDParam(0, option->cargo);
699 
700  Money money = cost.GetCost();
702  SetDParam(2, CT_MAIL);
704  if (this->order != INVALID_VEH_ORDER_ID) {
705  /* No predictable cost */
706  return STR_PURCHASE_INFO_AIRCRAFT_CAPACITY;
707  } else if (money <= 0) {
708  SetDParam(4, -money);
709  return STR_REFIT_NEW_CAPACITY_INCOME_FROM_AIRCRAFT_REFIT;
710  } else {
711  SetDParam(4, money);
712  return STR_REFIT_NEW_CAPACITY_COST_OF_AIRCRAFT_REFIT;
713  }
714  } else {
715  if (this->order != INVALID_VEH_ORDER_ID) {
716  /* No predictable cost */
717  SetDParam(2, STR_EMPTY);
718  return STR_PURCHASE_INFO_CAPACITY;
719  } else if (money <= 0) {
720  SetDParam(2, -money);
721  return STR_REFIT_NEW_CAPACITY_INCOME_FROM_REFIT;
722  } else {
723  SetDParam(2, money);
724  return STR_REFIT_NEW_CAPACITY_COST_OF_REFIT;
725  }
726  }
727  }
728 
729  virtual void DrawWidget(const Rect &r, int widget) const
730  {
731  switch (widget) {
733  Vehicle *v = Vehicle::Get(this->window_number);
735  r.top + WD_FRAMERECT_TOP, INVALID_VEHICLE, EIT_IN_DETAILS, this->hscroll != NULL ? this->hscroll->GetPosition() : 0);
736 
737  /* Highlight selected vehicles. */
738  if (this->order != INVALID_VEH_ORDER_ID) break;
739  int x = 0;
740  switch (v->type) {
741  case VEH_TRAIN: {
742  VehicleSet vehicles_to_refit;
743  GetVehicleSet(vehicles_to_refit, Vehicle::Get(this->selected_vehicle), this->num_vehicles);
744 
745  int left = INT32_MIN;
746  int width = 0;
747 
748  for (Train *u = Train::From(v); u != NULL; u = u->Next()) {
749  /* Start checking. */
750  if (vehicles_to_refit.Contains(u->index) && left == INT32_MIN) {
751  left = x - this->hscroll->GetPosition() + r.left + this->vehicle_margin;
752  width = 0;
753  }
754 
755  /* Draw a selection. */
756  if ((!vehicles_to_refit.Contains(u->index) || u->Next() == NULL) && left != INT32_MIN) {
757  if (u->Next() == NULL && vehicles_to_refit.Contains(u->index)) {
758  int current_width = u->GetDisplayImageWidth();
759  width += current_width;
760  x += current_width;
761  }
762 
763  int right = Clamp(left + width, 0, r.right);
764  left = max(0, left);
765 
766  if (_current_text_dir == TD_RTL) {
767  right = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY)->current_x - left;
768  left = right - width;
769  }
770 
771  if (left != right) {
772  DrawFrameRect(left, r.top + WD_FRAMERECT_TOP, right, r.top + WD_FRAMERECT_TOP + ScaleGUITrad(14) - 1, COLOUR_WHITE, FR_BORDERONLY);
773  }
774 
775  left = INT32_MIN;
776  }
777 
778  int current_width = u->GetDisplayImageWidth();
779  width += current_width;
780  x += current_width;
781  }
782  break;
783  }
784 
785  default: break;
786  }
787  break;
788  }
789 
790  case WID_VR_MATRIX:
791  DrawVehicleRefitWindow(this->list, this->sel, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->resize.step_height, r);
792  break;
793 
794  case WID_VR_INFO:
795  if (this->cargo != NULL) {
796  StringID string = this->GetCapacityString(this->cargo);
797  if (string != INVALID_STRING_ID) {
799  r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, string);
800  }
801  }
802  break;
803  }
804  }
805 
811  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
812  {
813  switch (data) {
814  case VIWD_AUTOREPLACE: // Autoreplace replaced the vehicle; selected_vehicle became invalid.
815  case VIWD_CONSIST_CHANGED: { // The consist has changed; rebuild the entire list.
816  /* Clear the selection. */
817  Vehicle *v = Vehicle::Get(this->window_number);
818  this->selected_vehicle = v->index;
819  this->num_vehicles = UINT8_MAX;
820  /* FALL THROUGH */
821  }
822 
823  case 2: { // The vehicle selection has changed; rebuild the entire list.
824  if (!gui_scope) break;
825  this->BuildRefitList();
826 
827  /* The vehicle width has changed too. */
829  uint max_width = 0;
830 
831  /* Check the width of all cargo information strings. */
832  for (uint i = 0; i < NUM_CARGO; i++) {
833  for (uint j = 0; j < this->list[i].Length(); j++) {
834  StringID string = this->GetCapacityString(&list[i][j]);
835  if (string != INVALID_STRING_ID) {
836  Dimension dim = GetStringBoundingBox(string);
837  max_width = max(dim.width, max_width);
838  }
839  }
840  }
841 
842  if (this->information_width < max_width) {
843  this->information_width = max_width;
844  this->ReInit();
845  }
846  /* FALL THROUGH */
847  }
848 
849  case 1: // A new cargo has been selected.
850  if (!gui_scope) break;
851  this->cargo = GetRefitOption();
852  this->RefreshScrollbar();
853  break;
854  }
855  }
856 
857  int GetClickPosition(int click_x)
858  {
859  const NWidgetCore *matrix_widget = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY);
860  if (_current_text_dir == TD_RTL) click_x = matrix_widget->current_x - click_x;
861  click_x -= this->vehicle_margin;
862  if (this->hscroll != NULL) click_x += this->hscroll->GetPosition();
863 
864  return click_x;
865  }
866 
867  void SetSelectedVehicles(int drag_x)
868  {
869  drag_x = GetClickPosition(drag_x);
870 
871  int left_x = min(this->click_x, drag_x);
872  int right_x = max(this->click_x, drag_x);
873  this->num_vehicles = 0;
874 
875  Vehicle *v = Vehicle::Get(this->window_number);
876  /* Find the vehicle part that was clicked. */
877  switch (v->type) {
878  case VEH_TRAIN: {
879  /* Don't select anything if we are not clicking in the vehicle. */
880  if (left_x >= 0) {
881  const Train *u = Train::From(v);
882  bool start_counting = false;
883  for (; u != NULL; u = u->Next()) {
884  int current_width = u->GetDisplayImageWidth();
885  left_x -= current_width;
886  right_x -= current_width;
887 
888  if (left_x < 0 && !start_counting) {
889  this->selected_vehicle = u->index;
890  start_counting = true;
891 
892  /* Count the first vehicle, even if articulated part */
893  this->num_vehicles++;
894  } else if (start_counting && !u->IsArticulatedPart()) {
895  /* Do not count articulated parts */
896  this->num_vehicles++;
897  }
898 
899  if (right_x < 0) break;
900  }
901  }
902 
903  /* If the selection is not correct, clear it. */
904  if (this->num_vehicles != 0) {
905  if (_ctrl_pressed) this->num_vehicles = UINT8_MAX;
906  break;
907  }
908  /* FALL THROUGH */
909  }
910 
911  default:
912  /* Clear the selection. */
913  this->selected_vehicle = v->index;
914  this->num_vehicles = UINT8_MAX;
915  break;
916  }
917  }
918 
919  virtual void OnClick(Point pt, int widget, int click_count)
920  {
921  switch (widget) {
922  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
923  if (this->order != INVALID_VEH_ORDER_ID) break;
924  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
925  this->click_x = GetClickPosition(pt.x - nwi->pos_x);
926  this->SetSelectedVehicles(pt.x - nwi->pos_x);
928  if (!_ctrl_pressed) {
929  SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
930  } else {
931  /* The vehicle selection has changed. */
932  this->InvalidateData(2);
933  }
934  break;
935  }
936 
937  case WID_VR_MATRIX: { // listbox
938  this->SetSelection(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VR_MATRIX));
939  this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0);
940  this->InvalidateData(1);
941 
942  if (click_count == 1) break;
943  /* FALL THROUGH */
944  }
945 
946  case WID_VR_REFIT: // refit button
947  if (this->cargo != NULL) {
948  const Vehicle *v = Vehicle::Get(this->window_number);
949 
950  if (this->order == INVALID_VEH_ORDER_ID) {
951  bool delete_window = this->selected_vehicle == v->index && this->num_vehicles == UINT8_MAX;
952  if (DoCommandP(v->tile, this->selected_vehicle, this->cargo->cargo | this->cargo->subtype << 8 | this->num_vehicles << 16, GetCmdRefitVeh(v)) && delete_window) delete this;
953  } else {
954  if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->order << 16, CMD_ORDER_REFIT)) delete this;
955  }
956  }
957  break;
958  }
959  }
960 
961  virtual void OnMouseDrag(Point pt, int widget)
962  {
963  switch (widget) {
964  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
965  if (this->order != INVALID_VEH_ORDER_ID) break;
966  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
967  this->SetSelectedVehicles(pt.x - nwi->pos_x);
969  break;
970  }
971  }
972  }
973 
974  virtual void OnDragDrop(Point pt, int widget)
975  {
976  switch (widget) {
977  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
978  if (this->order != INVALID_VEH_ORDER_ID) break;
979  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
980  this->SetSelectedVehicles(pt.x - nwi->pos_x);
981  this->InvalidateData(2);
982  break;
983  }
984  }
985  }
986 
987  virtual void OnResize()
988  {
991  if (this->hscroll != NULL) this->hscroll->SetCapacityFromWidget(this, WID_VR_VEHICLE_PANEL_DISPLAY);
992  }
993 };
994 
995 static const NWidgetPart _nested_vehicle_refit_widgets[] = {
997  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
998  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VR_CAPTION), SetDataTip(STR_REFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
999  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1000  EndContainer(),
1001  /* Vehicle display + scrollbar. */
1004  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VR_SHOW_HSCROLLBAR),
1005  NWidget(NWID_HSCROLLBAR, COLOUR_GREY, WID_VR_HSCROLLBAR),
1006  EndContainer(),
1007  EndContainer(),
1008  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_VR_SELECT_HEADER), SetDataTip(STR_REFIT_TITLE, STR_NULL), SetResize(1, 0),
1009  /* Matrix + scrollbar. */
1011  NWidget(WWT_MATRIX, COLOUR_GREY, WID_VR_MATRIX), SetMinimalSize(228, 112), SetResize(1, 14), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_VR_SCROLLBAR),
1012  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VR_SCROLLBAR),
1013  EndContainer(),
1016  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VR_REFIT), SetFill(1, 0), SetResize(1, 0),
1017  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1018  EndContainer(),
1019 };
1020 
1021 static WindowDesc _vehicle_refit_desc(
1022  WDP_AUTO, "view_vehicle_refit", 240, 174,
1025  _nested_vehicle_refit_widgets, lengthof(_nested_vehicle_refit_widgets)
1026 );
1027 
1035 void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit)
1036 {
1038  RefitWindow *w = new RefitWindow(&_vehicle_refit_desc, v, order, auto_refit);
1039  w->parent = parent;
1040 }
1041 
1043 uint ShowRefitOptionsList(int left, int right, int y, EngineID engine)
1044 {
1045  /* List of cargo types of this engine */
1046  uint32 cmask = GetUnionOfArticulatedRefitMasks(engine, false);
1047  /* List of cargo types available in this climate */
1048  uint32 lmask = _cargo_mask;
1049 
1050  /* Draw nothing if the engine is not refittable */
1051  if (HasAtMostOneBit(cmask)) return y;
1052 
1053  if (cmask == lmask) {
1054  /* Engine can be refitted to all types in this climate */
1055  SetDParam(0, STR_PURCHASE_INFO_ALL_TYPES);
1056  } else {
1057  /* Check if we are able to refit to more cargo types and unable to. If
1058  * so, invert the cargo types to list those that we can't refit to. */
1059  if (CountBits(cmask ^ lmask) < CountBits(cmask) && CountBits(cmask ^ lmask) <= 7) {
1060  cmask ^= lmask;
1061  SetDParam(0, STR_PURCHASE_INFO_ALL_BUT);
1062  } else {
1063  SetDParam(0, STR_JUST_CARGO_LIST);
1064  }
1065  SetDParam(1, cmask);
1066  }
1067 
1068  return DrawStringMultiLine(left, right, y, INT32_MAX, STR_PURCHASE_INFO_REFITTABLE_TO);
1069 }
1070 
1073 {
1074  if (HasBit(EngInfo(v->engine_type)->callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
1075  uint16 cb = GetVehicleCallback(CBID_VEHICLE_CARGO_SUFFIX, 0, 0, v->engine_type, v);
1076  if (cb != CALLBACK_FAILED) {
1078  if (cb >= 0x400 || (v->GetGRF()->grf_version < 8 && cb == 0xFF)) cb = CALLBACK_FAILED;
1079  }
1080  if (cb != CALLBACK_FAILED) {
1081  return GetGRFStringID(v->GetGRFID(), 0xD000 + cb);
1082  }
1083  }
1084  return STR_EMPTY;
1085 }
1086 
1088 static int CDECL VehicleNumberSorter(const Vehicle * const *a, const Vehicle * const *b)
1089 {
1090  return (*a)->unitnumber - (*b)->unitnumber;
1091 }
1092 
1094 static int CDECL VehicleNameSorter(const Vehicle * const *a, const Vehicle * const *b)
1095 {
1096  static char last_name[2][64];
1097 
1098  if (*a != _last_vehicle[0]) {
1099  _last_vehicle[0] = *a;
1100  SetDParam(0, (*a)->index);
1101  GetString(last_name[0], STR_VEHICLE_NAME, lastof(last_name[0]));
1102  }
1103 
1104  if (*b != _last_vehicle[1]) {
1105  _last_vehicle[1] = *b;
1106  SetDParam(0, (*b)->index);
1107  GetString(last_name[1], STR_VEHICLE_NAME, lastof(last_name[1]));
1108  }
1109 
1110  int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
1111  return (r != 0) ? r : VehicleNumberSorter(a, b);
1112 }
1113 
1115 static int CDECL VehicleAgeSorter(const Vehicle * const *a, const Vehicle * const *b)
1116 {
1117  int r = (*a)->age - (*b)->age;
1118  return (r != 0) ? r : VehicleNumberSorter(a, b);
1119 }
1120 
1122 static int CDECL VehicleProfitThisYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1123 {
1124  int r = ClampToI32((*a)->GetDisplayProfitThisYear() - (*b)->GetDisplayProfitThisYear());
1125  return (r != 0) ? r : VehicleNumberSorter(a, b);
1126 }
1127 
1129 static int CDECL VehicleProfitLastYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1130 {
1131  int r = ClampToI32((*a)->GetDisplayProfitLastYear() - (*b)->GetDisplayProfitLastYear());
1132  return (r != 0) ? r : VehicleNumberSorter(a, b);
1133 }
1134 
1136 static int CDECL VehicleCargoSorter(const Vehicle * const *a, const Vehicle * const *b)
1137 {
1138  const Vehicle *v;
1139  CargoArray diff;
1140 
1141  /* Append the cargo of the connected waggons */
1142  for (v = *a; v != NULL; v = v->Next()) diff[v->cargo_type] += v->cargo_cap;
1143  for (v = *b; v != NULL; v = v->Next()) diff[v->cargo_type] -= v->cargo_cap;
1144 
1145  int r = 0;
1146  for (CargoID i = 0; i < NUM_CARGO; i++) {
1147  r = diff[i];
1148  if (r != 0) break;
1149  }
1150 
1151  return (r != 0) ? r : VehicleNumberSorter(a, b);
1152 }
1153 
1155 static int CDECL VehicleReliabilitySorter(const Vehicle * const *a, const Vehicle * const *b)
1156 {
1157  int r = (*a)->reliability - (*b)->reliability;
1158  return (r != 0) ? r : VehicleNumberSorter(a, b);
1159 }
1160 
1162 static int CDECL VehicleMaxSpeedSorter(const Vehicle * const *a, const Vehicle * const *b)
1163 {
1164  int r = (*a)->vcache.cached_max_speed - (*b)->vcache.cached_max_speed;
1165  return (r != 0) ? r : VehicleNumberSorter(a, b);
1166 }
1167 
1169 static int CDECL VehicleModelSorter(const Vehicle * const *a, const Vehicle * const *b)
1170 {
1171  int r = (*a)->engine_type - (*b)->engine_type;
1172  return (r != 0) ? r : VehicleNumberSorter(a, b);
1173 }
1174 
1176 static int CDECL VehicleValueSorter(const Vehicle * const *a, const Vehicle * const *b)
1177 {
1178  const Vehicle *u;
1179  Money diff = 0;
1180 
1181  for (u = *a; u != NULL; u = u->Next()) diff += u->value;
1182  for (u = *b; u != NULL; u = u->Next()) diff -= u->value;
1183 
1184  int r = ClampToI32(diff);
1185  return (r != 0) ? r : VehicleNumberSorter(a, b);
1186 }
1187 
1189 static int CDECL VehicleLengthSorter(const Vehicle * const *a, const Vehicle * const *b)
1190 {
1191  int r = (*a)->GetGroundVehicleCache()->cached_total_length - (*b)->GetGroundVehicleCache()->cached_total_length;
1192  return (r != 0) ? r : VehicleNumberSorter(a, b);
1193 }
1194 
1196 static int CDECL VehicleTimeToLiveSorter(const Vehicle * const *a, const Vehicle * const *b)
1197 {
1198  int r = ClampToI32(((*a)->max_age - (*a)->age) - ((*b)->max_age - (*b)->age));
1199  return (r != 0) ? r : VehicleNumberSorter(a, b);
1200 }
1201 
1203 static int CDECL VehicleTimetableDelaySorter(const Vehicle * const *a, const Vehicle * const *b)
1204 {
1205  int r = (*a)->lateness_counter - (*b)->lateness_counter;
1206  return (r != 0) ? r : VehicleNumberSorter(a, b);
1207 }
1208 
1209 void InitializeGUI()
1210 {
1211  MemSetT(&_sorting, 0);
1212 }
1213 
1220 static inline void ChangeVehicleWindow(WindowClass window_class, VehicleID from_index, VehicleID to_index)
1221 {
1222  Window *w = FindWindowById(window_class, from_index);
1223  if (w != NULL) {
1224  /* Update window_number */
1225  w->window_number = to_index;
1226  if (w->viewport != NULL) w->viewport->follow_vehicle = to_index;
1227 
1228  /* Update vehicle drag data */
1229  if (_thd.window_class == window_class && _thd.window_number == (WindowNumber)from_index) {
1230  _thd.window_number = to_index;
1231  }
1232 
1233  /* Notify the window. */
1234  w->InvalidateData(VIWD_AUTOREPLACE, false);
1235  }
1236 }
1237 
1243 void ChangeVehicleViewWindow(VehicleID from_index, VehicleID to_index)
1244 {
1245  ChangeVehicleWindow(WC_VEHICLE_VIEW, from_index, to_index);
1246  ChangeVehicleWindow(WC_VEHICLE_ORDERS, from_index, to_index);
1247  ChangeVehicleWindow(WC_VEHICLE_REFIT, from_index, to_index);
1248  ChangeVehicleWindow(WC_VEHICLE_DETAILS, from_index, to_index);
1249  ChangeVehicleWindow(WC_VEHICLE_TIMETABLE, from_index, to_index);
1250 }
1251 
1252 static const NWidgetPart _nested_vehicle_list[] = {
1254  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1255  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VL_CAPTION),
1256  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1257  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1258  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1259  EndContainer(),
1260 
1262  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_SORT_ORDER), SetMinimalSize(81, 12), SetFill(0, 1), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1263  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
1264  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetFill(1, 1), SetResize(1, 0),
1265  EndContainer(),
1266  EndContainer(),
1267 
1269  NWidget(WWT_MATRIX, COLOUR_GREY, WID_VL_LIST), SetMinimalSize(248, 0), SetFill(1, 0), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_VL_SCROLLBAR),
1270  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VL_SCROLLBAR),
1271  EndContainer(),
1272 
1274  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VL_HIDE_BUTTONS),
1276  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
1277  SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
1278  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
1280  SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
1281  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1282  SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
1283  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1284  SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
1285  EndContainer(),
1286  /* Widget to be shown for other companies hiding the previous 5 widgets. */
1287  NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1288  EndContainer(),
1289  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1290  EndContainer(),
1291 };
1292 
1293 static void DrawSmallOrderList(const Vehicle *v, int left, int right, int y, VehicleOrderID start = 0)
1294 {
1295  const Order *order = v->GetOrder(start);
1296  if (order == NULL) return;
1297 
1298  bool rtl = _current_text_dir == TD_RTL;
1299  int l_offset = rtl ? 0 : ScaleGUITrad(6);
1300  int r_offset = rtl ? ScaleGUITrad(6) : 0;
1301  int i = 0;
1302  VehicleOrderID oid = start;
1303 
1304  do {
1305  if (oid == v->cur_real_order_index) DrawString(left, right, y, STR_TINY_RIGHT_ARROW, TC_BLACK);
1306 
1307  if (order->IsType(OT_GOTO_STATION)) {
1308  SetDParam(0, order->GetDestination());
1309  DrawString(left + l_offset, right - r_offset, y, STR_TINY_BLACK_STATION);
1310 
1311  y += FONT_HEIGHT_SMALL;
1312  if (++i == 4) break;
1313  }
1314 
1315  oid++;
1316  order = order->next;
1317  if (order == NULL) {
1318  order = v->orders.list->GetFirstOrder();
1319  oid = 0;
1320  }
1321  } while (oid != start);
1322 }
1323 
1333 void DrawVehicleImage(const Vehicle *v, int left, int right, int y, VehicleID selection, EngineImageType image_type, int skip)
1334 {
1335  switch (v->type) {
1336  case VEH_TRAIN: DrawTrainImage(Train::From(v), left, right, y, selection, image_type, skip); break;
1337  case VEH_ROAD: DrawRoadVehImage(v, left, right, y, selection, image_type, skip); break;
1338  case VEH_SHIP: DrawShipImage(v, left, right, y, selection, image_type); break;
1339  case VEH_AIRCRAFT: DrawAircraftImage(v, left, right, y, selection, image_type); break;
1340  default: NOT_REACHED();
1341  }
1342 }
1343 
1350 uint GetVehicleListHeight(VehicleType type, uint divisor)
1351 {
1352  /* Name + vehicle + profit */
1353  uint base = ScaleGUITrad(GetVehicleHeight(type)) + 2 * FONT_HEIGHT_SMALL;
1354  /* Drawing of the 4 small orders + profit*/
1355  if (type >= VEH_SHIP) base = max(base, 5U * FONT_HEIGHT_SMALL);
1356 
1357  if (divisor == 1) return base;
1358 
1359  /* Make sure the height is dividable by divisor */
1360  uint rem = base % divisor;
1361  return base + (rem == 0 ? 0 : divisor - rem);
1362 }
1363 
1370 void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int line_height, const Rect &r) const
1371 {
1372  int left = r.left + WD_MATRIX_LEFT;
1373  int right = r.right - WD_MATRIX_RIGHT;
1374  int width = right - left;
1375  bool rtl = _current_text_dir == TD_RTL;
1376 
1377  int text_offset = max<int>(GetSpriteSize(SPR_PROFIT_LOT).width, GetDigitWidth() * this->unitnumber_digits) + WD_FRAMERECT_RIGHT;
1378  int text_left = left + (rtl ? 0 : text_offset);
1379  int text_right = right - (rtl ? text_offset : 0);
1380 
1381  bool show_orderlist = this->vli.vtype >= VEH_SHIP;
1382  int orderlist_left = left + (rtl ? 0 : max(ScaleGUITrad(100) + text_offset, width / 2));
1383  int orderlist_right = right - (rtl ? max(ScaleGUITrad(100) + text_offset, width / 2) : 0);
1384 
1385  int image_left = (rtl && show_orderlist) ? orderlist_right : text_left;
1386  int image_right = (!rtl && show_orderlist) ? orderlist_left : text_right;
1387 
1388  int vehicle_button_x = rtl ? right - GetSpriteSize(SPR_PROFIT_LOT).width : left;
1389 
1390  int y = r.top;
1391  uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
1392  for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
1393  const Vehicle *v = this->vehicles[i];
1394  StringID str;
1395 
1398 
1399  DrawVehicleImage(v, image_left, image_right, y + FONT_HEIGHT_SMALL - 1, selected_vehicle, EIT_IN_LIST, 0);
1400  DrawString(text_left, text_right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1, STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR);
1401 
1402  if (v->name != NULL) {
1403  /* The vehicle got a name so we will print it */
1404  SetDParam(0, v->index);
1405  DrawString(text_left, text_right, y, STR_TINY_BLACK_VEHICLE);
1406  } else if (v->group_id != DEFAULT_GROUP) {
1407  /* The vehicle has no name, but is member of a group, so print group name */
1408  SetDParam(0, v->group_id);
1409  DrawString(text_left, text_right, y, STR_TINY_GROUP, TC_BLACK);
1410  }
1411 
1412  if (show_orderlist) DrawSmallOrderList(v, orderlist_left, orderlist_right, y, v->cur_real_order_index);
1413 
1414  if (v->IsChainInDepot()) {
1415  str = STR_BLUE_COMMA;
1416  } else {
1417  str = (v->age > v->max_age - DAYS_IN_LEAP_YEAR) ? STR_RED_COMMA : STR_BLACK_COMMA;
1418  }
1419 
1420  SetDParam(0, v->unitnumber);
1421  DrawString(left, right, y + 2, str);
1422 
1423  DrawVehicleProfitButton(v, vehicle_button_x, y + FONT_HEIGHT_NORMAL + 3);
1424 
1425  y += line_height;
1426  }
1427 }
1428 
1439 private:
1444  };
1445 
1446 public:
1448  {
1449  /* Set up sorting. Make the window-specific _sorting variable
1450  * point to the correct global _sorting struct so we are freed
1451  * from having conditionals during window operation */
1452  switch (this->vli.vtype) {
1453  case VEH_TRAIN: this->sorting = &_sorting.train; break;
1454  case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
1455  case VEH_SHIP: this->sorting = &_sorting.ship; break;
1456  case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
1457  default: NOT_REACHED();
1458  }
1459 
1460  this->CreateNestedTree();
1461 
1462  this->vscroll = this->GetScrollbar(WID_VL_SCROLLBAR);
1463 
1464  this->vehicles.SetListing(*this->sorting);
1465  this->vehicles.ForceRebuild();
1466  this->vehicles.NeedResort();
1467  this->BuildVehicleList();
1468  this->SortVehicleList();
1469 
1470  /* Set up the window widgets */
1471  this->GetWidget<NWidgetCore>(WID_VL_LIST)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
1472 
1473  if (this->vli.type == VL_SHARED_ORDERS) {
1474  this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_SHARED_ORDERS_LIST_CAPTION;
1475  } else {
1476  this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
1477  }
1478 
1479  this->FinishInitNested(window_number);
1480  if (this->vli.company != OWNER_NONE) this->owner = this->vli.company;
1481  }
1482 
1484  {
1485  *this->sorting = this->vehicles.GetListing();
1486  }
1487 
1488  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1489  {
1490  switch (widget) {
1491  case WID_VL_LIST:
1492  resize->height = GetVehicleListHeight(this->vli.vtype, 1);
1493 
1494  switch (this->vli.vtype) {
1495  case VEH_TRAIN:
1496  case VEH_ROAD:
1497  size->height = 6 * resize->height;
1498  break;
1499  case VEH_SHIP:
1500  case VEH_AIRCRAFT:
1501  size->height = 4 * resize->height;
1502  break;
1503  default: NOT_REACHED();
1504  }
1505  break;
1506 
1507  case WID_VL_SORT_ORDER: {
1508  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1509  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1510  d.height += padding.height;
1511  *size = maxdim(*size, d);
1512  break;
1513  }
1514 
1516  Dimension d = this->GetActionDropdownSize(this->vli.type == VL_STANDARD, false);
1517  d.height += padding.height;
1518  d.width += padding.width;
1519  *size = maxdim(*size, d);
1520  break;
1521  }
1522  }
1523  }
1524 
1525  virtual void SetStringParameters(int widget) const
1526  {
1527  switch (widget) {
1529  SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
1530  break;
1531 
1532  case WID_VL_CAPTION: {
1533  switch (this->vli.type) {
1534  case VL_SHARED_ORDERS: // Shared Orders
1535  if (this->vehicles.Length() == 0) {
1536  /* We can't open this window without vehicles using this order
1537  * and we should close the window when deleting the order. */
1538  NOT_REACHED();
1539  }
1540  SetDParam(0, this->vscroll->GetCount());
1541  break;
1542 
1543  case VL_STANDARD: // Company Name
1544  SetDParam(0, STR_COMPANY_NAME);
1545  SetDParam(1, this->vli.index);
1546  SetDParam(3, this->vscroll->GetCount());
1547  break;
1548 
1549  case VL_STATION_LIST: // Station/Waypoint Name
1550  SetDParam(0, Station::IsExpected(BaseStation::Get(this->vli.index)) ? STR_STATION_NAME : STR_WAYPOINT_NAME);
1551  SetDParam(1, this->vli.index);
1552  SetDParam(3, this->vscroll->GetCount());
1553  break;
1554 
1555  case VL_DEPOT_LIST:
1556  SetDParam(0, STR_DEPOT_CAPTION);
1557  SetDParam(1, this->vli.vtype);
1558  SetDParam(2, this->vli.index);
1559  SetDParam(3, this->vscroll->GetCount());
1560  break;
1561  default: NOT_REACHED();
1562  }
1563  break;
1564  }
1565  }
1566  }
1567 
1568  virtual void DrawWidget(const Rect &r, int widget) const
1569  {
1570  switch (widget) {
1571  case WID_VL_SORT_ORDER:
1572  /* draw arrow pointing up/down for ascending/descending sorting */
1573  this->DrawSortButtonState(widget, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1574  break;
1575 
1576  case WID_VL_LIST:
1578  break;
1579  }
1580  }
1581 
1582  virtual void OnPaint()
1583  {
1584  this->BuildVehicleList();
1585  this->SortVehicleList();
1586 
1587  if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_VL_MANAGE_VEHICLES_DROPDOWN)) {
1588  HideDropDownMenu(this);
1589  }
1590 
1591  /* Hide the widgets that we will not use in this window
1592  * Some windows contains actions only fit for the owner */
1593  int plane_to_show = (this->owner == _local_company) ? BP_SHOW_BUTTONS : BP_HIDE_BUTTONS;
1594  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VL_HIDE_BUTTONS);
1595  if (plane_to_show != nwi->shown_plane) {
1596  nwi->SetDisplayedPlane(plane_to_show);
1597  nwi->SetDirty(this);
1598  }
1599  if (this->owner == _local_company) {
1600  this->SetWidgetDisabledState(WID_VL_AVAILABLE_VEHICLES, this->vli.type != VL_STANDARD);
1601  this->SetWidgetsDisabledState(this->vehicles.Length() == 0,
1605  WIDGET_LIST_END);
1606  }
1607 
1608  /* Set text of sort by dropdown widget. */
1609  this->GetWidget<NWidgetCore>(WID_VL_SORT_BY_PULLDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
1610 
1611  this->DrawWidgets();
1612  }
1613 
1614  virtual void OnClick(Point pt, int widget, int click_count)
1615  {
1616  switch (widget) {
1617  case WID_VL_SORT_ORDER: // Flip sorting method ascending/descending
1618  this->vehicles.ToggleSortOrder();
1619  this->SetDirty();
1620  break;
1621 
1622  case WID_VL_SORT_BY_PULLDOWN:// Select sorting criteria dropdown menu
1623  ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_VL_SORT_BY_PULLDOWN, 0,
1624  (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10));
1625  return;
1626 
1627  case WID_VL_LIST: { // Matrix to show vehicles
1628  uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VL_LIST);
1629  if (id_v >= this->vehicles.Length()) return; // click out of list bound
1630 
1631  const Vehicle *v = this->vehicles[id_v];
1633  break;
1634  }
1635 
1637  ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
1638  break;
1639 
1641  DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier::UnPack(this->window_number).type == VL_STANDARD, false);
1643  break;
1644  }
1645 
1646  case WID_VL_STOP_ALL:
1647  case WID_VL_START_ALL:
1648  DoCommandP(0, (1 << 1) | (widget == WID_VL_START_ALL ? (1 << 0) : 0), this->window_number, CMD_MASS_START_STOP);
1649  break;
1650  }
1651  }
1652 
1653  virtual void OnDropdownSelect(int widget, int index)
1654  {
1655  switch (widget) {
1657  this->vehicles.SetSortType(index);
1658  break;
1660  assert(this->vehicles.Length() != 0);
1661 
1662  switch (index) {
1663  case ADI_REPLACE: // Replace window
1665  break;
1666  case ADI_SERVICE: // Send for servicing
1667  case ADI_DEPOT: // Send to Depots
1668  DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : (DepotCommand)0), this->window_number, GetCmdSendToDepot(this->vli.vtype));
1669  break;
1670 
1671  default: NOT_REACHED();
1672  }
1673  break;
1674  default: NOT_REACHED();
1675  }
1676  this->SetDirty();
1677  }
1678 
1679  virtual void OnTick()
1680  {
1681  if (_pause_mode != PM_UNPAUSED) return;
1682  if (this->vehicles.NeedResort()) {
1683  StationID station = (this->vli.type == VL_STATION_LIST) ? this->vli.index : INVALID_STATION;
1684 
1685  DEBUG(misc, 3, "Periodic resort %d list company %d at station %d", this->vli.vtype, this->owner, station);
1686  this->SetDirty();
1687  }
1688  }
1689 
1690  virtual void OnResize()
1691  {
1692  this->vscroll->SetCapacityFromWidget(this, WID_VL_LIST);
1693  }
1694 
1700  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1701  {
1702  if (!gui_scope && HasBit(data, 31) && this->vli.type == VL_SHARED_ORDERS) {
1703  /* Needs to be done in command-scope, so everything stays valid */
1704  this->vli.index = GB(data, 0, 20);
1705  this->window_number = this->vli.Pack();
1706  this->vehicles.ForceRebuild();
1707  return;
1708  }
1709 
1710  if (data == 0) {
1711  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1712  this->vehicles.ForceRebuild();
1713  } else {
1714  this->vehicles.ForceResort();
1715  }
1716  }
1717 };
1718 
1719 static WindowDesc _vehicle_list_other_desc(
1720  WDP_AUTO, "list_vehicles", 260, 246,
1722  0,
1723  _nested_vehicle_list, lengthof(_nested_vehicle_list)
1724 );
1725 
1726 static WindowDesc _vehicle_list_train_desc(
1727  WDP_AUTO, "list_vehicles_train", 325, 246,
1729  0,
1730  _nested_vehicle_list, lengthof(_nested_vehicle_list)
1731 );
1732 
1733 static void ShowVehicleListWindowLocal(CompanyID company, VehicleListType vlt, VehicleType vehicle_type, uint32 unique_number)
1734 {
1735  if (!Company::IsValidID(company) && company != OWNER_NONE) return;
1736 
1737  WindowNumber num = VehicleListIdentifier(vlt, vehicle_type, company, unique_number).Pack();
1738  if (vehicle_type == VEH_TRAIN) {
1739  AllocateWindowDescFront<VehicleListWindow>(&_vehicle_list_train_desc, num);
1740  } else {
1741  _vehicle_list_other_desc.cls = GetWindowClassForVehicleType(vehicle_type);
1742  AllocateWindowDescFront<VehicleListWindow>(&_vehicle_list_other_desc, num);
1743  }
1744 }
1745 
1746 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type)
1747 {
1748  /* If _settings_client.gui.advanced_vehicle_list > 1, display the Advanced list
1749  * if _settings_client.gui.advanced_vehicle_list == 1, display Advanced list only for local company
1750  * if _ctrl_pressed, do the opposite action (Advanced list x Normal list)
1751  */
1752 
1753  if ((_settings_client.gui.advanced_vehicle_list > (uint)(company != _local_company)) != _ctrl_pressed) {
1754  ShowCompanyGroup(company, vehicle_type);
1755  } else {
1756  ShowVehicleListWindowLocal(company, VL_STANDARD, vehicle_type, company);
1757  }
1758 }
1759 
1760 void ShowVehicleListWindow(const Vehicle *v)
1761 {
1762  ShowVehicleListWindowLocal(v->owner, VL_SHARED_ORDERS, v->type, v->FirstShared()->index);
1763 }
1764 
1765 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station)
1766 {
1767  ShowVehicleListWindowLocal(company, VL_STATION_LIST, vehicle_type, station);
1768 }
1769 
1770 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile)
1771 {
1772  uint16 depot_airport_index;
1773 
1774  if (vehicle_type == VEH_AIRCRAFT) {
1775  depot_airport_index = GetStationIndex(depot_tile);
1776  } else {
1777  depot_airport_index = GetDepotIndex(depot_tile);
1778  }
1779  ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_airport_index);
1780 }
1781 
1782 
1783 /* Unified vehicle GUI - Vehicle Details Window */
1784 
1789 
1793  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1794  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1795  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_RENAME_VEHICLE), SetMinimalSize(40, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_VEHICLE_NAME_BUTTON, STR_NULL /* filled in later */),
1796  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1797  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1798  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1799  EndContainer(),
1800  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetMinimalSize(405, 42), SetResize(1, 0), EndContainer(),
1801  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_MIDDLE_DETAILS), SetMinimalSize(405, 45), SetResize(1, 0), EndContainer(),
1804  SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1806  SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_INCREASE_SERVICING_INTERVAL_TOOLTIP),
1808  SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1809  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1810  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1811  EndContainer(),
1812 };
1813 
1817  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1818  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1819  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_RENAME_VEHICLE), SetMinimalSize(40, 0), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetDataTip(STR_VEHICLE_NAME_BUTTON, STR_NULL /* filled in later */),
1820  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1821  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1822  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1823  EndContainer(),
1824  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetResize(1, 0), SetMinimalSize(405, 42), EndContainer(),
1826  NWidget(WWT_MATRIX, COLOUR_GREY, WID_VD_MATRIX), SetResize(1, 1), SetMinimalSize(393, 45), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 0), SetScrollbar(WID_VD_SCROLLBAR),
1827  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VD_SCROLLBAR),
1828  EndContainer(),
1831  SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1833  SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1835  SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1836  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1837  EndContainer(),
1840  SetDataTip(STR_VEHICLE_DETAIL_TAB_CARGO, STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1842  SetDataTip(STR_VEHICLE_DETAIL_TAB_INFORMATION, STR_VEHICLE_DETAILS_TRAIN_INFORMATION_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1844  SetDataTip(STR_VEHICLE_DETAIL_TAB_CAPACITIES, STR_VEHICLE_DETAILS_TRAIN_CAPACITIES_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1846  SetDataTip(STR_VEHICLE_DETAIL_TAB_TOTAL_CARGO, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1847  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1848  EndContainer(),
1849 };
1850 
1851 
1852 extern int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab);
1853 extern void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_pos, uint16 vscroll_cap, TrainDetailsWindowTabs det_tab);
1854 extern void DrawRoadVehDetails(const Vehicle *v, int left, int right, int y);
1855 extern void DrawShipDetails(const Vehicle *v, int left, int right, int y);
1856 extern void DrawAircraftDetails(const Aircraft *v, int left, int right, int y);
1857 
1858 static StringID _service_interval_dropdown[] = {
1859  STR_VEHICLE_DETAILS_DEFAULT,
1860  STR_VEHICLE_DETAILS_DAYS,
1861  STR_VEHICLE_DETAILS_PERCENT,
1863 };
1864 
1868  Scrollbar *vscroll;
1869 
1871  VehicleDetailsWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
1872  {
1873  const Vehicle *v = Vehicle::Get(window_number);
1874 
1875  this->CreateNestedTree();
1876  this->vscroll = (v->type == VEH_TRAIN ? this->GetScrollbar(WID_VD_SCROLLBAR) : NULL);
1877  this->FinishInitNested(window_number);
1878 
1879  this->GetWidget<NWidgetCore>(WID_VD_RENAME_VEHICLE)->tool_tip = STR_VEHICLE_DETAILS_TRAIN_RENAME + v->type;
1880 
1881  this->owner = v->owner;
1882  this->tab = TDW_TAB_CARGO;
1883  }
1884 
1890  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1891  {
1892  if (data == VIWD_AUTOREPLACE) {
1893  /* Autoreplace replaced the vehicle.
1894  * Nothing to do for this window. */
1895  return;
1896  }
1897  if (!gui_scope) return;
1898  const Vehicle *v = Vehicle::Get(this->window_number);
1899  if (v->type == VEH_ROAD) {
1900  const NWidgetBase *nwid_info = this->GetWidget<NWidgetBase>(WID_VD_MIDDLE_DETAILS);
1901  uint aimed_height = this->GetRoadVehDetailsHeight(v);
1902  /* If the number of articulated parts changes, the size of the window must change too. */
1903  if (aimed_height != nwid_info->current_y) {
1904  this->ReInit();
1905  }
1906  }
1907  }
1908 
1915  {
1916  uint desired_height;
1917  if (v->HasArticulatedPart()) {
1918  /* An articulated RV has its text drawn under the sprite instead of after it, hence 15 pixels extra. */
1919  desired_height = WD_FRAMERECT_TOP + ScaleGUITrad(15) + 3 * FONT_HEIGHT_NORMAL + 2 + WD_FRAMERECT_BOTTOM;
1920  /* Add space for the cargo amount for each part. */
1921  for (const Vehicle *u = v; u != NULL; u = u->Next()) {
1922  if (u->cargo_cap != 0) desired_height += FONT_HEIGHT_NORMAL + 1;
1923  }
1924  } else {
1925  desired_height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
1926  }
1927  return desired_height;
1928  }
1929 
1930  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1931  {
1932  switch (widget) {
1933  case WID_VD_TOP_DETAILS: {
1934  Dimension dim = { 0, 0 };
1936 
1937  for (uint i = 0; i < 4; i++) SetDParamMaxValue(i, INT16_MAX);
1938  static const StringID info_strings[] = {
1939  STR_VEHICLE_INFO_MAX_SPEED,
1940  STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED,
1941  STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE,
1942  STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR,
1943  STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS
1944  };
1945  for (uint i = 0; i < lengthof(info_strings); i++) {
1946  dim = maxdim(dim, GetStringBoundingBox(info_strings[i]));
1947  }
1948  SetDParam(0, STR_VEHICLE_INFO_AGE);
1949  dim = maxdim(dim, GetStringBoundingBox(STR_VEHICLE_INFO_AGE_RUNNING_COST_YR));
1950  size->width = dim.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1951  break;
1952  }
1953 
1954  case WID_VD_MIDDLE_DETAILS: {
1955  const Vehicle *v = Vehicle::Get(this->window_number);
1956  switch (v->type) {
1957  case VEH_ROAD:
1958  size->height = this->GetRoadVehDetailsHeight(v);
1959  break;
1960 
1961  case VEH_SHIP:
1962  size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
1963  break;
1964 
1965  case VEH_AIRCRAFT:
1966  size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + 4 + WD_FRAMERECT_BOTTOM;
1967  break;
1968 
1969  default:
1970  NOT_REACHED(); // Train uses WID_VD_MATRIX instead.
1971  }
1972  break;
1973  }
1974 
1975  case WID_VD_MATRIX:
1977  size->height = 4 * resize->height;
1978  break;
1979 
1981  StringID *strs = _service_interval_dropdown;
1982  while (*strs != INVALID_STRING_ID) {
1983  *size = maxdim(*size, GetStringBoundingBox(*strs++));
1984  }
1985  size->width += padding.width;
1987  break;
1988  }
1989 
1991  SetDParamMaxValue(0, MAX_SERVINT_DAYS); // Roughly the maximum interval
1992  SetDParamMaxValue(1, MAX_YEAR * DAYS_IN_YEAR); // Roughly the maximum year
1993  size->width = max(GetStringBoundingBox(STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT).width, GetStringBoundingBox(STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS).width) + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1995  break;
1996  }
1997  }
1998 
2000  static bool IsVehicleServiceIntervalEnabled(const VehicleType vehicle_type, CompanyID company_id)
2001  {
2002  const VehicleDefaultSettings *vds = &Company::Get(company_id)->settings.vehicle;
2003  switch (vehicle_type) {
2004  default: NOT_REACHED();
2005  case VEH_TRAIN: return vds->servint_trains != 0;
2006  case VEH_ROAD: return vds->servint_roadveh != 0;
2007  case VEH_SHIP: return vds->servint_ships != 0;
2008  case VEH_AIRCRAFT: return vds->servint_aircraft != 0;
2009  }
2010  }
2011 
2023  static void DrawVehicleDetails(const Vehicle *v, int left, int right, int y, int vscroll_pos, uint vscroll_cap, TrainDetailsWindowTabs det_tab)
2024  {
2025  switch (v->type) {
2026  case VEH_TRAIN: DrawTrainDetails(Train::From(v), left, right, y, vscroll_pos, vscroll_cap, det_tab); break;
2027  case VEH_ROAD: DrawRoadVehDetails(v, left, right, y); break;
2028  case VEH_SHIP: DrawShipDetails(v, left, right, y); break;
2029  case VEH_AIRCRAFT: DrawAircraftDetails(Aircraft::From(v), left, right, y); break;
2030  default: NOT_REACHED();
2031  }
2032  }
2033 
2034  virtual void SetStringParameters(int widget) const
2035  {
2036  if (widget == WID_VD_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
2037  }
2038 
2039  virtual void DrawWidget(const Rect &r, int widget) const
2040  {
2041  const Vehicle *v = Vehicle::Get(this->window_number);
2042 
2043  switch (widget) {
2044  case WID_VD_TOP_DETAILS: {
2045  int y = r.top + WD_FRAMERECT_TOP;
2046 
2047  /* Draw running cost */
2048  SetDParam(1, v->age / DAYS_IN_LEAP_YEAR);
2049  SetDParam(0, (v->age + DAYS_IN_YEAR < v->max_age) ? STR_VEHICLE_INFO_AGE : STR_VEHICLE_INFO_AGE_RED);
2052  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_AGE_RUNNING_COST_YR);
2053  y += FONT_HEIGHT_NORMAL;
2054 
2055  /* Draw max speed */
2056  StringID string;
2057  if (v->type == VEH_TRAIN ||
2058  (v->type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL)) {
2059  const GroundVehicleCache *gcache = v->GetGroundVehicleCache();
2060  SetDParam(2, v->GetDisplayMaxSpeed());
2061  SetDParam(1, gcache->cached_power);
2062  SetDParam(0, gcache->cached_weight);
2063  SetDParam(3, gcache->cached_max_te / 1000);
2064  if (v->type == VEH_TRAIN && (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL ||
2065  GetRailTypeInfo(Train::From(v)->railtype)->acceleration_type == 2)) {
2066  string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED;
2067  } else {
2068  string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE;
2069  }
2070  } else {
2071  SetDParam(0, v->GetDisplayMaxSpeed());
2072  if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->GetRange() > 0) {
2073  SetDParam(1, Aircraft::From(v)->GetRange());
2074  string = STR_VEHICLE_INFO_MAX_SPEED_RANGE;
2075  } else {
2076  string = STR_VEHICLE_INFO_MAX_SPEED;
2077  }
2078  }
2079  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, string);
2080  y += FONT_HEIGHT_NORMAL;
2081 
2082  /* Draw profit */
2085  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR);
2086  y += FONT_HEIGHT_NORMAL;
2087 
2088  /* Draw breakdown & reliability */
2091  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS);
2092  break;
2093  }
2094 
2095  case WID_VD_MATRIX:
2096  /* For trains only. */
2097  DrawVehicleDetails(v, r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, r.top + WD_MATRIX_TOP, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->tab);
2098  break;
2099 
2100  case WID_VD_MIDDLE_DETAILS: {
2101  /* For other vehicles, at the place of the matrix. */
2102  bool rtl = _current_text_dir == TD_RTL;
2104 
2105  uint text_left = r.left + (rtl ? 0 : sprite_width);
2106  uint text_right = r.right - (rtl ? sprite_width : 0);
2107 
2108  /* Articulated road vehicles use a complete line. */
2109  if (v->type == VEH_ROAD && v->HasArticulatedPart()) {
2110  DrawVehicleImage(v, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, INVALID_VEHICLE, EIT_IN_DETAILS, 0);
2111  } else {
2112  uint sprite_left = rtl ? text_right : r.left;
2113  uint sprite_right = rtl ? r.right : text_left;
2114 
2115  DrawVehicleImage(v, sprite_left + WD_FRAMERECT_LEFT, sprite_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, INVALID_VEHICLE, EIT_IN_DETAILS, 0);
2116  }
2117  DrawVehicleDetails(v, text_left + WD_FRAMERECT_LEFT, text_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, 0, 0, this->tab);
2118  break;
2119  }
2120 
2122  /* Draw service interval text */
2123  SetDParam(0, v->GetServiceInterval());
2125  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + (r.bottom - r.top + 1 - FONT_HEIGHT_NORMAL) / 2,
2126  v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS);
2127  break;
2128  }
2129  }
2130 
2132  virtual void OnPaint()
2133  {
2134  const Vehicle *v = Vehicle::Get(this->window_number);
2135 
2137 
2138  if (v->type == VEH_TRAIN) {
2140  this->vscroll->SetCount(GetTrainDetailsWndVScroll(v->index, this->tab));
2141  }
2142 
2143  /* Disable service-scroller when interval is set to disabled */
2147  WIDGET_LIST_END);
2148 
2149  StringID str = v->ServiceIntervalIsCustom() ?
2150  (v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_PERCENT : STR_VEHICLE_DETAILS_DAYS) :
2151  STR_VEHICLE_DETAILS_DEFAULT;
2152  this->GetWidget<NWidgetCore>(WID_VD_SERVICE_INTERVAL_DROPDOWN)->widget_data = str;
2153 
2154  this->DrawWidgets();
2155  }
2156 
2157  virtual void OnClick(Point pt, int widget, int click_count)
2158  {
2159  switch (widget) {
2160  case WID_VD_RENAME_VEHICLE: { // rename
2161  const Vehicle *v = Vehicle::Get(this->window_number);
2162  SetDParam(0, v->index);
2163  ShowQueryString(STR_VEHICLE_NAME, STR_QUERY_RENAME_TRAIN_CAPTION + v->type,
2165  break;
2166  }
2167 
2168  case WID_VD_INCREASE_SERVICING_INTERVAL: // increase int
2169  case WID_VD_DECREASE_SERVICING_INTERVAL: { // decrease int
2170  int mod = _ctrl_pressed ? 5 : 10;
2171  const Vehicle *v = Vehicle::Get(this->window_number);
2172 
2173  mod = (widget == WID_VD_DECREASE_SERVICING_INTERVAL) ? -mod : mod;
2174  mod = GetServiceIntervalClamped(mod + v->GetServiceInterval(), v->ServiceIntervalIsPercent());
2175  if (mod == v->GetServiceInterval()) return;
2176 
2177  DoCommandP(v->tile, v->index, mod | (1 << 16) | (v->ServiceIntervalIsPercent() << 17), CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_SERVICING));
2178  break;
2179  }
2180 
2182  const Vehicle *v = Vehicle::Get(this->window_number);
2183  ShowDropDownMenu(this, _service_interval_dropdown, v->ServiceIntervalIsCustom() ? (v->ServiceIntervalIsPercent() ? 2 : 1) : 0, widget, 0, 0);
2184  break;
2185  }
2186 
2191  this->SetWidgetsDisabledState(false,
2196  widget,
2197  WIDGET_LIST_END);
2198 
2200  this->SetDirty();
2201  break;
2202  }
2203  }
2204 
2205  virtual void OnDropdownSelect(int widget, int index)
2206  {
2207  switch (widget) {
2209  const Vehicle *v = Vehicle::Get(this->window_number);
2210  bool iscustom = index != 0;
2211  bool ispercent = iscustom ? (index == 2) : Company::Get(v->owner)->settings.vehicle.servint_ispercent;
2212  uint16 interval = GetServiceIntervalClamped(v->GetServiceInterval(), ispercent);
2213  DoCommandP(v->tile, v->index, interval | (iscustom << 16) | (ispercent << 17), CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_SERVICING));
2214  break;
2215  }
2216  }
2217  }
2218 
2219  virtual void OnQueryTextFinished(char *str)
2220  {
2221  if (str == NULL) return;
2222 
2223  DoCommandP(0, this->window_number, 0, CMD_RENAME_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_RENAME_TRAIN + Vehicle::Get(this->window_number)->type), NULL, str);
2224  }
2225 
2226  virtual void OnResize()
2227  {
2228  NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_VD_MATRIX);
2229  if (nwi != NULL) {
2230  this->vscroll->SetCapacityFromWidget(this, WID_VD_MATRIX);
2231  }
2232  }
2233 };
2234 
2237  WDP_AUTO, "view_vehicle_details_train", 405, 178,
2239  0,
2240  _nested_train_vehicle_details_widgets, lengthof(_nested_train_vehicle_details_widgets)
2241 );
2242 
2245  WDP_AUTO, "view_vehicle_details", 405, 113,
2247  0,
2248  _nested_nontrain_vehicle_details_widgets, lengthof(_nested_nontrain_vehicle_details_widgets)
2249 );
2250 
2252 static void ShowVehicleDetailsWindow(const Vehicle *v)
2253 {
2256  AllocateWindowDescFront<VehicleDetailsWindow>((v->type == VEH_TRAIN) ? &_train_vehicle_details_desc : &_nontrain_vehicle_details_desc, v->index);
2257 }
2258 
2259 
2260 /* Unified vehicle GUI - Vehicle View Window */
2261 
2265  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2266  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VV_CAPTION), SetDataTip(STR_VEHICLE_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2267  NWidget(WWT_DEBUGBOX, COLOUR_GREY),
2268  NWidget(WWT_SHADEBOX, COLOUR_GREY),
2269  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
2270  NWidget(WWT_STICKYBOX, COLOUR_GREY),
2271  EndContainer(),
2273  NWidget(WWT_PANEL, COLOUR_GREY),
2274  NWidget(WWT_INSET, COLOUR_GREY), SetPadding(2, 2, 2, 2),
2275  NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_VV_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1), SetPadding(1, 1, 1, 1),
2276  EndContainer(),
2277  EndContainer(),
2279  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CENTER_MAIN_VIEW), SetMinimalSize(18, 18), SetDataTip(SPR_CENTRE_VIEW_VEHICLE, 0x0 /* filled later */),
2281  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_GOTO_DEPOT), SetMinimalSize(18, 18), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2282  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CLONE), SetMinimalSize(18, 18), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2283  EndContainer(),
2284  /* For trains only, 'ignore signal' button. */
2286  SetDataTip(SPR_IGNORE_SIGNALS, STR_VEHICLE_VIEW_TRAIN_IGNORE_SIGNAL_TOOLTIP),
2288  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_REFIT), SetMinimalSize(18, 18), SetDataTip(SPR_REFIT_VEHICLE, 0x0 /* filled later */),
2289  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_TURN_AROUND), SetMinimalSize(18, 18),
2290  SetDataTip(SPR_FORCE_VEHICLE_TURN, STR_VEHICLE_VIEW_ROAD_VEHICLE_REVERSE_TOOLTIP),
2291  EndContainer(),
2292  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_ORDERS), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_ORDERS, 0x0 /* filled later */),
2293  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_DETAILS), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_VEHICLE_DETAILS, 0x0 /* filled later */),
2294  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(18, 0), SetResize(0, 1), EndContainer(),
2295  EndContainer(),
2296  EndContainer(),
2299  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
2300  EndContainer(),
2301 };
2302 
2305  WDP_AUTO, "view_vehicle", 250, 116,
2307  0,
2308  _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
2309 );
2310 
2316  WDP_AUTO, "view_vehicle_train", 250, 134,
2318  0,
2319  _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
2320 );
2321 
2322 
2323 /* Just to make sure, nobody has changed the vehicle type constants, as we are
2324  using them for array indexing in a number of places here. */
2325 assert_compile(VEH_TRAIN == 0);
2326 assert_compile(VEH_ROAD == 1);
2327 assert_compile(VEH_SHIP == 2);
2328 assert_compile(VEH_AIRCRAFT == 3);
2329 
2334  ZOOM_LVL_SHIP,
2336 };
2337 
2338 /* Constants for geometry of vehicle view viewport */
2339 static const int VV_INITIAL_VIEWPORT_WIDTH = 226;
2340 static const int VV_INITIAL_VIEWPORT_HEIGHT = 84;
2341 static const int VV_INITIAL_VIEWPORT_HEIGHT_TRAIN = 102;
2342 
2345  VCT_CMD_START_STOP = 0,
2346  VCT_CMD_CLONE_VEH,
2347  VCT_CMD_TURN_AROUND,
2348 };
2349 
2351 static const uint32 _vehicle_command_translation_table[][4] = {
2352  { // VCT_CMD_START_STOP
2353  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_TRAIN),
2354  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_ROAD_VEHICLE),
2355  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_SHIP),
2356  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_AIRCRAFT)
2357  },
2358  { // VCT_CMD_CLONE_VEH
2359  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN),
2360  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_ROAD_VEHICLE),
2361  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_SHIP),
2362  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_AIRCRAFT)
2363  },
2364  { // VCT_CMD_TURN_AROUND
2365  CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN),
2366  CMD_TURN_ROADVEH | CMD_MSG(STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN),
2367  0xffffffff, // invalid for ships
2368  0xffffffff // invalid for aircrafts
2369  },
2370 };
2371 
2379 void CcStartStopVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2380 {
2381  if (result.Failed()) return;
2382 
2383  const Vehicle *v = Vehicle::GetIfValid(p1);
2384  if (v == NULL || !v->IsPrimaryVehicle() || v->owner != _local_company) return;
2385 
2386  StringID msg = (v->vehstatus & VS_STOPPED) ? STR_VEHICLE_COMMAND_STOPPED : STR_VEHICLE_COMMAND_STARTED;
2387  Point pt = RemapCoords(v->x_pos, v->y_pos, v->z_pos);
2388  AddTextEffect(msg, pt.x, pt.y, DAY_TICKS, TE_RISING);
2389 }
2390 
2396 void StartStopVehicle(const Vehicle *v, bool texteffect)
2397 {
2398  assert(v->IsPrimaryVehicle());
2399  DoCommandP(v->tile, v->index, 0, _vehicle_command_translation_table[VCT_CMD_START_STOP][v->type], texteffect ? CcStartStopVehicle : NULL);
2400 }
2401 
2403 static bool IsVehicleRefitable(const Vehicle *v)
2404 {
2405  if (!v->IsStoppedInDepot()) return false;
2406 
2407  do {
2408  if (IsEngineRefittable(v->engine_type)) return true;
2409  } while (v->IsGroundVehicle() && (v = v->Next()) != NULL);
2410 
2411  return false;
2412 }
2413 
2416 private:
2421 
2424 
2427  };
2428 
2434  {
2435  switch (plane) {
2436  case SEL_DC_GOTO_DEPOT:
2437  case SEL_DC_CLONE:
2438  this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE)->SetDisplayedPlane(plane - SEL_DC_BASEPLANE);
2439  break;
2440 
2441  case SEL_RT_REFIT:
2442  case SEL_RT_TURN_AROUND:
2443  this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN)->SetDisplayedPlane(plane - SEL_RT_BASEPLANE);
2444  break;
2445 
2446  default:
2447  NOT_REACHED();
2448  }
2449  }
2450 
2451 public:
2452  VehicleViewWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
2453  {
2454  this->CreateNestedTree();
2455 
2456  /* Sprites for the 'send to depot' button indexed by vehicle type. */
2457  static const SpriteID vehicle_view_goto_depot_sprites[] = {
2458  SPR_SEND_TRAIN_TODEPOT,
2459  SPR_SEND_ROADVEH_TODEPOT,
2460  SPR_SEND_SHIP_TODEPOT,
2461  SPR_SEND_AIRCRAFT_TODEPOT,
2462  };
2463  const Vehicle *v = Vehicle::Get(window_number);
2464  this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->widget_data = vehicle_view_goto_depot_sprites[v->type];
2465 
2466  /* Sprites for the 'clone vehicle' button indexed by vehicle type. */
2467  static const SpriteID vehicle_view_clone_sprites[] = {
2469  SPR_CLONE_ROADVEH,
2470  SPR_CLONE_SHIP,
2471  SPR_CLONE_AIRCRAFT,
2472  };
2473  this->GetWidget<NWidgetCore>(WID_VV_CLONE)->widget_data = vehicle_view_clone_sprites[v->type];
2474 
2475  switch (v->type) {
2476  case VEH_TRAIN:
2477  this->GetWidget<NWidgetCore>(WID_VV_TURN_AROUND)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REVERSE_TOOLTIP;
2478  break;
2479 
2480  case VEH_ROAD:
2481  break;
2482 
2483  case VEH_SHIP:
2484  case VEH_AIRCRAFT:
2485  this->SelectPlane(SEL_RT_REFIT);
2486  break;
2487 
2488  default: NOT_REACHED();
2489  }
2490  this->FinishInitNested(window_number);
2491  this->owner = v->owner;
2492  this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT)->InitializeViewport(this, this->window_number | (1 << 31), _vehicle_view_zoom_levels[v->type]);
2493 
2494  this->GetWidget<NWidgetCore>(WID_VV_START_STOP)->tool_tip = STR_VEHICLE_VIEW_TRAIN_STATE_START_STOP_TOOLTIP + v->type;
2495  this->GetWidget<NWidgetCore>(WID_VV_CENTER_MAIN_VIEW)->tool_tip = STR_VEHICLE_VIEW_TRAIN_LOCATION_TOOLTIP + v->type;
2496  this->GetWidget<NWidgetCore>(WID_VV_REFIT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REFIT_TOOLTIP + v->type;
2497  this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SEND_TO_DEPOT_TOOLTIP + v->type;
2498  this->GetWidget<NWidgetCore>(WID_VV_SHOW_ORDERS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_ORDERS_TOOLTIP + v->type;
2499  this->GetWidget<NWidgetCore>(WID_VV_SHOW_DETAILS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SHOW_DETAILS_TOOLTIP + v->type;
2500  this->GetWidget<NWidgetCore>(WID_VV_CLONE)->tool_tip = STR_VEHICLE_VIEW_CLONE_TRAIN_INFO + v->type;
2501  }
2502 
2504  {
2505  DeleteWindowById(WC_VEHICLE_ORDERS, this->window_number, false);
2506  DeleteWindowById(WC_VEHICLE_REFIT, this->window_number, false);
2507  DeleteWindowById(WC_VEHICLE_DETAILS, this->window_number, false);
2508  DeleteWindowById(WC_VEHICLE_TIMETABLE, this->window_number, false);
2509  }
2510 
2511  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2512  {
2513  const Vehicle *v = Vehicle::Get(this->window_number);
2514  switch (widget) {
2515  case WID_VV_START_STOP:
2516  size->height = max(size->height, max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).height, GetSpriteSize(SPR_FLAG_VEH_RUNNING).height) + WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
2517  break;
2518 
2519  case WID_VV_FORCE_PROCEED:
2520  if (v->type != VEH_TRAIN) {
2521  size->height = 0;
2522  size->width = 0;
2523  }
2524  break;
2525 
2526  case WID_VV_VIEWPORT:
2527  size->width = VV_INITIAL_VIEWPORT_WIDTH;
2528  size->height = (v->type == VEH_TRAIN) ? VV_INITIAL_VIEWPORT_HEIGHT_TRAIN : VV_INITIAL_VIEWPORT_HEIGHT;
2529  break;
2530  }
2531  }
2532 
2533  virtual void OnPaint()
2534  {
2535  const Vehicle *v = Vehicle::Get(this->window_number);
2536  bool is_localcompany = v->owner == _local_company;
2537  bool refitable_and_stopped_in_depot = IsVehicleRefitable(v);
2538 
2539  this->SetWidgetDisabledState(WID_VV_GOTO_DEPOT, !is_localcompany);
2540  this->SetWidgetDisabledState(WID_VV_REFIT, !refitable_and_stopped_in_depot || !is_localcompany);
2541  this->SetWidgetDisabledState(WID_VV_CLONE, !is_localcompany);
2542 
2543  if (v->type == VEH_TRAIN) {
2544  this->SetWidgetLoweredState(WID_VV_FORCE_PROCEED, Train::From(v)->force_proceed == TFP_SIGNAL);
2545  this->SetWidgetDisabledState(WID_VV_FORCE_PROCEED, !is_localcompany);
2546  this->SetWidgetDisabledState(WID_VV_TURN_AROUND, !is_localcompany);
2547  }
2548 
2549  this->DrawWidgets();
2550  }
2551 
2552  virtual void SetStringParameters(int widget) const
2553  {
2554  if (widget != WID_VV_CAPTION) return;
2555 
2556  const Vehicle *v = Vehicle::Get(this->window_number);
2557  SetDParam(0, v->index);
2558  }
2559 
2560  virtual void DrawWidget(const Rect &r, int widget) const
2561  {
2562  if (widget != WID_VV_START_STOP) return;
2563 
2564  const Vehicle *v = Vehicle::Get(this->window_number);
2565  StringID str;
2566  if (v->vehstatus & VS_CRASHED) {
2567  str = STR_VEHICLE_STATUS_CRASHED;
2568  } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary?
2569  str = STR_VEHICLE_STATUS_BROKEN_DOWN;
2570  } else if (v->vehstatus & VS_STOPPED) {
2571  if (v->type == VEH_TRAIN) {
2572  if (v->cur_speed == 0) {
2573  if (Train::From(v)->gcache.cached_power == 0) {
2574  str = STR_VEHICLE_STATUS_TRAIN_NO_POWER;
2575  } else {
2576  str = STR_VEHICLE_STATUS_STOPPED;
2577  }
2578  } else {
2579  SetDParam(0, v->GetDisplaySpeed());
2580  str = STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL;
2581  }
2582  } else { // no train
2583  str = STR_VEHICLE_STATUS_STOPPED;
2584  }
2585  } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) {
2586  str = STR_VEHICLE_STATUS_TRAIN_STUCK;
2587  } else if (v->type == VEH_AIRCRAFT && HasBit(Aircraft::From(v)->flags, VAF_DEST_TOO_FAR) && !v->current_order.IsType(OT_LOADING)) {
2588  str = STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR;
2589  } else { // vehicle is in a "normal" state, show current order
2590  switch (v->current_order.GetType()) {
2591  case OT_GOTO_STATION: {
2593  SetDParam(1, v->GetDisplaySpeed());
2594  str = STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL;
2595  break;
2596  }
2597 
2598  case OT_GOTO_DEPOT: {
2599  SetDParam(0, v->type);
2601  SetDParam(2, v->GetDisplaySpeed());
2603  /* This case *only* happens when multiple nearest depot orders
2604  * follow each other (including an order list only one order: a
2605  * nearest depot order) and there are no reachable depots.
2606  * It is primarily to guard for the case that there is no
2607  * depot with index 0, which would be used as fallback for
2608  * evaluating the string in the status bar. */
2609  str = STR_EMPTY;
2610  } else if (v->current_order.GetDepotActionType() & ODATFB_HALT) {
2611  str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL;
2612  } else {
2613  str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL;
2614  }
2615  break;
2616  }
2617 
2618  case OT_LOADING:
2619  str = STR_VEHICLE_STATUS_LOADING_UNLOADING;
2620  break;
2621 
2622  case OT_GOTO_WAYPOINT: {
2623  assert(v->type == VEH_TRAIN || v->type == VEH_SHIP);
2625  str = STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL;
2626  SetDParam(1, v->GetDisplaySpeed());
2627  break;
2628  }
2629 
2630  case OT_LEAVESTATION:
2631  if (v->type != VEH_AIRCRAFT) {
2632  str = STR_VEHICLE_STATUS_LEAVING;
2633  break;
2634  }
2635  /* FALL THROUGH, if aircraft. Does this even happen? */
2636 
2637  default:
2638  if (v->GetNumManualOrders() == 0) {
2639  str = STR_VEHICLE_STATUS_NO_ORDERS_VEL;
2640  SetDParam(0, v->GetDisplaySpeed());
2641  } else {
2642  str = STR_EMPTY;
2643  }
2644  break;
2645  }
2646  }
2647 
2648  /* Draw the flag plus orders. */
2649  bool rtl = (_current_text_dir == TD_RTL);
2650  uint text_offset = max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).width, GetSpriteSize(SPR_FLAG_VEH_RUNNING).width) + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
2651  int text_left = r.left + (rtl ? (uint)WD_FRAMERECT_LEFT : text_offset);
2652  int text_right = r.right - (rtl ? text_offset : (uint)WD_FRAMERECT_RIGHT);
2653  int image_left = (rtl ? text_right + 1 : r.left) + WD_IMGBTN_LEFT;
2654  int image = ((v->vehstatus & VS_STOPPED) != 0) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING;
2655  int lowered = this->IsWidgetLowered(WID_VV_START_STOP) ? 1 : 0;
2656  DrawSprite(image, PAL_NONE, image_left + lowered, r.top + WD_IMGBTN_TOP + lowered);
2657  DrawString(text_left + lowered, text_right + lowered, r.top + WD_FRAMERECT_TOP + lowered, str, TC_FROMSTRING, SA_HOR_CENTER);
2658  }
2659 
2660  virtual void OnClick(Point pt, int widget, int click_count)
2661  {
2662  const Vehicle *v = Vehicle::Get(this->window_number);
2663 
2664  switch (widget) {
2665  case WID_VV_START_STOP: // start stop
2666  if (_ctrl_pressed) {
2667  /* Scroll to current order destination */
2668  TileIndex tile = v->current_order.GetLocation(v);
2669  if (tile != INVALID_TILE) ScrollMainWindowToTile(tile);
2670  } else {
2671  /* Start/Stop */
2672  StartStopVehicle(v, false);
2673  }
2674  break;
2675  case WID_VV_CENTER_MAIN_VIEW: {// center main view
2676  const Window *mainwindow = FindWindowById(WC_MAIN_WINDOW, 0);
2677  /* code to allow the main window to 'follow' the vehicle if the ctrl key is pressed */
2678  if (_ctrl_pressed && mainwindow->viewport->zoom <= ZOOM_LVL_OUT_4X) {
2679  mainwindow->viewport->follow_vehicle = v->index;
2680  } else {
2681  ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
2682  }
2683  break;
2684  }
2685 
2686  case WID_VV_GOTO_DEPOT: // goto hangar
2687  DoCommandP(v->tile, v->index | (_ctrl_pressed ? DEPOT_SERVICE : 0U), 0, GetCmdSendToDepot(v));
2688  break;
2689  case WID_VV_REFIT: // refit
2691  break;
2692  case WID_VV_SHOW_ORDERS: // show orders
2693  if (_ctrl_pressed) {
2695  } else {
2696  ShowOrdersWindow(v);
2697  }
2698  break;
2699  case WID_VV_SHOW_DETAILS: // show details
2701  break;
2702  case WID_VV_CLONE: // clone vehicle
2703  /* Suppress the vehicle GUI when share-cloning.
2704  * There is no point to it except for starting the vehicle.
2705  * For starting the vehicle the player has to open the depot GUI, which is
2706  * most likely already open, but is also visible in the vehicle viewport. */
2707  DoCommandP(v->tile, v->index, _ctrl_pressed ? 1 : 0,
2708  _vehicle_command_translation_table[VCT_CMD_CLONE_VEH][v->type],
2709  _ctrl_pressed ? NULL : CcCloneVehicle);
2710  break;
2711  case WID_VV_TURN_AROUND: // turn around
2712  assert(v->IsGroundVehicle());
2713  DoCommandP(v->tile, v->index, 0,
2714  _vehicle_command_translation_table[VCT_CMD_TURN_AROUND][v->type]);
2715  break;
2716  case WID_VV_FORCE_PROCEED: // force proceed
2717  assert(v->type == VEH_TRAIN);
2718  DoCommandP(v->tile, v->index, 0, CMD_FORCE_TRAIN_PROCEED | CMD_MSG(STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL));
2719  break;
2720  }
2721  }
2722 
2723  virtual void OnResize()
2724  {
2725  if (this->viewport != NULL) {
2726  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT);
2727  nvp->UpdateViewportCoordinates(this);
2728  }
2729  }
2730 
2731  virtual void OnTick()
2732  {
2733  const Vehicle *v = Vehicle::Get(this->window_number);
2734  bool veh_stopped = v->IsStoppedInDepot();
2735 
2736  /* Widget WID_VV_GOTO_DEPOT must be hidden if the vehicle is already stopped in depot.
2737  * Widget WID_VV_CLONE_VEH should then be shown, since cloning is allowed only while in depot and stopped.
2738  */
2739  PlaneSelections plane = veh_stopped ? SEL_DC_CLONE : SEL_DC_GOTO_DEPOT;
2740  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE); // Selection widget 'send to depot' / 'clone'.
2741  if (nwi->shown_plane + SEL_DC_BASEPLANE != plane) {
2742  this->SelectPlane(plane);
2743  this->SetWidgetDirty(WID_VV_SELECT_DEPOT_CLONE);
2744  }
2745  /* The same system applies to widget WID_VV_REFIT_VEH and VVW_WIDGET_TURN_AROUND.*/
2746  if (v->IsGroundVehicle()) {
2747  PlaneSelections plane = veh_stopped ? SEL_RT_REFIT : SEL_RT_TURN_AROUND;
2748  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN);
2749  if (nwi->shown_plane + SEL_RT_BASEPLANE != plane) {
2750  this->SelectPlane(plane);
2751  this->SetWidgetDirty(WID_VV_SELECT_REFIT_TURN);
2752  }
2753  }
2754  }
2755 
2761  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2762  {
2763  if (data == VIWD_AUTOREPLACE) {
2764  /* Autoreplace replaced the vehicle.
2765  * Nothing to do for this window. */
2766  return;
2767  }
2768  }
2769 
2770  virtual bool IsNewGRFInspectable() const
2771  {
2772  return ::IsNewGRFInspectable(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2773  }
2774 
2775  virtual void ShowNewGRFInspectWindow() const
2776  {
2777  ::ShowNewGRFInspectWindow(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2778  }
2779 };
2780 
2781 
2784 {
2785  AllocateWindowDescFront<VehicleViewWindow>((v->type == VEH_TRAIN) ? &_train_view_desc : &_vehicle_view_desc, v->index);
2786 }
2787 
2793 bool VehicleClicked(const Vehicle *v)
2794 {
2795  assert(v != NULL);
2796  if (!(_thd.place_mode & HT_VEHICLE)) return false;
2797 
2798  v = v->First();
2799  if (!v->IsPrimaryVehicle()) return false;
2800 
2801  return _thd.GetCallbackWnd()->OnVehicleSelect(v);
2802 }
2803 
2804 void StopGlobalFollowVehicle(const Vehicle *v)
2805 {
2807  if (w != NULL && w->viewport->follow_vehicle == v->index) {
2808  ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos, true); // lock the main view on the vehicle's last position
2810  }
2811 }
2812 
2813 
2821 void CcBuildPrimaryVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2822 {
2823  if (result.Failed()) return;
2824 
2825  const Vehicle *v = Vehicle::Get(_new_vehicle_id);
2827 }
2828 
2835 {
2836  switch (v->type) {
2837  case VEH_TRAIN:
2838  return Train::From(v)->GetDisplayImageWidth();
2839 
2840  case VEH_ROAD:
2842 
2843  default:
2844  bool rtl = _current_text_dir == TD_RTL;
2845  VehicleSpriteSeq seq;
2846  v->GetImage(rtl ? DIR_E : DIR_W, image_type, &seq);
2847  Rect rec;
2848  seq.GetBounds(&rec);
2849  return UnScaleGUI(rec.right - rec.left + 1);
2850  }
2851 }
2852 
2858 int GetVehicleWidth(const Vehicle *v, EngineImageType image_type)
2859 {
2860  if (v->type == VEH_TRAIN || v->type == VEH_ROAD) {
2861  int vehicle_width = 0;
2862  for (const Vehicle *u = v; u != NULL; u = u->Next()) {
2863  vehicle_width += GetSingleVehicleWidth(u, image_type);
2864  }
2865  return vehicle_width;
2866  } else {
2867  return GetSingleVehicleWidth(v, image_type);
2868  }
2869 }
2870 
2877 {
2878  bool rtl = _current_text_dir == TD_RTL;
2879 
2880  _cursor.sprite_count = 0;
2881  int total_width = 0;
2882  for (; v != NULL; v = v->HasArticulatedPart() ? v->GetNextArticulatedPart() : NULL) {
2883  if (total_width >= 2 * (int)VEHICLEINFO_FULL_VEHICLE_WIDTH) break;
2884 
2886  VehicleSpriteSeq seq;
2887  v->GetImage(rtl ? DIR_E : DIR_W, image_type, &seq);
2888 
2889  if (_cursor.sprite_count + seq.count > lengthof(_cursor.sprite_seq)) break;
2890 
2891  for (uint i = 0; i < seq.count; ++i) {
2892  PaletteID pal2 = (v->vehstatus & VS_CRASHED) || !seq.seq[i].pal ? pal : seq.seq[i].pal;
2893  _cursor.sprite_seq[_cursor.sprite_count].sprite = seq.seq[i].sprite;
2894  _cursor.sprite_seq[_cursor.sprite_count].pal = pal2;
2895  _cursor.sprite_pos[_cursor.sprite_count].x = rtl ? -total_width : total_width;
2896  _cursor.sprite_pos[_cursor.sprite_count].y = 0;
2897  _cursor.sprite_count++;
2898  }
2899 
2900  total_width += GetSingleVehicleWidth(v, image_type);
2901  }
2902 
2903  int offs = ((int)VEHICLEINFO_FULL_VEHICLE_WIDTH - total_width) / 2;
2904  if (rtl) offs = -offs;
2905  for (uint i = 0; i < _cursor.sprite_count; ++i) {
2906  _cursor.sprite_pos[i].x += offs;
2907  }
2908 
2909  UpdateCursorSize();
2910 }