OpenTTD
vehicle_gui.cpp
Go to the documentation of this file.
1 /* $Id: vehicle_gui.cpp 27134 2015-02-01 20:54:24Z 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 (money <= 0) {
705  SetDParam(4, -money);
706  return STR_REFIT_NEW_CAPACITY_INCOME_FROM_AIRCRAFT_REFIT;
707  } else {
708  SetDParam(4, money);
709  return STR_REFIT_NEW_CAPACITY_COST_OF_AIRCRAFT_REFIT;
710  }
711  } else {
712  if (money <= 0) {
713  SetDParam(2, -money);
714  return STR_REFIT_NEW_CAPACITY_INCOME_FROM_REFIT;
715  } else {
716  SetDParam(2, money);
717  return STR_REFIT_NEW_CAPACITY_COST_OF_REFIT;
718  }
719  }
720  }
721 
722  virtual void DrawWidget(const Rect &r, int widget) const
723  {
724  switch (widget) {
726  Vehicle *v = Vehicle::Get(this->window_number);
728  r.top + WD_FRAMERECT_TOP, INVALID_VEHICLE, EIT_IN_DETAILS, this->hscroll != NULL ? this->hscroll->GetPosition() : 0);
729 
730  /* Highlight selected vehicles. */
731  if (this->order != INVALID_VEH_ORDER_ID) break;
732  int x = 0;
733  switch (v->type) {
734  case VEH_TRAIN: {
735  VehicleSet vehicles_to_refit;
736  GetVehicleSet(vehicles_to_refit, Vehicle::Get(this->selected_vehicle), this->num_vehicles);
737 
738  int left = INT32_MIN;
739  int width = 0;
740 
741  for (Train *u = Train::From(v); u != NULL; u = u->Next()) {
742  /* Start checking. */
743  if (vehicles_to_refit.Contains(u->index) && left == INT32_MIN) {
744  left = x - this->hscroll->GetPosition() + r.left + this->vehicle_margin;
745  width = 0;
746  }
747 
748  /* Draw a selection. */
749  if ((!vehicles_to_refit.Contains(u->index) || u->Next() == NULL) && left != INT32_MIN) {
750  if (u->Next() == NULL && vehicles_to_refit.Contains(u->index)) {
751  int current_width = u->GetDisplayImageWidth();
752  width += current_width;
753  x += current_width;
754  }
755 
756  int right = Clamp(left + width, 0, r.right);
757  left = max(0, left);
758 
759  if (_current_text_dir == TD_RTL) {
760  right = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY)->current_x - left;
761  left = right - width;
762  }
763 
764  if (left != right) {
765  DrawFrameRect(left, r.top + WD_FRAMERECT_TOP, right, r.top + WD_FRAMERECT_TOP + ScaleGUITrad(14) - 1, COLOUR_WHITE, FR_BORDERONLY);
766  }
767 
768  left = INT32_MIN;
769  }
770 
771  int current_width = u->GetDisplayImageWidth();
772  width += current_width;
773  x += current_width;
774  }
775  break;
776  }
777 
778  default: break;
779  }
780  break;
781  }
782 
783  case WID_VR_MATRIX:
784  DrawVehicleRefitWindow(this->list, this->sel, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->resize.step_height, r);
785  break;
786 
787  case WID_VR_INFO:
788  if (this->cargo != NULL) {
789  StringID string = this->GetCapacityString(this->cargo);
790  if (string != INVALID_STRING_ID) {
792  r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, string);
793  }
794  }
795  break;
796  }
797  }
798 
804  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
805  {
806  switch (data) {
807  case VIWD_AUTOREPLACE: // Autoreplace replaced the vehicle; selected_vehicle became invalid.
808  case VIWD_CONSIST_CHANGED: { // The consist has changed; rebuild the entire list.
809  /* Clear the selection. */
810  Vehicle *v = Vehicle::Get(this->window_number);
811  this->selected_vehicle = v->index;
812  this->num_vehicles = UINT8_MAX;
813  /* FALL THROUGH */
814  }
815 
816  case 2: { // The vehicle selection has changed; rebuild the entire list.
817  if (!gui_scope) break;
818  this->BuildRefitList();
819 
820  /* The vehicle width has changed too. */
822  uint max_width = 0;
823 
824  /* Check the width of all cargo information strings. */
825  for (uint i = 0; i < NUM_CARGO; i++) {
826  for (uint j = 0; j < this->list[i].Length(); j++) {
827  StringID string = this->GetCapacityString(&list[i][j]);
828  if (string != INVALID_STRING_ID) {
829  Dimension dim = GetStringBoundingBox(string);
830  max_width = max(dim.width, max_width);
831  }
832  }
833  }
834 
835  if (this->information_width < max_width) {
836  this->information_width = max_width;
837  this->ReInit();
838  }
839  /* FALL THROUGH */
840  }
841 
842  case 1: // A new cargo has been selected.
843  if (!gui_scope) break;
844  this->cargo = GetRefitOption();
845  this->RefreshScrollbar();
846  break;
847  }
848  }
849 
850  int GetClickPosition(int click_x)
851  {
852  const NWidgetCore *matrix_widget = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY);
853  if (_current_text_dir == TD_RTL) click_x = matrix_widget->current_x - click_x;
854  click_x -= this->vehicle_margin;
855  if (this->hscroll != NULL) click_x += this->hscroll->GetPosition();
856 
857  return click_x;
858  }
859 
860  void SetSelectedVehicles(int drag_x)
861  {
862  drag_x = GetClickPosition(drag_x);
863 
864  int left_x = min(this->click_x, drag_x);
865  int right_x = max(this->click_x, drag_x);
866  this->num_vehicles = 0;
867 
868  Vehicle *v = Vehicle::Get(this->window_number);
869  /* Find the vehicle part that was clicked. */
870  switch (v->type) {
871  case VEH_TRAIN: {
872  /* Don't select anything if we are not clicking in the vehicle. */
873  if (left_x >= 0) {
874  const Train *u = Train::From(v);
875  bool start_counting = false;
876  for (; u != NULL; u = u->Next()) {
877  int current_width = u->GetDisplayImageWidth();
878  left_x -= current_width;
879  right_x -= current_width;
880 
881  if (left_x < 0 && !start_counting) {
882  this->selected_vehicle = u->index;
883  start_counting = true;
884 
885  /* Count the first vehicle, even if articulated part */
886  this->num_vehicles++;
887  } else if (start_counting && !u->IsArticulatedPart()) {
888  /* Do not count articulated parts */
889  this->num_vehicles++;
890  }
891 
892  if (right_x < 0) break;
893  }
894  }
895 
896  /* If the selection is not correct, clear it. */
897  if (this->num_vehicles != 0) {
898  if (_ctrl_pressed) this->num_vehicles = UINT8_MAX;
899  break;
900  }
901  /* FALL THROUGH */
902  }
903 
904  default:
905  /* Clear the selection. */
906  this->selected_vehicle = v->index;
907  this->num_vehicles = UINT8_MAX;
908  break;
909  }
910  }
911 
912  virtual void OnClick(Point pt, int widget, int click_count)
913  {
914  switch (widget) {
915  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
916  if (this->order != INVALID_VEH_ORDER_ID) break;
917  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
918  this->click_x = GetClickPosition(pt.x - nwi->pos_x);
919  this->SetSelectedVehicles(pt.x - nwi->pos_x);
921  if (!_ctrl_pressed) {
922  SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this);
923  } else {
924  /* The vehicle selection has changed. */
925  this->InvalidateData(2);
926  }
927  break;
928  }
929 
930  case WID_VR_MATRIX: { // listbox
931  this->SetSelection(this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VR_MATRIX));
932  this->SetWidgetDisabledState(WID_VR_REFIT, this->sel[0] < 0);
933  this->InvalidateData(1);
934 
935  if (click_count == 1) break;
936  /* FALL THROUGH */
937  }
938 
939  case WID_VR_REFIT: // refit button
940  if (this->cargo != NULL) {
941  const Vehicle *v = Vehicle::Get(this->window_number);
942 
943  if (this->order == INVALID_VEH_ORDER_ID) {
944  bool delete_window = this->selected_vehicle == v->index && this->num_vehicles == UINT8_MAX;
945  if (DoCommandP(v->tile, this->selected_vehicle, this->cargo->cargo | this->cargo->subtype << 8 | this->num_vehicles << 16, GetCmdRefitVeh(v)) && delete_window) delete this;
946  } else {
947  if (DoCommandP(v->tile, v->index, this->cargo->cargo | this->order << 16, CMD_ORDER_REFIT)) delete this;
948  }
949  }
950  break;
951  }
952  }
953 
954  virtual void OnMouseDrag(Point pt, int widget)
955  {
956  switch (widget) {
957  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
958  if (this->order != INVALID_VEH_ORDER_ID) break;
959  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
960  this->SetSelectedVehicles(pt.x - nwi->pos_x);
962  break;
963  }
964  }
965  }
966 
967  virtual void OnDragDrop(Point pt, int widget)
968  {
969  switch (widget) {
970  case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
971  if (this->order != INVALID_VEH_ORDER_ID) break;
972  NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
973  this->SetSelectedVehicles(pt.x - nwi->pos_x);
974  this->InvalidateData(2);
975  break;
976  }
977  }
978  }
979 
980  virtual void OnResize()
981  {
984  if (this->hscroll != NULL) this->hscroll->SetCapacityFromWidget(this, WID_VR_VEHICLE_PANEL_DISPLAY);
985  }
986 };
987 
988 static const NWidgetPart _nested_vehicle_refit_widgets[] = {
990  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
991  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VR_CAPTION), SetDataTip(STR_REFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
992  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
993  EndContainer(),
994  /* Vehicle display + scrollbar. */
999  EndContainer(),
1000  EndContainer(),
1001  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_VR_SELECT_HEADER), SetDataTip(STR_REFIT_TITLE, STR_NULL), SetResize(1, 0),
1002  /* Matrix + scrollbar. */
1004  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),
1005  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VR_SCROLLBAR),
1006  EndContainer(),
1009  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VR_REFIT), SetFill(1, 0), SetResize(1, 0),
1010  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1011  EndContainer(),
1012 };
1013 
1014 static WindowDesc _vehicle_refit_desc(
1015  WDP_AUTO, "view_vehicle_refit", 240, 174,
1018  _nested_vehicle_refit_widgets, lengthof(_nested_vehicle_refit_widgets)
1019 );
1020 
1028 void ShowVehicleRefitWindow(const Vehicle *v, VehicleOrderID order, Window *parent, bool auto_refit)
1029 {
1031  RefitWindow *w = new RefitWindow(&_vehicle_refit_desc, v, order, auto_refit);
1032  w->parent = parent;
1033 }
1034 
1036 uint ShowRefitOptionsList(int left, int right, int y, EngineID engine)
1037 {
1038  /* List of cargo types of this engine */
1039  uint32 cmask = GetUnionOfArticulatedRefitMasks(engine, false);
1040  /* List of cargo types available in this climate */
1041  uint32 lmask = _cargo_mask;
1042 
1043  /* Draw nothing if the engine is not refittable */
1044  if (HasAtMostOneBit(cmask)) return y;
1045 
1046  if (cmask == lmask) {
1047  /* Engine can be refitted to all types in this climate */
1048  SetDParam(0, STR_PURCHASE_INFO_ALL_TYPES);
1049  } else {
1050  /* Check if we are able to refit to more cargo types and unable to. If
1051  * so, invert the cargo types to list those that we can't refit to. */
1052  if (CountBits(cmask ^ lmask) < CountBits(cmask) && CountBits(cmask ^ lmask) <= 7) {
1053  cmask ^= lmask;
1054  SetDParam(0, STR_PURCHASE_INFO_ALL_BUT);
1055  } else {
1056  SetDParam(0, STR_JUST_CARGO_LIST);
1057  }
1058  SetDParam(1, cmask);
1059  }
1060 
1061  return DrawStringMultiLine(left, right, y, INT32_MAX, STR_PURCHASE_INFO_REFITTABLE_TO);
1062 }
1063 
1066 {
1067  if (HasBit(EngInfo(v->engine_type)->callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
1068  uint16 cb = GetVehicleCallback(CBID_VEHICLE_CARGO_SUFFIX, 0, 0, v->engine_type, v);
1069  if (cb != CALLBACK_FAILED) {
1071  if (cb >= 0x400 || (v->GetGRF()->grf_version < 8 && cb == 0xFF)) cb = CALLBACK_FAILED;
1072  }
1073  if (cb != CALLBACK_FAILED) {
1074  return GetGRFStringID(v->GetGRFID(), 0xD000 + cb);
1075  }
1076  }
1077  return STR_EMPTY;
1078 }
1079 
1081 static int CDECL VehicleNumberSorter(const Vehicle * const *a, const Vehicle * const *b)
1082 {
1083  return (*a)->unitnumber - (*b)->unitnumber;
1084 }
1085 
1087 static int CDECL VehicleNameSorter(const Vehicle * const *a, const Vehicle * const *b)
1088 {
1089  static char last_name[2][64];
1090 
1091  if (*a != _last_vehicle[0]) {
1092  _last_vehicle[0] = *a;
1093  SetDParam(0, (*a)->index);
1094  GetString(last_name[0], STR_VEHICLE_NAME, lastof(last_name[0]));
1095  }
1096 
1097  if (*b != _last_vehicle[1]) {
1098  _last_vehicle[1] = *b;
1099  SetDParam(0, (*b)->index);
1100  GetString(last_name[1], STR_VEHICLE_NAME, lastof(last_name[1]));
1101  }
1102 
1103  int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
1104  return (r != 0) ? r : VehicleNumberSorter(a, b);
1105 }
1106 
1108 static int CDECL VehicleAgeSorter(const Vehicle * const *a, const Vehicle * const *b)
1109 {
1110  int r = (*a)->age - (*b)->age;
1111  return (r != 0) ? r : VehicleNumberSorter(a, b);
1112 }
1113 
1115 static int CDECL VehicleProfitThisYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1116 {
1117  int r = ClampToI32((*a)->GetDisplayProfitThisYear() - (*b)->GetDisplayProfitThisYear());
1118  return (r != 0) ? r : VehicleNumberSorter(a, b);
1119 }
1120 
1122 static int CDECL VehicleProfitLastYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1123 {
1124  int r = ClampToI32((*a)->GetDisplayProfitLastYear() - (*b)->GetDisplayProfitLastYear());
1125  return (r != 0) ? r : VehicleNumberSorter(a, b);
1126 }
1127 
1129 static int CDECL VehicleCargoSorter(const Vehicle * const *a, const Vehicle * const *b)
1130 {
1131  const Vehicle *v;
1132  CargoArray diff;
1133 
1134  /* Append the cargo of the connected waggons */
1135  for (v = *a; v != NULL; v = v->Next()) diff[v->cargo_type] += v->cargo_cap;
1136  for (v = *b; v != NULL; v = v->Next()) diff[v->cargo_type] -= v->cargo_cap;
1137 
1138  int r = 0;
1139  for (CargoID i = 0; i < NUM_CARGO; i++) {
1140  r = diff[i];
1141  if (r != 0) break;
1142  }
1143 
1144  return (r != 0) ? r : VehicleNumberSorter(a, b);
1145 }
1146 
1148 static int CDECL VehicleReliabilitySorter(const Vehicle * const *a, const Vehicle * const *b)
1149 {
1150  int r = (*a)->reliability - (*b)->reliability;
1151  return (r != 0) ? r : VehicleNumberSorter(a, b);
1152 }
1153 
1155 static int CDECL VehicleMaxSpeedSorter(const Vehicle * const *a, const Vehicle * const *b)
1156 {
1157  int r = (*a)->vcache.cached_max_speed - (*b)->vcache.cached_max_speed;
1158  return (r != 0) ? r : VehicleNumberSorter(a, b);
1159 }
1160 
1162 static int CDECL VehicleModelSorter(const Vehicle * const *a, const Vehicle * const *b)
1163 {
1164  int r = (*a)->engine_type - (*b)->engine_type;
1165  return (r != 0) ? r : VehicleNumberSorter(a, b);
1166 }
1167 
1169 static int CDECL VehicleValueSorter(const Vehicle * const *a, const Vehicle * const *b)
1170 {
1171  const Vehicle *u;
1172  Money diff = 0;
1173 
1174  for (u = *a; u != NULL; u = u->Next()) diff += u->value;
1175  for (u = *b; u != NULL; u = u->Next()) diff -= u->value;
1176 
1177  int r = ClampToI32(diff);
1178  return (r != 0) ? r : VehicleNumberSorter(a, b);
1179 }
1180 
1182 static int CDECL VehicleLengthSorter(const Vehicle * const *a, const Vehicle * const *b)
1183 {
1184  int r = (*a)->GetGroundVehicleCache()->cached_total_length - (*b)->GetGroundVehicleCache()->cached_total_length;
1185  return (r != 0) ? r : VehicleNumberSorter(a, b);
1186 }
1187 
1189 static int CDECL VehicleTimeToLiveSorter(const Vehicle * const *a, const Vehicle * const *b)
1190 {
1191  int r = ClampToI32(((*a)->max_age - (*a)->age) - ((*b)->max_age - (*b)->age));
1192  return (r != 0) ? r : VehicleNumberSorter(a, b);
1193 }
1194 
1196 static int CDECL VehicleTimetableDelaySorter(const Vehicle * const *a, const Vehicle * const *b)
1197 {
1198  int r = (*a)->lateness_counter - (*b)->lateness_counter;
1199  return (r != 0) ? r : VehicleNumberSorter(a, b);
1200 }
1201 
1202 void InitializeGUI()
1203 {
1204  MemSetT(&_sorting, 0);
1205 }
1206 
1213 static inline void ChangeVehicleWindow(WindowClass window_class, VehicleID from_index, VehicleID to_index)
1214 {
1215  Window *w = FindWindowById(window_class, from_index);
1216  if (w != NULL) {
1217  /* Update window_number */
1218  w->window_number = to_index;
1219  if (w->viewport != NULL) w->viewport->follow_vehicle = to_index;
1220 
1221  /* Update vehicle drag data */
1222  if (_thd.window_class == window_class && _thd.window_number == (WindowNumber)from_index) {
1223  _thd.window_number = to_index;
1224  }
1225 
1226  /* Notify the window. */
1227  w->InvalidateData(VIWD_AUTOREPLACE, false);
1228  }
1229 }
1230 
1236 void ChangeVehicleViewWindow(VehicleID from_index, VehicleID to_index)
1237 {
1238  ChangeVehicleWindow(WC_VEHICLE_VIEW, from_index, to_index);
1239  ChangeVehicleWindow(WC_VEHICLE_ORDERS, from_index, to_index);
1240  ChangeVehicleWindow(WC_VEHICLE_REFIT, from_index, to_index);
1241  ChangeVehicleWindow(WC_VEHICLE_DETAILS, from_index, to_index);
1242  ChangeVehicleWindow(WC_VEHICLE_TIMETABLE, from_index, to_index);
1243 }
1244 
1245 static const NWidgetPart _nested_vehicle_list[] = {
1247  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1248  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VL_CAPTION),
1249  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1250  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1251  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1252  EndContainer(),
1253 
1255  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_SORT_ORDER), SetMinimalSize(81, 12), SetFill(0, 1), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1256  NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
1257  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetFill(1, 1), SetResize(1, 0),
1258  EndContainer(),
1259  EndContainer(),
1260 
1262  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),
1263  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VL_SCROLLBAR),
1264  EndContainer(),
1265 
1267  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VL_HIDE_BUTTONS),
1269  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
1270  SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
1271  NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
1273  SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
1274  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1275  SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
1276  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1277  SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
1278  EndContainer(),
1279  /* Widget to be shown for other companies hiding the previous 5 widgets. */
1280  NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1281  EndContainer(),
1282  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1283  EndContainer(),
1284 };
1285 
1286 static void DrawSmallOrderList(const Vehicle *v, int left, int right, int y, VehicleOrderID start = 0)
1287 {
1288  const Order *order = v->GetOrder(start);
1289  if (order == NULL) return;
1290 
1291  bool rtl = _current_text_dir == TD_RTL;
1292  int l_offset = rtl ? 0 : ScaleGUITrad(6);
1293  int r_offset = rtl ? ScaleGUITrad(6) : 0;
1294  int i = 0;
1295  VehicleOrderID oid = start;
1296 
1297  do {
1298  if (oid == v->cur_real_order_index) DrawString(left, right, y, STR_TINY_RIGHT_ARROW, TC_BLACK);
1299 
1300  if (order->IsType(OT_GOTO_STATION)) {
1301  SetDParam(0, order->GetDestination());
1302  DrawString(left + l_offset, right - r_offset, y, STR_TINY_BLACK_STATION);
1303 
1304  y += FONT_HEIGHT_SMALL;
1305  if (++i == 4) break;
1306  }
1307 
1308  oid++;
1309  order = order->next;
1310  if (order == NULL) {
1311  order = v->orders.list->GetFirstOrder();
1312  oid = 0;
1313  }
1314  } while (oid != start);
1315 }
1316 
1326 void DrawVehicleImage(const Vehicle *v, int left, int right, int y, VehicleID selection, EngineImageType image_type, int skip)
1327 {
1328  switch (v->type) {
1329  case VEH_TRAIN: DrawTrainImage(Train::From(v), left, right, y, selection, image_type, skip); break;
1330  case VEH_ROAD: DrawRoadVehImage(v, left, right, y, selection, image_type, skip); break;
1331  case VEH_SHIP: DrawShipImage(v, left, right, y, selection, image_type); break;
1332  case VEH_AIRCRAFT: DrawAircraftImage(v, left, right, y, selection, image_type); break;
1333  default: NOT_REACHED();
1334  }
1335 }
1336 
1343 uint GetVehicleListHeight(VehicleType type, uint divisor)
1344 {
1345  /* Name + vehicle + profit */
1346  uint base = ScaleGUITrad(GetVehicleHeight(type)) + 2 * FONT_HEIGHT_SMALL;
1347  /* Drawing of the 4 small orders + profit*/
1348  if (type >= VEH_SHIP) base = max(base, 5U * FONT_HEIGHT_SMALL);
1349 
1350  if (divisor == 1) return base;
1351 
1352  /* Make sure the height is dividable by divisor */
1353  uint rem = base % divisor;
1354  return base + (rem == 0 ? 0 : divisor - rem);
1355 }
1356 
1363 void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int line_height, const Rect &r) const
1364 {
1365  int left = r.left + WD_MATRIX_LEFT;
1366  int right = r.right - WD_MATRIX_RIGHT;
1367  int width = right - left;
1368  bool rtl = _current_text_dir == TD_RTL;
1369 
1370  int text_offset = max<int>(GetSpriteSize(SPR_PROFIT_LOT).width, GetDigitWidth() * this->unitnumber_digits) + WD_FRAMERECT_RIGHT;
1371  int text_left = left + (rtl ? 0 : text_offset);
1372  int text_right = right - (rtl ? text_offset : 0);
1373 
1374  bool show_orderlist = this->vli.vtype >= VEH_SHIP;
1375  int orderlist_left = left + (rtl ? 0 : max(ScaleGUITrad(100) + text_offset, width / 2));
1376  int orderlist_right = right - (rtl ? max(ScaleGUITrad(100) + text_offset, width / 2) : 0);
1377 
1378  int image_left = (rtl && show_orderlist) ? orderlist_right : text_left;
1379  int image_right = (!rtl && show_orderlist) ? orderlist_left : text_right;
1380 
1381  int vehicle_button_x = rtl ? right - GetSpriteSize(SPR_PROFIT_LOT).width : left;
1382 
1383  int y = r.top;
1384  uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
1385  for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
1386  const Vehicle *v = this->vehicles[i];
1387  StringID str;
1388 
1391 
1392  DrawVehicleImage(v, image_left, image_right, y + FONT_HEIGHT_SMALL - 1, selected_vehicle, EIT_IN_LIST, 0);
1393  DrawString(text_left, text_right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1, STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR);
1394 
1395  if (v->name != NULL) {
1396  /* The vehicle got a name so we will print it */
1397  SetDParam(0, v->index);
1398  DrawString(text_left, text_right, y, STR_TINY_BLACK_VEHICLE);
1399  } else if (v->group_id != DEFAULT_GROUP) {
1400  /* The vehicle has no name, but is member of a group, so print group name */
1401  SetDParam(0, v->group_id);
1402  DrawString(text_left, text_right, y, STR_TINY_GROUP, TC_BLACK);
1403  }
1404 
1405  if (show_orderlist) DrawSmallOrderList(v, orderlist_left, orderlist_right, y, v->cur_real_order_index);
1406 
1407  if (v->IsChainInDepot()) {
1408  str = STR_BLUE_COMMA;
1409  } else {
1410  str = (v->age > v->max_age - DAYS_IN_LEAP_YEAR) ? STR_RED_COMMA : STR_BLACK_COMMA;
1411  }
1412 
1413  SetDParam(0, v->unitnumber);
1414  DrawString(left, right, y + 2, str);
1415 
1416  DrawVehicleProfitButton(v, vehicle_button_x, y + FONT_HEIGHT_NORMAL + 3);
1417 
1418  y += line_height;
1419  }
1420 }
1421 
1432 private:
1437  };
1438 
1439 public:
1441  {
1442  /* Set up sorting. Make the window-specific _sorting variable
1443  * point to the correct global _sorting struct so we are freed
1444  * from having conditionals during window operation */
1445  switch (this->vli.vtype) {
1446  case VEH_TRAIN: this->sorting = &_sorting.train; break;
1447  case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
1448  case VEH_SHIP: this->sorting = &_sorting.ship; break;
1449  case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
1450  default: NOT_REACHED();
1451  }
1452 
1453  this->CreateNestedTree();
1454 
1455  this->vscroll = this->GetScrollbar(WID_VL_SCROLLBAR);
1456 
1457  this->vehicles.SetListing(*this->sorting);
1458  this->vehicles.ForceRebuild();
1459  this->vehicles.NeedResort();
1460  this->BuildVehicleList();
1461  this->SortVehicleList();
1462 
1463  /* Set up the window widgets */
1464  this->GetWidget<NWidgetCore>(WID_VL_LIST)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
1465 
1466  if (this->vli.type == VL_SHARED_ORDERS) {
1467  this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_SHARED_ORDERS_LIST_CAPTION;
1468  } else {
1469  this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
1470  }
1471 
1472  this->FinishInitNested(window_number);
1473  if (this->vli.company != OWNER_NONE) this->owner = this->vli.company;
1474  }
1475 
1477  {
1478  *this->sorting = this->vehicles.GetListing();
1479  }
1480 
1481  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1482  {
1483  switch (widget) {
1484  case WID_VL_LIST:
1485  resize->height = GetVehicleListHeight(this->vli.vtype, 1);
1486 
1487  switch (this->vli.vtype) {
1488  case VEH_TRAIN:
1489  case VEH_ROAD:
1490  size->height = 6 * resize->height;
1491  break;
1492  case VEH_SHIP:
1493  case VEH_AIRCRAFT:
1494  size->height = 4 * resize->height;
1495  break;
1496  default: NOT_REACHED();
1497  }
1498  break;
1499 
1500  case WID_VL_SORT_ORDER: {
1501  Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1502  d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1503  d.height += padding.height;
1504  *size = maxdim(*size, d);
1505  break;
1506  }
1507 
1509  Dimension d = this->GetActionDropdownSize(this->vli.type == VL_STANDARD, false);
1510  d.height += padding.height;
1511  d.width += padding.width;
1512  *size = maxdim(*size, d);
1513  break;
1514  }
1515  }
1516  }
1517 
1518  virtual void SetStringParameters(int widget) const
1519  {
1520  switch (widget) {
1522  SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
1523  break;
1524 
1525  case WID_VL_CAPTION: {
1526  switch (this->vli.type) {
1527  case VL_SHARED_ORDERS: // Shared Orders
1528  if (this->vehicles.Length() == 0) {
1529  /* We can't open this window without vehicles using this order
1530  * and we should close the window when deleting the order. */
1531  NOT_REACHED();
1532  }
1533  SetDParam(0, this->vscroll->GetCount());
1534  break;
1535 
1536  case VL_STANDARD: // Company Name
1537  SetDParam(0, STR_COMPANY_NAME);
1538  SetDParam(1, this->vli.index);
1539  SetDParam(3, this->vscroll->GetCount());
1540  break;
1541 
1542  case VL_STATION_LIST: // Station/Waypoint Name
1543  SetDParam(0, Station::IsExpected(BaseStation::Get(this->vli.index)) ? STR_STATION_NAME : STR_WAYPOINT_NAME);
1544  SetDParam(1, this->vli.index);
1545  SetDParam(3, this->vscroll->GetCount());
1546  break;
1547 
1548  case VL_DEPOT_LIST:
1549  SetDParam(0, STR_DEPOT_CAPTION);
1550  SetDParam(1, this->vli.vtype);
1551  SetDParam(2, this->vli.index);
1552  SetDParam(3, this->vscroll->GetCount());
1553  break;
1554  default: NOT_REACHED();
1555  }
1556  break;
1557  }
1558  }
1559  }
1560 
1561  virtual void DrawWidget(const Rect &r, int widget) const
1562  {
1563  switch (widget) {
1564  case WID_VL_SORT_ORDER:
1565  /* draw arrow pointing up/down for ascending/descending sorting */
1566  this->DrawSortButtonState(widget, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1567  break;
1568 
1569  case WID_VL_LIST:
1571  break;
1572  }
1573  }
1574 
1575  virtual void OnPaint()
1576  {
1577  this->BuildVehicleList();
1578  this->SortVehicleList();
1579 
1580  if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_VL_MANAGE_VEHICLES_DROPDOWN)) {
1581  HideDropDownMenu(this);
1582  }
1583 
1584  /* Hide the widgets that we will not use in this window
1585  * Some windows contains actions only fit for the owner */
1586  int plane_to_show = (this->owner == _local_company) ? BP_SHOW_BUTTONS : BP_HIDE_BUTTONS;
1587  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VL_HIDE_BUTTONS);
1588  if (plane_to_show != nwi->shown_plane) {
1589  nwi->SetDisplayedPlane(plane_to_show);
1590  nwi->SetDirty(this);
1591  }
1592  if (this->owner == _local_company) {
1593  this->SetWidgetDisabledState(WID_VL_AVAILABLE_VEHICLES, this->vli.type != VL_STANDARD);
1594  this->SetWidgetsDisabledState(this->vehicles.Length() == 0,
1598  WIDGET_LIST_END);
1599  }
1600 
1601  /* Set text of sort by dropdown widget. */
1602  this->GetWidget<NWidgetCore>(WID_VL_SORT_BY_PULLDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
1603 
1604  this->DrawWidgets();
1605  }
1606 
1607  virtual void OnClick(Point pt, int widget, int click_count)
1608  {
1609  switch (widget) {
1610  case WID_VL_SORT_ORDER: // Flip sorting method ascending/descending
1611  this->vehicles.ToggleSortOrder();
1612  this->SetDirty();
1613  break;
1614 
1615  case WID_VL_SORT_BY_PULLDOWN:// Select sorting criteria dropdown menu
1616  ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_VL_SORT_BY_PULLDOWN, 0,
1617  (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10));
1618  return;
1619 
1620  case WID_VL_LIST: { // Matrix to show vehicles
1621  uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VL_LIST);
1622  if (id_v >= this->vehicles.Length()) return; // click out of list bound
1623 
1624  const Vehicle *v = this->vehicles[id_v];
1626  break;
1627  }
1628 
1630  ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
1631  break;
1632 
1634  DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier(this->window_number).type == VL_STANDARD, false);
1636  break;
1637  }
1638 
1639  case WID_VL_STOP_ALL:
1640  case WID_VL_START_ALL:
1641  DoCommandP(0, (1 << 1) | (widget == WID_VL_START_ALL ? (1 << 0) : 0), this->window_number, CMD_MASS_START_STOP);
1642  break;
1643  }
1644  }
1645 
1646  virtual void OnDropdownSelect(int widget, int index)
1647  {
1648  switch (widget) {
1650  this->vehicles.SetSortType(index);
1651  break;
1653  assert(this->vehicles.Length() != 0);
1654 
1655  switch (index) {
1656  case ADI_REPLACE: // Replace window
1658  break;
1659  case ADI_SERVICE: // Send for servicing
1660  case ADI_DEPOT: // Send to Depots
1661  DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : (DepotCommand)0), this->window_number, GetCmdSendToDepot(this->vli.vtype));
1662  break;
1663 
1664  default: NOT_REACHED();
1665  }
1666  break;
1667  default: NOT_REACHED();
1668  }
1669  this->SetDirty();
1670  }
1671 
1672  virtual void OnTick()
1673  {
1674  if (_pause_mode != PM_UNPAUSED) return;
1675  if (this->vehicles.NeedResort()) {
1676  StationID station = (this->vli.type == VL_STATION_LIST) ? this->vli.index : INVALID_STATION;
1677 
1678  DEBUG(misc, 3, "Periodic resort %d list company %d at station %d", this->vli.vtype, this->owner, station);
1679  this->SetDirty();
1680  }
1681  }
1682 
1683  virtual void OnResize()
1684  {
1685  this->vscroll->SetCapacityFromWidget(this, WID_VL_LIST);
1686  }
1687 
1693  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1694  {
1695  if (!gui_scope && HasBit(data, 31) && this->vli.type == VL_SHARED_ORDERS) {
1696  /* Needs to be done in command-scope, so everything stays valid */
1697  this->vli.index = GB(data, 0, 20);
1698  this->window_number = this->vli.Pack();
1699  this->vehicles.ForceRebuild();
1700  return;
1701  }
1702 
1703  if (data == 0) {
1704  /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1705  this->vehicles.ForceRebuild();
1706  } else {
1707  this->vehicles.ForceResort();
1708  }
1709  }
1710 };
1711 
1712 static WindowDesc _vehicle_list_other_desc(
1713  WDP_AUTO, "list_vehicles", 260, 246,
1715  0,
1716  _nested_vehicle_list, lengthof(_nested_vehicle_list)
1717 );
1718 
1719 static WindowDesc _vehicle_list_train_desc(
1720  WDP_AUTO, "list_vehicles_train", 325, 246,
1722  0,
1723  _nested_vehicle_list, lengthof(_nested_vehicle_list)
1724 );
1725 
1726 static void ShowVehicleListWindowLocal(CompanyID company, VehicleListType vlt, VehicleType vehicle_type, uint32 unique_number)
1727 {
1728  if (!Company::IsValidID(company) && company != OWNER_NONE) return;
1729 
1730  WindowNumber num = VehicleListIdentifier(vlt, vehicle_type, company, unique_number).Pack();
1731  if (vehicle_type == VEH_TRAIN) {
1732  AllocateWindowDescFront<VehicleListWindow>(&_vehicle_list_train_desc, num);
1733  } else {
1734  _vehicle_list_other_desc.cls = GetWindowClassForVehicleType(vehicle_type);
1735  AllocateWindowDescFront<VehicleListWindow>(&_vehicle_list_other_desc, num);
1736  }
1737 }
1738 
1739 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type)
1740 {
1741  /* If _settings_client.gui.advanced_vehicle_list > 1, display the Advanced list
1742  * if _settings_client.gui.advanced_vehicle_list == 1, display Advanced list only for local company
1743  * if _ctrl_pressed, do the opposite action (Advanced list x Normal list)
1744  */
1745 
1746  if ((_settings_client.gui.advanced_vehicle_list > (uint)(company != _local_company)) != _ctrl_pressed) {
1747  ShowCompanyGroup(company, vehicle_type);
1748  } else {
1749  ShowVehicleListWindowLocal(company, VL_STANDARD, vehicle_type, company);
1750  }
1751 }
1752 
1753 void ShowVehicleListWindow(const Vehicle *v)
1754 {
1755  ShowVehicleListWindowLocal(v->owner, VL_SHARED_ORDERS, v->type, v->FirstShared()->index);
1756 }
1757 
1758 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station)
1759 {
1760  ShowVehicleListWindowLocal(company, VL_STATION_LIST, vehicle_type, station);
1761 }
1762 
1763 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile)
1764 {
1765  uint16 depot_airport_index;
1766 
1767  if (vehicle_type == VEH_AIRCRAFT) {
1768  depot_airport_index = GetStationIndex(depot_tile);
1769  } else {
1770  depot_airport_index = GetDepotIndex(depot_tile);
1771  }
1772  ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_airport_index);
1773 }
1774 
1775 
1776 /* Unified vehicle GUI - Vehicle Details Window */
1777 
1782 
1786  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1787  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1788  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 */),
1789  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1790  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1791  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1792  EndContainer(),
1793  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetMinimalSize(405, 42), SetResize(1, 0), EndContainer(),
1794  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_MIDDLE_DETAILS), SetMinimalSize(405, 45), SetResize(1, 0), EndContainer(),
1797  SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1799  SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_INCREASE_SERVICING_INTERVAL_TOOLTIP),
1801  SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1802  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1803  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1804  EndContainer(),
1805 };
1806 
1810  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1811  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1812  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 */),
1813  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1814  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1815  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1816  EndContainer(),
1817  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetResize(1, 0), SetMinimalSize(405, 42), EndContainer(),
1819  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),
1820  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VD_SCROLLBAR),
1821  EndContainer(),
1824  SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1826  SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1828  SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1829  NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1830  EndContainer(),
1833  SetDataTip(STR_VEHICLE_DETAIL_TAB_CARGO, STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1835  SetDataTip(STR_VEHICLE_DETAIL_TAB_INFORMATION, STR_VEHICLE_DETAILS_TRAIN_INFORMATION_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1837  SetDataTip(STR_VEHICLE_DETAIL_TAB_CAPACITIES, STR_VEHICLE_DETAILS_TRAIN_CAPACITIES_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1839  SetDataTip(STR_VEHICLE_DETAIL_TAB_TOTAL_CARGO, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1840  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1841  EndContainer(),
1842 };
1843 
1844 
1845 extern int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab);
1846 extern void DrawTrainDetails(const Train *v, int left, int right, int y, int vscroll_pos, uint16 vscroll_cap, TrainDetailsWindowTabs det_tab);
1847 extern void DrawRoadVehDetails(const Vehicle *v, int left, int right, int y);
1848 extern void DrawShipDetails(const Vehicle *v, int left, int right, int y);
1849 extern void DrawAircraftDetails(const Aircraft *v, int left, int right, int y);
1850 
1851 static StringID _service_interval_dropdown[] = {
1852  STR_VEHICLE_DETAILS_DEFAULT,
1853  STR_VEHICLE_DETAILS_DAYS,
1854  STR_VEHICLE_DETAILS_PERCENT,
1856 };
1857 
1861  Scrollbar *vscroll;
1862 
1864  VehicleDetailsWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
1865  {
1866  const Vehicle *v = Vehicle::Get(window_number);
1867 
1868  this->CreateNestedTree();
1869  this->vscroll = (v->type == VEH_TRAIN ? this->GetScrollbar(WID_VD_SCROLLBAR) : NULL);
1870  this->FinishInitNested(window_number);
1871 
1872  this->GetWidget<NWidgetCore>(WID_VD_RENAME_VEHICLE)->tool_tip = STR_VEHICLE_DETAILS_TRAIN_RENAME + v->type;
1873 
1874  this->owner = v->owner;
1875  this->tab = TDW_TAB_CARGO;
1876  }
1877 
1883  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1884  {
1885  if (data == VIWD_AUTOREPLACE) {
1886  /* Autoreplace replaced the vehicle.
1887  * Nothing to do for this window. */
1888  return;
1889  }
1890  if (!gui_scope) return;
1891  const Vehicle *v = Vehicle::Get(this->window_number);
1892  if (v->type == VEH_ROAD) {
1893  const NWidgetBase *nwid_info = this->GetWidget<NWidgetBase>(WID_VD_MIDDLE_DETAILS);
1894  uint aimed_height = this->GetRoadVehDetailsHeight(v);
1895  /* If the number of articulated parts changes, the size of the window must change too. */
1896  if (aimed_height != nwid_info->current_y) {
1897  this->ReInit();
1898  }
1899  }
1900  }
1901 
1908  {
1909  uint desired_height;
1910  if (v->HasArticulatedPart()) {
1911  /* An articulated RV has its text drawn under the sprite instead of after it, hence 15 pixels extra. */
1912  desired_height = WD_FRAMERECT_TOP + ScaleGUITrad(15) + 3 * FONT_HEIGHT_NORMAL + 2 + WD_FRAMERECT_BOTTOM;
1913  /* Add space for the cargo amount for each part. */
1914  for (const Vehicle *u = v; u != NULL; u = u->Next()) {
1915  if (u->cargo_cap != 0) desired_height += FONT_HEIGHT_NORMAL + 1;
1916  }
1917  } else {
1918  desired_height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
1919  }
1920  return desired_height;
1921  }
1922 
1923  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1924  {
1925  switch (widget) {
1926  case WID_VD_TOP_DETAILS: {
1927  Dimension dim = { 0, 0 };
1929 
1930  for (uint i = 0; i < 4; i++) SetDParamMaxValue(i, INT16_MAX);
1931  static const StringID info_strings[] = {
1932  STR_VEHICLE_INFO_MAX_SPEED,
1933  STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED,
1934  STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE,
1935  STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR,
1936  STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS
1937  };
1938  for (uint i = 0; i < lengthof(info_strings); i++) {
1939  dim = maxdim(dim, GetStringBoundingBox(info_strings[i]));
1940  }
1941  SetDParam(0, STR_VEHICLE_INFO_AGE);
1942  dim = maxdim(dim, GetStringBoundingBox(STR_VEHICLE_INFO_AGE_RUNNING_COST_YR));
1943  size->width = dim.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
1944  break;
1945  }
1946 
1947  case WID_VD_MIDDLE_DETAILS: {
1948  const Vehicle *v = Vehicle::Get(this->window_number);
1949  switch (v->type) {
1950  case VEH_ROAD:
1951  size->height = this->GetRoadVehDetailsHeight(v);
1952  break;
1953 
1954  case VEH_SHIP:
1955  size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
1956  break;
1957 
1958  case VEH_AIRCRAFT:
1959  size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + 4 + WD_FRAMERECT_BOTTOM;
1960  break;
1961 
1962  default:
1963  NOT_REACHED(); // Train uses WID_VD_MATRIX instead.
1964  }
1965  break;
1966  }
1967 
1968  case WID_VD_MATRIX:
1970  size->height = 4 * resize->height;
1971  break;
1972 
1974  StringID *strs = _service_interval_dropdown;
1975  while (*strs != INVALID_STRING_ID) {
1976  *size = maxdim(*size, GetStringBoundingBox(*strs++));
1977  }
1978  size->width += padding.width;
1980  break;
1981  }
1982 
1984  SetDParamMaxValue(0, MAX_SERVINT_DAYS); // Roughly the maximum interval
1985  SetDParamMaxValue(1, MAX_YEAR * DAYS_IN_YEAR); // Roughly the maximum year
1986  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;
1988  break;
1989  }
1990  }
1991 
1993  static bool IsVehicleServiceIntervalEnabled(const VehicleType vehicle_type, CompanyID company_id)
1994  {
1995  const VehicleDefaultSettings *vds = &Company::Get(company_id)->settings.vehicle;
1996  switch (vehicle_type) {
1997  default: NOT_REACHED();
1998  case VEH_TRAIN: return vds->servint_trains != 0;
1999  case VEH_ROAD: return vds->servint_roadveh != 0;
2000  case VEH_SHIP: return vds->servint_ships != 0;
2001  case VEH_AIRCRAFT: return vds->servint_aircraft != 0;
2002  }
2003  }
2004 
2016  static void DrawVehicleDetails(const Vehicle *v, int left, int right, int y, int vscroll_pos, uint vscroll_cap, TrainDetailsWindowTabs det_tab)
2017  {
2018  switch (v->type) {
2019  case VEH_TRAIN: DrawTrainDetails(Train::From(v), left, right, y, vscroll_pos, vscroll_cap, det_tab); break;
2020  case VEH_ROAD: DrawRoadVehDetails(v, left, right, y); break;
2021  case VEH_SHIP: DrawShipDetails(v, left, right, y); break;
2022  case VEH_AIRCRAFT: DrawAircraftDetails(Aircraft::From(v), left, right, y); break;
2023  default: NOT_REACHED();
2024  }
2025  }
2026 
2027  virtual void SetStringParameters(int widget) const
2028  {
2029  if (widget == WID_VD_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
2030  }
2031 
2032  virtual void DrawWidget(const Rect &r, int widget) const
2033  {
2034  const Vehicle *v = Vehicle::Get(this->window_number);
2035 
2036  switch (widget) {
2037  case WID_VD_TOP_DETAILS: {
2038  int y = r.top + WD_FRAMERECT_TOP;
2039 
2040  /* Draw running cost */
2041  SetDParam(1, v->age / DAYS_IN_LEAP_YEAR);
2042  SetDParam(0, (v->age + DAYS_IN_YEAR < v->max_age) ? STR_VEHICLE_INFO_AGE : STR_VEHICLE_INFO_AGE_RED);
2045  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_AGE_RUNNING_COST_YR);
2046  y += FONT_HEIGHT_NORMAL;
2047 
2048  /* Draw max speed */
2049  StringID string;
2050  if (v->type == VEH_TRAIN ||
2051  (v->type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL)) {
2052  const GroundVehicleCache *gcache = v->GetGroundVehicleCache();
2053  SetDParam(2, v->GetDisplayMaxSpeed());
2054  SetDParam(1, gcache->cached_power);
2055  SetDParam(0, gcache->cached_weight);
2056  SetDParam(3, gcache->cached_max_te / 1000);
2057  if (v->type == VEH_TRAIN && (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL ||
2058  GetRailTypeInfo(Train::From(v)->railtype)->acceleration_type == 2)) {
2059  string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED;
2060  } else {
2061  string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE;
2062  }
2063  } else {
2064  SetDParam(0, v->GetDisplayMaxSpeed());
2065  if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->GetRange() > 0) {
2066  SetDParam(1, Aircraft::From(v)->GetRange());
2067  string = STR_VEHICLE_INFO_MAX_SPEED_RANGE;
2068  } else {
2069  string = STR_VEHICLE_INFO_MAX_SPEED;
2070  }
2071  }
2072  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, string);
2073  y += FONT_HEIGHT_NORMAL;
2074 
2075  /* Draw profit */
2078  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR);
2079  y += FONT_HEIGHT_NORMAL;
2080 
2081  /* Draw breakdown & reliability */
2084  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS);
2085  break;
2086  }
2087 
2088  case WID_VD_MATRIX:
2089  /* For trains only. */
2090  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);
2091  break;
2092 
2093  case WID_VD_MIDDLE_DETAILS: {
2094  /* For other vehicles, at the place of the matrix. */
2095  bool rtl = _current_text_dir == TD_RTL;
2096  uint sprite_width = UnScaleGUI(
2097  max<uint>(GetSprite(v->GetImage(rtl ? DIR_E : DIR_W, EIT_IN_DETAILS), ST_NORMAL)->width, 70U)) +
2099 
2100  uint text_left = r.left + (rtl ? 0 : sprite_width);
2101  uint text_right = r.right - (rtl ? sprite_width : 0);
2102 
2103  /* Articulated road vehicles use a complete line. */
2104  if (v->type == VEH_ROAD && v->HasArticulatedPart()) {
2106  } else {
2107  uint sprite_left = rtl ? text_right : r.left;
2108  uint sprite_right = rtl ? r.right : text_left;
2109 
2111  }
2112  DrawVehicleDetails(v, text_left + WD_FRAMERECT_LEFT, text_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, 0, 0, this->tab);
2113  break;
2114  }
2115 
2117  /* Draw service interval text */
2118  SetDParam(0, v->GetServiceInterval());
2120  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + (r.bottom - r.top + 1 - FONT_HEIGHT_NORMAL) / 2,
2121  v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS);
2122  break;
2123  }
2124  }
2125 
2127  virtual void OnPaint()
2128  {
2129  const Vehicle *v = Vehicle::Get(this->window_number);
2130 
2132 
2133  if (v->type == VEH_TRAIN) {
2135  this->vscroll->SetCount(GetTrainDetailsWndVScroll(v->index, this->tab));
2136  }
2137 
2138  /* Disable service-scroller when interval is set to disabled */
2142  WIDGET_LIST_END);
2143 
2144  StringID str = v->ServiceIntervalIsCustom() ?
2145  (v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_PERCENT : STR_VEHICLE_DETAILS_DAYS) :
2146  STR_VEHICLE_DETAILS_DEFAULT;
2147  this->GetWidget<NWidgetCore>(WID_VD_SERVICE_INTERVAL_DROPDOWN)->widget_data = str;
2148 
2149  this->DrawWidgets();
2150  }
2151 
2152  virtual void OnClick(Point pt, int widget, int click_count)
2153  {
2154  switch (widget) {
2155  case WID_VD_RENAME_VEHICLE: { // rename
2156  const Vehicle *v = Vehicle::Get(this->window_number);
2157  SetDParam(0, v->index);
2158  ShowQueryString(STR_VEHICLE_NAME, STR_QUERY_RENAME_TRAIN_CAPTION + v->type,
2160  break;
2161  }
2162 
2163  case WID_VD_INCREASE_SERVICING_INTERVAL: // increase int
2164  case WID_VD_DECREASE_SERVICING_INTERVAL: { // decrease int
2165  int mod = _ctrl_pressed ? 5 : 10;
2166  const Vehicle *v = Vehicle::Get(this->window_number);
2167 
2168  mod = (widget == WID_VD_DECREASE_SERVICING_INTERVAL) ? -mod : mod;
2169  mod = GetServiceIntervalClamped(mod + v->GetServiceInterval(), v->ServiceIntervalIsPercent());
2170  if (mod == v->GetServiceInterval()) return;
2171 
2172  DoCommandP(v->tile, v->index, mod | (1 << 16) | (v->ServiceIntervalIsPercent() << 17), CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_SERVICING));
2173  break;
2174  }
2175 
2177  const Vehicle *v = Vehicle::Get(this->window_number);
2178  ShowDropDownMenu(this, _service_interval_dropdown, v->ServiceIntervalIsCustom() ? (v->ServiceIntervalIsPercent() ? 2 : 1) : 0, widget, 0, 0);
2179  break;
2180  }
2181 
2186  this->SetWidgetsDisabledState(false,
2191  widget,
2192  WIDGET_LIST_END);
2193 
2195  this->SetDirty();
2196  break;
2197  }
2198  }
2199 
2200  virtual void OnDropdownSelect(int widget, int index)
2201  {
2202  switch (widget) {
2204  const Vehicle *v = Vehicle::Get(this->window_number);
2205  bool iscustom = index != 0;
2206  bool ispercent = iscustom ? (index == 2) : Company::Get(v->owner)->settings.vehicle.servint_ispercent;
2207  uint16 interval = GetServiceIntervalClamped(v->GetServiceInterval(), ispercent);
2208  DoCommandP(v->tile, v->index, interval | (iscustom << 16) | (ispercent << 17), CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_ERROR_CAN_T_CHANGE_SERVICING));
2209  break;
2210  }
2211  }
2212  }
2213 
2214  virtual void OnQueryTextFinished(char *str)
2215  {
2216  if (str == NULL) return;
2217 
2218  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);
2219  }
2220 
2221  virtual void OnResize()
2222  {
2223  NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_VD_MATRIX);
2224  if (nwi != NULL) {
2225  this->vscroll->SetCapacityFromWidget(this, WID_VD_MATRIX);
2226  }
2227  }
2228 };
2229 
2232  WDP_AUTO, "view_vehicle_details_train", 405, 178,
2234  0,
2235  _nested_train_vehicle_details_widgets, lengthof(_nested_train_vehicle_details_widgets)
2236 );
2237 
2240  WDP_AUTO, "view_vehicle_details", 405, 113,
2242  0,
2243  _nested_nontrain_vehicle_details_widgets, lengthof(_nested_nontrain_vehicle_details_widgets)
2244 );
2245 
2247 static void ShowVehicleDetailsWindow(const Vehicle *v)
2248 {
2251  AllocateWindowDescFront<VehicleDetailsWindow>((v->type == VEH_TRAIN) ? &_train_vehicle_details_desc : &_nontrain_vehicle_details_desc, v->index);
2252 }
2253 
2254 
2255 /* Unified vehicle GUI - Vehicle View Window */
2256 
2260  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2261  NWidget(WWT_CAPTION, COLOUR_GREY, WID_VV_CAPTION), SetDataTip(STR_VEHICLE_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2262  NWidget(WWT_DEBUGBOX, COLOUR_GREY),
2263  NWidget(WWT_SHADEBOX, COLOUR_GREY),
2264  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
2265  NWidget(WWT_STICKYBOX, COLOUR_GREY),
2266  EndContainer(),
2268  NWidget(WWT_PANEL, COLOUR_GREY),
2269  NWidget(WWT_INSET, COLOUR_GREY), SetPadding(2, 2, 2, 2),
2270  NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_VV_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1), SetPadding(1, 1, 1, 1),
2271  EndContainer(),
2272  EndContainer(),
2274  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CENTER_MAIN_VIEW), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(SPR_CENTRE_VIEW_VEHICLE, 0x0 /* filled later */),
2276  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_GOTO_DEPOT), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2277  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CLONE), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2278  EndContainer(),
2279  /* For trains only, 'ignore signal' button. */
2280  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_FORCE_PROCEED), SetMinimalSize(18, 18), SetFill(1, 1),
2281  SetDataTip(SPR_IGNORE_SIGNALS, STR_VEHICLE_VIEW_TRAIN_IGNORE_SIGNAL_TOOLTIP),
2283  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_REFIT), SetMinimalSize(18, 18), SetFill(1, 1), SetDataTip(SPR_REFIT_VEHICLE, 0x0 /* filled later */),
2284  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_TURN_AROUND), SetMinimalSize(18, 18), SetFill(1, 1),
2285  SetDataTip(SPR_FORCE_VEHICLE_TURN, STR_VEHICLE_VIEW_ROAD_VEHICLE_REVERSE_TOOLTIP),
2286  EndContainer(),
2287  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_ORDERS), SetFill(1, 1), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_ORDERS, 0x0 /* filled later */),
2288  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_DETAILS), SetFill(1, 1), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_VEHICLE_DETAILS, 0x0 /* filled later */),
2289  NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetMinimalSize(18, 0), SetResize(0, 1), EndContainer(),
2290  EndContainer(),
2291  EndContainer(),
2294  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
2295  EndContainer(),
2296 };
2297 
2300  WDP_AUTO, "view_vehicle", 250, 116,
2302  0,
2303  _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
2304 );
2305 
2311  WDP_AUTO, "view_vehicle_train", 250, 134,
2313  0,
2314  _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets)
2315 );
2316 
2317 
2318 /* Just to make sure, nobody has changed the vehicle type constants, as we are
2319  using them for array indexing in a number of places here. */
2320 assert_compile(VEH_TRAIN == 0);
2321 assert_compile(VEH_ROAD == 1);
2322 assert_compile(VEH_SHIP == 2);
2323 assert_compile(VEH_AIRCRAFT == 3);
2324 
2329  ZOOM_LVL_SHIP,
2331 };
2332 
2333 /* Constants for geometry of vehicle view viewport */
2334 static const int VV_INITIAL_VIEWPORT_WIDTH = 226;
2335 static const int VV_INITIAL_VIEWPORT_HEIGHT = 84;
2336 static const int VV_INITIAL_VIEWPORT_HEIGHT_TRAIN = 102;
2337 
2340  VCT_CMD_START_STOP = 0,
2341  VCT_CMD_CLONE_VEH,
2342  VCT_CMD_TURN_AROUND,
2343 };
2344 
2346 static const uint32 _vehicle_command_translation_table[][4] = {
2347  { // VCT_CMD_START_STOP
2348  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_TRAIN),
2349  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_ROAD_VEHICLE),
2350  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_SHIP),
2351  CMD_START_STOP_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_STOP_START_AIRCRAFT)
2352  },
2353  { // VCT_CMD_CLONE_VEH
2354  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_TRAIN),
2355  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_ROAD_VEHICLE),
2356  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_SHIP),
2357  CMD_CLONE_VEHICLE | CMD_MSG(STR_ERROR_CAN_T_BUY_AIRCRAFT)
2358  },
2359  { // VCT_CMD_TURN_AROUND
2360  CMD_REVERSE_TRAIN_DIRECTION | CMD_MSG(STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN),
2361  CMD_TURN_ROADVEH | CMD_MSG(STR_ERROR_CAN_T_MAKE_ROAD_VEHICLE_TURN),
2362  0xffffffff, // invalid for ships
2363  0xffffffff // invalid for aircrafts
2364  },
2365 };
2366 
2374 void CcStartStopVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2375 {
2376  if (result.Failed()) return;
2377 
2378  const Vehicle *v = Vehicle::GetIfValid(p1);
2379  if (v == NULL || !v->IsPrimaryVehicle() || v->owner != _local_company) return;
2380 
2381  StringID msg = (v->vehstatus & VS_STOPPED) ? STR_VEHICLE_COMMAND_STOPPED : STR_VEHICLE_COMMAND_STARTED;
2382  Point pt = RemapCoords(v->x_pos, v->y_pos, v->z_pos);
2383  AddTextEffect(msg, pt.x, pt.y, DAY_TICKS, TE_RISING);
2384 }
2385 
2391 void StartStopVehicle(const Vehicle *v, bool texteffect)
2392 {
2393  assert(v->IsPrimaryVehicle());
2394  DoCommandP(v->tile, v->index, 0, _vehicle_command_translation_table[VCT_CMD_START_STOP][v->type], texteffect ? CcStartStopVehicle : NULL);
2395 }
2396 
2398 static bool IsVehicleRefitable(const Vehicle *v)
2399 {
2400  if (!v->IsStoppedInDepot()) return false;
2401 
2402  do {
2403  if (IsEngineRefittable(v->engine_type)) return true;
2404  } while (v->IsGroundVehicle() && (v = v->Next()) != NULL);
2405 
2406  return false;
2407 }
2408 
2411 private:
2416 
2419 
2422  };
2423 
2429  {
2430  switch (plane) {
2431  case SEL_DC_GOTO_DEPOT:
2432  case SEL_DC_CLONE:
2433  this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE)->SetDisplayedPlane(plane - SEL_DC_BASEPLANE);
2434  break;
2435 
2436  case SEL_RT_REFIT:
2437  case SEL_RT_TURN_AROUND:
2438  this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN)->SetDisplayedPlane(plane - SEL_RT_BASEPLANE);
2439  break;
2440 
2441  default:
2442  NOT_REACHED();
2443  }
2444  }
2445 
2446 public:
2447  VehicleViewWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
2448  {
2449  this->CreateNestedTree();
2450 
2451  /* Sprites for the 'send to depot' button indexed by vehicle type. */
2452  static const SpriteID vehicle_view_goto_depot_sprites[] = {
2453  SPR_SEND_TRAIN_TODEPOT,
2454  SPR_SEND_ROADVEH_TODEPOT,
2455  SPR_SEND_SHIP_TODEPOT,
2456  SPR_SEND_AIRCRAFT_TODEPOT,
2457  };
2458  const Vehicle *v = Vehicle::Get(window_number);
2459  this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->widget_data = vehicle_view_goto_depot_sprites[v->type];
2460 
2461  /* Sprites for the 'clone vehicle' button indexed by vehicle type. */
2462  static const SpriteID vehicle_view_clone_sprites[] = {
2464  SPR_CLONE_ROADVEH,
2465  SPR_CLONE_SHIP,
2466  SPR_CLONE_AIRCRAFT,
2467  };
2468  this->GetWidget<NWidgetCore>(WID_VV_CLONE)->widget_data = vehicle_view_clone_sprites[v->type];
2469 
2470  switch (v->type) {
2471  case VEH_TRAIN:
2472  this->GetWidget<NWidgetCore>(WID_VV_TURN_AROUND)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REVERSE_TOOLTIP;
2473  break;
2474 
2475  case VEH_ROAD:
2476  break;
2477 
2478  case VEH_SHIP:
2479  case VEH_AIRCRAFT:
2480  this->SelectPlane(SEL_RT_REFIT);
2481  break;
2482 
2483  default: NOT_REACHED();
2484  }
2485  this->FinishInitNested(window_number);
2486  this->owner = v->owner;
2487  this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT)->InitializeViewport(this, this->window_number | (1 << 31), _vehicle_view_zoom_levels[v->type]);
2488 
2489  this->GetWidget<NWidgetCore>(WID_VV_START_STOP)->tool_tip = STR_VEHICLE_VIEW_TRAIN_STATE_START_STOP_TOOLTIP + v->type;
2490  this->GetWidget<NWidgetCore>(WID_VV_CENTER_MAIN_VIEW)->tool_tip = STR_VEHICLE_VIEW_TRAIN_LOCATION_TOOLTIP + v->type;
2491  this->GetWidget<NWidgetCore>(WID_VV_REFIT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REFIT_TOOLTIP + v->type;
2492  this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SEND_TO_DEPOT_TOOLTIP + v->type;
2493  this->GetWidget<NWidgetCore>(WID_VV_SHOW_ORDERS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_ORDERS_TOOLTIP + v->type;
2494  this->GetWidget<NWidgetCore>(WID_VV_SHOW_DETAILS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SHOW_DETAILS_TOOLTIP + v->type;
2495  this->GetWidget<NWidgetCore>(WID_VV_CLONE)->tool_tip = STR_VEHICLE_VIEW_CLONE_TRAIN_INFO + v->type;
2496  }
2497 
2499  {
2500  DeleteWindowById(WC_VEHICLE_ORDERS, this->window_number, false);
2501  DeleteWindowById(WC_VEHICLE_REFIT, this->window_number, false);
2502  DeleteWindowById(WC_VEHICLE_DETAILS, this->window_number, false);
2503  DeleteWindowById(WC_VEHICLE_TIMETABLE, this->window_number, false);
2504  }
2505 
2506  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2507  {
2508  const Vehicle *v = Vehicle::Get(this->window_number);
2509  switch (widget) {
2510  case WID_VV_START_STOP:
2511  size->height = max(size->height, max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).height, GetSpriteSize(SPR_FLAG_VEH_RUNNING).height) + WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
2512  break;
2513 
2514  case WID_VV_FORCE_PROCEED:
2515  if (v->type != VEH_TRAIN) {
2516  size->height = 0;
2517  size->width = 0;
2518  }
2519  break;
2520 
2521  case WID_VV_VIEWPORT:
2522  size->width = VV_INITIAL_VIEWPORT_WIDTH;
2523  size->height = (v->type == VEH_TRAIN) ? VV_INITIAL_VIEWPORT_HEIGHT_TRAIN : VV_INITIAL_VIEWPORT_HEIGHT;
2524  break;
2525  }
2526  }
2527 
2528  virtual void OnPaint()
2529  {
2530  const Vehicle *v = Vehicle::Get(this->window_number);
2531  bool is_localcompany = v->owner == _local_company;
2532  bool refitable_and_stopped_in_depot = IsVehicleRefitable(v);
2533 
2534  this->SetWidgetDisabledState(WID_VV_GOTO_DEPOT, !is_localcompany);
2535  this->SetWidgetDisabledState(WID_VV_REFIT, !refitable_and_stopped_in_depot || !is_localcompany);
2536  this->SetWidgetDisabledState(WID_VV_CLONE, !is_localcompany);
2537 
2538  if (v->type == VEH_TRAIN) {
2539  this->SetWidgetLoweredState(WID_VV_FORCE_PROCEED, Train::From(v)->force_proceed == TFP_SIGNAL);
2540  this->SetWidgetDisabledState(WID_VV_FORCE_PROCEED, !is_localcompany);
2541  this->SetWidgetDisabledState(WID_VV_TURN_AROUND, !is_localcompany);
2542  }
2543 
2544  this->DrawWidgets();
2545  }
2546 
2547  virtual void SetStringParameters(int widget) const
2548  {
2549  if (widget != WID_VV_CAPTION) return;
2550 
2551  const Vehicle *v = Vehicle::Get(this->window_number);
2552  SetDParam(0, v->index);
2553  }
2554 
2555  virtual void DrawWidget(const Rect &r, int widget) const
2556  {
2557  if (widget != WID_VV_START_STOP) return;
2558 
2559  const Vehicle *v = Vehicle::Get(this->window_number);
2560  StringID str;
2561  if (v->vehstatus & VS_CRASHED) {
2562  str = STR_VEHICLE_STATUS_CRASHED;
2563  } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary?
2564  str = STR_VEHICLE_STATUS_BROKEN_DOWN;
2565  } else if (v->vehstatus & VS_STOPPED) {
2566  if (v->type == VEH_TRAIN) {
2567  if (v->cur_speed == 0) {
2568  if (Train::From(v)->gcache.cached_power == 0) {
2569  str = STR_VEHICLE_STATUS_TRAIN_NO_POWER;
2570  } else {
2571  str = STR_VEHICLE_STATUS_STOPPED;
2572  }
2573  } else {
2574  SetDParam(0, v->GetDisplaySpeed());
2575  str = STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL;
2576  }
2577  } else { // no train
2578  str = STR_VEHICLE_STATUS_STOPPED;
2579  }
2580  } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) {
2581  str = STR_VEHICLE_STATUS_TRAIN_STUCK;
2582  } else if (v->type == VEH_AIRCRAFT && HasBit(Aircraft::From(v)->flags, VAF_DEST_TOO_FAR) && !v->current_order.IsType(OT_LOADING)) {
2583  str = STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR;
2584  } else { // vehicle is in a "normal" state, show current order
2585  switch (v->current_order.GetType()) {
2586  case OT_GOTO_STATION: {
2588  SetDParam(1, v->GetDisplaySpeed());
2589  str = STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL;
2590  break;
2591  }
2592 
2593  case OT_GOTO_DEPOT: {
2594  SetDParam(0, v->type);
2596  SetDParam(2, v->GetDisplaySpeed());
2598  /* This case *only* happens when multiple nearest depot orders
2599  * follow each other (including an order list only one order: a
2600  * nearest depot order) and there are no reachable depots.
2601  * It is primarily to guard for the case that there is no
2602  * depot with index 0, which would be used as fallback for
2603  * evaluating the string in the status bar. */
2604  str = STR_EMPTY;
2605  } else if (v->current_order.GetDepotActionType() & ODATFB_HALT) {
2606  str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL;
2607  } else {
2608  str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL;
2609  }
2610  break;
2611  }
2612 
2613  case OT_LOADING:
2614  str = STR_VEHICLE_STATUS_LOADING_UNLOADING;
2615  break;
2616 
2617  case OT_GOTO_WAYPOINT: {
2618  assert(v->type == VEH_TRAIN || v->type == VEH_SHIP);
2620  str = STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL;
2621  SetDParam(1, v->GetDisplaySpeed());
2622  break;
2623  }
2624 
2625  case OT_LEAVESTATION:
2626  if (v->type != VEH_AIRCRAFT) {
2627  str = STR_VEHICLE_STATUS_LEAVING;
2628  break;
2629  }
2630  /* FALL THROUGH, if aircraft. Does this even happen? */
2631 
2632  default:
2633  if (v->GetNumManualOrders() == 0) {
2634  str = STR_VEHICLE_STATUS_NO_ORDERS_VEL;
2635  SetDParam(0, v->GetDisplaySpeed());
2636  } else {
2637  str = STR_EMPTY;
2638  }
2639  break;
2640  }
2641  }
2642 
2643  /* Draw the flag plus orders. */
2644  bool rtl = (_current_text_dir == TD_RTL);
2645  uint text_offset = max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).width, GetSpriteSize(SPR_FLAG_VEH_RUNNING).width) + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
2646  int text_left = r.left + (rtl ? (uint)WD_FRAMERECT_LEFT : text_offset);
2647  int text_right = r.right - (rtl ? text_offset : (uint)WD_FRAMERECT_RIGHT);
2648  int image_left = (rtl ? text_right + 1 : r.left) + WD_IMGBTN_LEFT;
2649  int image = ((v->vehstatus & VS_STOPPED) != 0) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING;
2650  int lowered = this->IsWidgetLowered(WID_VV_START_STOP) ? 1 : 0;
2651  DrawSprite(image, PAL_NONE, image_left + lowered, r.top + WD_IMGBTN_TOP + lowered);
2652  DrawString(text_left + lowered, text_right + lowered, r.top + WD_FRAMERECT_TOP + lowered, str, TC_FROMSTRING, SA_HOR_CENTER);
2653  }
2654 
2655  virtual void OnClick(Point pt, int widget, int click_count)
2656  {
2657  const Vehicle *v = Vehicle::Get(this->window_number);
2658 
2659  switch (widget) {
2660  case WID_VV_START_STOP: // start stop
2661  if (_ctrl_pressed) {
2662  /* Scroll to current order destination */
2663  TileIndex tile = v->current_order.GetLocation(v);
2664  if (tile != INVALID_TILE) ScrollMainWindowToTile(tile);
2665  } else {
2666  /* Start/Stop */
2667  StartStopVehicle(v, false);
2668  }
2669  break;
2670  case WID_VV_CENTER_MAIN_VIEW: {// center main view
2671  const Window *mainwindow = FindWindowById(WC_MAIN_WINDOW, 0);
2672  /* code to allow the main window to 'follow' the vehicle if the ctrl key is pressed */
2673  if (_ctrl_pressed && mainwindow->viewport->zoom <= ZOOM_LVL_OUT_4X) {
2674  mainwindow->viewport->follow_vehicle = v->index;
2675  } else {
2676  ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
2677  }
2678  break;
2679  }
2680 
2681  case WID_VV_GOTO_DEPOT: // goto hangar
2682  DoCommandP(v->tile, v->index | (_ctrl_pressed ? DEPOT_SERVICE : 0U), 0, GetCmdSendToDepot(v));
2683  break;
2684  case WID_VV_REFIT: // refit
2686  break;
2687  case WID_VV_SHOW_ORDERS: // show orders
2688  if (_ctrl_pressed) {
2690  } else {
2691  ShowOrdersWindow(v);
2692  }
2693  break;
2694  case WID_VV_SHOW_DETAILS: // show details
2696  break;
2697  case WID_VV_CLONE: // clone vehicle
2698  /* Suppress the vehicle GUI when share-cloning.
2699  * There is no point to it except for starting the vehicle.
2700  * For starting the vehicle the player has to open the depot GUI, which is
2701  * most likely already open, but is also visible in the vehicle viewport. */
2702  DoCommandP(v->tile, v->index, _ctrl_pressed ? 1 : 0,
2703  _vehicle_command_translation_table[VCT_CMD_CLONE_VEH][v->type],
2704  _ctrl_pressed ? NULL : CcCloneVehicle);
2705  break;
2706  case WID_VV_TURN_AROUND: // turn around
2707  assert(v->IsGroundVehicle());
2708  DoCommandP(v->tile, v->index, 0,
2709  _vehicle_command_translation_table[VCT_CMD_TURN_AROUND][v->type]);
2710  break;
2711  case WID_VV_FORCE_PROCEED: // force proceed
2712  assert(v->type == VEH_TRAIN);
2713  DoCommandP(v->tile, v->index, 0, CMD_FORCE_TRAIN_PROCEED | CMD_MSG(STR_ERROR_CAN_T_MAKE_TRAIN_PASS_SIGNAL));
2714  break;
2715  }
2716  }
2717 
2718  virtual void OnResize()
2719  {
2720  if (this->viewport != NULL) {
2721  NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT);
2722  nvp->UpdateViewportCoordinates(this);
2723  }
2724  }
2725 
2726  virtual void OnTick()
2727  {
2728  const Vehicle *v = Vehicle::Get(this->window_number);
2729  bool veh_stopped = v->IsStoppedInDepot();
2730 
2731  /* Widget WID_VV_GOTO_DEPOT must be hidden if the vehicle is already stopped in depot.
2732  * Widget WID_VV_CLONE_VEH should then be shown, since cloning is allowed only while in depot and stopped.
2733  */
2734  PlaneSelections plane = veh_stopped ? SEL_DC_CLONE : SEL_DC_GOTO_DEPOT;
2735  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE); // Selection widget 'send to depot' / 'clone'.
2736  if (nwi->shown_plane + SEL_DC_BASEPLANE != plane) {
2737  this->SelectPlane(plane);
2738  this->SetWidgetDirty(WID_VV_SELECT_DEPOT_CLONE);
2739  }
2740  /* The same system applies to widget WID_VV_REFIT_VEH and VVW_WIDGET_TURN_AROUND.*/
2741  if (v->IsGroundVehicle()) {
2742  PlaneSelections plane = veh_stopped ? SEL_RT_REFIT : SEL_RT_TURN_AROUND;
2743  NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN);
2744  if (nwi->shown_plane + SEL_RT_BASEPLANE != plane) {
2745  this->SelectPlane(plane);
2746  this->SetWidgetDirty(WID_VV_SELECT_REFIT_TURN);
2747  }
2748  }
2749  }
2750 
2756  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2757  {
2758  if (data == VIWD_AUTOREPLACE) {
2759  /* Autoreplace replaced the vehicle.
2760  * Nothing to do for this window. */
2761  return;
2762  }
2763  }
2764 
2765  virtual bool IsNewGRFInspectable() const
2766  {
2767  return ::IsNewGRFInspectable(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2768  }
2769 
2770  virtual void ShowNewGRFInspectWindow() const
2771  {
2772  ::ShowNewGRFInspectWindow(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2773  }
2774 };
2775 
2776 
2779 {
2780  AllocateWindowDescFront<VehicleViewWindow>((v->type == VEH_TRAIN) ? &_train_view_desc : &_vehicle_view_desc, v->index);
2781 }
2782 
2788 bool VehicleClicked(const Vehicle *v)
2789 {
2790  assert(v != NULL);
2791  if (!(_thd.place_mode & HT_VEHICLE)) return false;
2792 
2793  v = v->First();
2794  if (!v->IsPrimaryVehicle()) return false;
2795 
2796  return _thd.GetCallbackWnd()->OnVehicleSelect(v);
2797 }
2798 
2799 void StopGlobalFollowVehicle(const Vehicle *v)
2800 {
2802  if (w != NULL && w->viewport->follow_vehicle == v->index) {
2803  ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos, true); // lock the main view on the vehicle's last position
2805  }
2806 }
2807 
2808 
2816 void CcBuildPrimaryVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2817 {
2818  if (result.Failed()) return;
2819 
2820  const Vehicle *v = Vehicle::Get(_new_vehicle_id);
2822 }
2823 
2830 {
2831  int vehicle_width = 0;
2832 
2833  switch (v->type) {
2834  case VEH_TRAIN:
2835  for (const Train *u = Train::From(v); u != NULL; u = u->Next()) {
2836  vehicle_width += u->GetDisplayImageWidth();
2837  }
2838  break;
2839 
2840  case VEH_ROAD:
2841  for (const RoadVehicle *u = RoadVehicle::From(v); u != NULL; u = u->Next()) {
2842  vehicle_width += u->GetDisplayImageWidth();
2843  }
2844  break;
2845 
2846  default:
2847  bool rtl = _current_text_dir == TD_RTL;
2848  SpriteID sprite = v->GetImage(rtl ? DIR_E : DIR_W, image_type);
2849  const Sprite *real_sprite = GetSprite(sprite, ST_NORMAL);
2850  vehicle_width = UnScaleGUI(real_sprite->width);
2851 
2852  break;
2853  }
2854 
2855  return vehicle_width;
2856 }