Translations update
[openttd/fttd.git] / src / vehicle_gui.cpp
blob7bbc2a6891f390ddfcc3ba8a16bcb30d5daad6be
1 /* $Id$ */
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 */
10 /** @file vehicle_gui.cpp The base GUI for all vehicles. */
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 "group_gui.h"
26 #include "strings_func.h"
27 #include "vehicle_func.h"
28 #include "autoreplace_gui.h"
29 #include "string.h"
30 #include "widgets/dropdown_func.h"
31 #include "timetable.h"
32 #include "articulated_vehicles.h"
33 #include "spritecache.h"
34 #include "core/geometry_func.hpp"
35 #include "company_base.h"
36 #include "engine_func.h"
37 #include "station_base.h"
38 #include "tilehighlight_func.h"
39 #include "zoom_func.h"
40 #include "font.h"
43 Sorting _sorting;
45 static GUIVehicleList::SortFunction VehicleNumberSorter;
46 static GUIVehicleList::SortFunction VehicleNameSorter;
47 static GUIVehicleList::SortFunction VehicleAgeSorter;
48 static GUIVehicleList::SortFunction VehicleProfitThisYearSorter;
49 static GUIVehicleList::SortFunction VehicleProfitLastYearSorter;
50 static GUIVehicleList::SortFunction VehicleCargoSorter;
51 static GUIVehicleList::SortFunction VehicleReliabilitySorter;
52 static GUIVehicleList::SortFunction VehicleMaxSpeedSorter;
53 static GUIVehicleList::SortFunction VehicleModelSorter;
54 static GUIVehicleList::SortFunction VehicleValueSorter;
55 static GUIVehicleList::SortFunction VehicleLengthSorter;
56 static GUIVehicleList::SortFunction VehicleTimeToLiveSorter;
57 static GUIVehicleList::SortFunction VehicleTimetableDelaySorter;
59 GUIVehicleList::SortFunction * const BaseVehicleListWindow::vehicle_sorter_funcs[] = {
60 &VehicleNumberSorter,
61 &VehicleNameSorter,
62 &VehicleAgeSorter,
63 &VehicleProfitThisYearSorter,
64 &VehicleProfitLastYearSorter,
65 &VehicleCargoSorter,
66 &VehicleReliabilitySorter,
67 &VehicleMaxSpeedSorter,
68 &VehicleModelSorter,
69 &VehicleValueSorter,
70 &VehicleLengthSorter,
71 &VehicleTimeToLiveSorter,
72 &VehicleTimetableDelaySorter,
75 const StringID BaseVehicleListWindow::vehicle_sorter_names[] = {
76 STR_SORT_BY_NUMBER,
77 STR_SORT_BY_NAME,
78 STR_SORT_BY_AGE,
79 STR_SORT_BY_PROFIT_THIS_YEAR,
80 STR_SORT_BY_PROFIT_LAST_YEAR,
81 STR_SORT_BY_TOTAL_CAPACITY_PER_CARGOTYPE,
82 STR_SORT_BY_RELIABILITY,
83 STR_SORT_BY_MAX_SPEED,
84 STR_SORT_BY_MODEL,
85 STR_SORT_BY_VALUE,
86 STR_SORT_BY_LENGTH,
87 STR_SORT_BY_LIFE_TIME,
88 STR_SORT_BY_TIMETABLE_DELAY,
89 INVALID_STRING_ID
92 const StringID BaseVehicleListWindow::vehicle_depot_name[] = {
93 STR_VEHICLE_LIST_SEND_TRAIN_TO_DEPOT,
94 STR_VEHICLE_LIST_SEND_ROAD_VEHICLE_TO_DEPOT,
95 STR_VEHICLE_LIST_SEND_SHIP_TO_DEPOT,
96 STR_VEHICLE_LIST_SEND_AIRCRAFT_TO_HANGAR
99 /**
100 * Get the number of digits the biggest unit number of a set of vehicles has.
101 * @param vehicles The list of vehicles.
102 * @return The number of digits to allocate space for.
104 uint GetUnitNumberDigits(VehicleList &vehicles)
106 uint unitnumber = 0;
107 for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
108 unitnumber = max<uint>(unitnumber, (*v)->unitnumber);
111 if (unitnumber >= 10000) return 5;
112 if (unitnumber >= 1000) return 4;
113 if (unitnumber >= 100) return 3;
116 * When the smallest unit number is less than 10, it is
117 * quite likely that it will expand to become more than
118 * 10 quite soon.
120 return 2;
123 void BaseVehicleListWindow::BuildVehicleList()
125 if (!this->vehicles.NeedRebuild()) return;
127 DEBUG(misc, 3, "Building vehicle list type %d for company %d given index %d", this->vli.type, this->vli.company, this->vli.index);
129 GenerateVehicleSortList(&this->vehicles, this->vli);
131 this->unitnumber_digits = GetUnitNumberDigits(this->vehicles);
133 this->vehicles.RebuildDone();
134 this->vscroll->SetCount(this->vehicles.Length());
138 * Compute the size for the Action dropdown.
139 * @param show_autoreplace If true include the autoreplace item.
140 * @param show_group If true include group-related stuff.
141 * @return Required size.
143 Dimension BaseVehicleListWindow::GetActionDropdownSize(bool show_autoreplace, bool show_group)
145 Dimension d = {0, 0};
147 if (show_autoreplace) d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_REPLACE_VEHICLES));
148 d = maxdim(d, GetStringBoundingBox(STR_VEHICLE_LIST_SEND_FOR_SERVICING));
149 d = maxdim(d, GetStringBoundingBox(this->vehicle_depot_name[this->vli.vtype]));
151 if (show_group) {
152 d = maxdim(d, GetStringBoundingBox(STR_GROUP_ADD_SHARED_VEHICLE));
153 d = maxdim(d, GetStringBoundingBox(STR_GROUP_REMOVE_ALL_VEHICLES));
156 return d;
160 * Display the Action dropdown window.
161 * @param show_autoreplace If true include the autoreplace item.
162 * @param show_group If true include group-related stuff.
163 * @return Itemlist for dropdown
165 DropDownList *BaseVehicleListWindow::BuildActionDropdownList(bool show_autoreplace, bool show_group)
167 DropDownList *list = new DropDownList();
169 if (show_autoreplace) *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_REPLACE_VEHICLES, ADI_REPLACE, false);
170 *list->Append() = new DropDownListStringItem(STR_VEHICLE_LIST_SEND_FOR_SERVICING, ADI_SERVICE, false);
171 *list->Append() = new DropDownListStringItem(this->vehicle_depot_name[this->vli.vtype], ADI_DEPOT, false);
173 if (show_group) {
174 *list->Append() = new DropDownListStringItem(STR_GROUP_ADD_SHARED_VEHICLE, ADI_ADD_SHARED, false);
175 *list->Append() = new DropDownListStringItem(STR_GROUP_REMOVE_ALL_VEHICLES, ADI_REMOVE_ALL, false);
178 return list;
181 /* cached values for VehicleNameSorter to spare many GetString() calls */
182 static const Vehicle *_last_vehicle[2] = { NULL, NULL };
184 void BaseVehicleListWindow::SortVehicleList()
186 if (this->vehicles.Sort()) return;
188 /* invalidate cached values for name sorter - vehicle names could change */
189 _last_vehicle[0] = _last_vehicle[1] = NULL;
192 void DepotSortList(VehicleList *list)
194 if (list->Length() < 2) return;
195 QSortT(list->Begin(), list->Length(), &VehicleNumberSorter);
198 /** Maximum number of refit cycles we try, to prevent infinite loops. And we store only a byte anyway */
199 static const uint MAX_REFIT_CYCLE = 256;
202 * Get the best fitting subtype when 'cloning'/'replacing' \a v_from with \a v_for.
203 * All articulated parts of both vehicles are tested to find a possibly shared subtype.
204 * For \a v_for only vehicle refittable to \a dest_cargo_type are considered.
205 * @param v_from the vehicle to match the subtype from
206 * @param v_for the vehicle to get the subtype for
207 * @param dest_cargo_type Destination cargo type.
208 * @return the best sub type
210 byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type)
212 v_from = v_from->GetFirstEnginePart();
213 v_for = v_for->GetFirstEnginePart();
215 /* Create a list of subtypes used by the various parts of v_for */
216 static SmallVector<StringID, 4> subtypes;
217 subtypes.Clear();
218 for (; v_from != NULL; v_from = v_from->HasArticulatedPart() ? v_from->GetNextArticulatedPart() : NULL) {
219 const Engine *e_from = v_from->GetEngine();
220 if (!e_from->CanCarryCargo() || !HasBit(e_from->info.callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) continue;
221 subtypes.Include(GetCargoSubtypeText(v_from));
224 byte ret_refit_cyc = 0;
225 bool success = false;
226 if (subtypes.Length() > 0) {
227 /* Check whether any articulated part is refittable to 'dest_cargo_type' with a subtype listed in 'subtypes' */
228 for (Vehicle *v = v_for; v != NULL; v = v->HasArticulatedPart() ? v->GetNextArticulatedPart() : NULL) {
229 const Engine *e = v->GetEngine();
230 if (!e->CanCarryCargo() || !HasBit(e->info.callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) continue;
231 if (!HasBit(e->info.refit_mask, dest_cargo_type) && v->cargo_type != dest_cargo_type) continue;
233 CargoID old_cargo_type = v->cargo_type;
234 byte old_cargo_subtype = v->cargo_subtype;
236 /* Set the 'destination' cargo */
237 v->cargo_type = dest_cargo_type;
239 /* Cycle through the refits */
240 for (uint refit_cyc = 0; refit_cyc < MAX_REFIT_CYCLE; refit_cyc++) {
241 v->cargo_subtype = refit_cyc;
243 /* Make sure we don't pick up anything cached. */
244 v->First()->InvalidateNewGRFCache();
245 v->InvalidateNewGRFCache();
247 StringID subtype = GetCargoSubtypeText(v);
248 if (subtype == STR_EMPTY) break;
250 if (!subtypes.Contains(subtype)) continue;
252 /* We found something matching. */
253 ret_refit_cyc = refit_cyc;
254 success = true;
255 break;
258 /* Reset the vehicle's cargo type */
259 v->cargo_type = old_cargo_type;
260 v->cargo_subtype = old_cargo_subtype;
262 /* Make sure we don't taint the vehicle. */
263 v->First()->InvalidateNewGRFCache();
264 v->InvalidateNewGRFCache();
266 if (success) break;
270 return ret_refit_cyc;
273 /** Refit cargo window. */
274 struct RefitWindow : public Window {
275 /** Option to refit a vehicle chain */
276 struct RefitOption {
277 CargoID cargo; ///< Cargo to refit to
278 byte subtype; ///< Subcargo to use
279 StringID string; ///< GRF-local String to display for the cargo
282 * Inequality operator for #RefitOption.
283 * @param other Compare to this #RefitOption.
284 * @return True if both #RefitOption are different.
286 inline bool operator != (const RefitOption &other) const
288 return other.cargo != this->cargo || other.string != this->string;
292 * Equality operator for #RefitOption.
293 * @param other Compare to this #RefitOption.
294 * @return True if both #RefitOption are equal.
296 inline bool operator == (const RefitOption &other) const
298 return other.cargo == this->cargo && other.string == this->string;
302 typedef SmallVector<RefitOption, 32> SubtypeList; ///< List of refit subtypes associated to a cargo.
304 CargoID sel_cargo; ///< Selected cargo, or CT_INVALID for none
305 byte sel_subcargo; ///< Selected subcargo
306 RefitOption *cargo; ///< Refit option selected by #sel.
307 SubtypeList list[NUM_CARGO]; ///< List of refit subtypes available for each sorted cargo.
308 VehicleOrderID order; ///< If not #INVALID_VEH_ORDER_ID, selection is part of a refit order (rather than execute directly).
309 uint information_width; ///< Width required for correctly displaying all cargoes in the information panel.
310 Scrollbar *vscroll; ///< The main scrollbar.
311 Scrollbar *hscroll; ///< Only used for long vehicles.
312 int vehicle_width; ///< Width of the vehicle being drawn.
313 int sprite_left; ///< Left position of the vehicle sprite.
314 int sprite_right; ///< Right position of the vehicle sprite.
315 uint vehicle_margin; ///< Margin to use while selecting vehicles when the vehicle image is centered.
316 int click_x; ///< Position of the first click while dragging.
317 VehicleID selected_vehicle; ///< First vehicle in the current selection.
318 uint8 num_vehicles; ///< Number of selected vehicles.
319 bool auto_refit; ///< Select cargo for auto-refitting.
320 CargoMask cargo_mask; ///< Selected cargoes, when auto-refitting.
323 * Collects all (cargo, subcargo) refit options of a vehicle chain.
325 void BuildRefitList()
327 for (uint i = 0; i < NUM_CARGO; i++) this->list[i].Clear();
328 Vehicle *v = Vehicle::Get(this->window_number);
330 /* Check only the selected vehicles. */
331 VehicleSet vehicles_to_refit;
332 GetVehicleSet(vehicles_to_refit, Vehicle::Get(this->selected_vehicle), this->num_vehicles);
334 do {
335 if (v->type == VEH_TRAIN && !vehicles_to_refit.Contains(v->index)) continue;
336 const Engine *e = v->GetEngine();
337 uint32 cmask = e->info.refit_mask;
338 byte callback_mask = e->info.callback_mask;
340 /* Skip this engine if it does not carry anything */
341 if (!e->CanCarryCargo()) continue;
342 /* Skip this engine if we build the list for auto-refitting and engine doesn't allow it. */
343 if (this->auto_refit && !HasBit(e->info.misc_flags, EF_AUTO_REFIT)) continue;
345 /* Loop through all cargoes in the refit mask */
346 int current_index = 0;
347 const CargoSpec *cs;
348 FOR_ALL_SORTED_CARGOSPECS(cs) {
349 CargoID cid = cs->Index();
350 /* Skip cargo type if it's not listed */
351 if (!HasBit(cmask, cid)) {
352 current_index++;
353 continue;
356 bool first_vehicle = this->list[current_index].Length() == 0;
357 if (first_vehicle) {
358 /* Keeping the current subtype is always an option. It also serves as the option in case of no subtypes */
359 RefitOption *option = this->list[current_index].Append();
360 option->cargo = cid;
361 option->subtype = 0xFF;
362 option->string = STR_EMPTY;
365 /* Check the vehicle's callback mask for cargo suffixes.
366 * This is not supported for ordered refits, since subtypes only have a meaning
367 * for a specific vehicle at a specific point in time, which conflicts with shared orders,
368 * autoreplace, autorenew, clone, order restoration, ... */
369 if (this->order == INVALID_VEH_ORDER_ID && HasBit(callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
370 /* Make a note of the original cargo type. It has to be
371 * changed to test the cargo & subtype... */
372 CargoID temp_cargo = v->cargo_type;
373 byte temp_subtype = v->cargo_subtype;
375 v->cargo_type = cid;
377 for (uint refit_cyc = 0; refit_cyc < MAX_REFIT_CYCLE; refit_cyc++) {
378 v->cargo_subtype = refit_cyc;
380 /* Make sure we don't pick up anything cached. */
381 v->First()->InvalidateNewGRFCache();
382 v->InvalidateNewGRFCache();
384 StringID subtype = GetCargoSubtypeText(v);
386 if (first_vehicle) {
387 /* Append new subtype (don't add duplicates though) */
388 if (subtype == STR_EMPTY) break;
390 RefitOption option;
391 option.cargo = cid;
392 option.subtype = refit_cyc;
393 option.string = subtype;
394 this->list[current_index].Include(option);
395 } else {
396 /* Intersect the subtypes of earlier vehicles with the subtypes of this vehicle */
397 if (subtype == STR_EMPTY) {
398 /* No more subtypes for this vehicle, delete all subtypes >= refit_cyc */
399 SubtypeList &l = this->list[current_index];
400 /* 0xFF item is in front, other subtypes are sorted. So just truncate the list in the right spot */
401 for (uint i = 1; i < l.Length(); i++) {
402 if (l[i].subtype >= refit_cyc) {
403 l.Resize(i);
404 break;
407 break;
408 } else {
409 /* Check whether the subtype matches with the subtype of earlier vehicles. */
410 uint pos = 1;
411 SubtypeList &l = this->list[current_index];
412 while (pos < l.Length() && l[pos].subtype != refit_cyc) pos++;
413 if (pos < l.Length() && l[pos].string != subtype) {
414 /* String mismatch, remove item keeping the order */
415 l.ErasePreservingOrder(pos);
421 /* Reset the vehicle's cargo type */
422 v->cargo_type = temp_cargo;
423 v->cargo_subtype = temp_subtype;
425 /* And make sure we haven't tainted the cache */
426 v->First()->InvalidateNewGRFCache();
427 v->InvalidateNewGRFCache();
429 current_index++;
431 } while (v->IsGroundVehicle() && (v = v->Next()) != NULL);
435 * Refresh scrollbar after selection changed
437 void RefreshScrollbar()
439 uint scroll_row = 0;
440 uint row = 0;
442 for (uint i = 0; i < NUM_CARGO; i++) {
443 for (uint j = 0; j < this->list[i].Length(); j++) {
444 const RefitOption &refit = this->list[i][j];
446 /* Hide subtypes if sel_cargo does not match */
447 if (this->sel_cargo != i && refit.subtype != 0xFF) continue;
449 if (this->sel_cargo == i && this->sel_subcargo == j) scroll_row = row;
451 row++;
455 this->vscroll->SetCount(row);
456 if (scroll_row < row) this->vscroll->ScrollTowards(scroll_row);
460 * Select a row.
461 * @param click_row Clicked row
462 * @return Selected cargo id
464 CargoID SetSelection(uint click_row)
466 uint row = 0;
468 for (uint i = 0; i < NUM_CARGO; i++) {
469 for (uint j = 0; j < this->list[i].Length(); j++) {
470 const RefitOption &refit = this->list[i][j];
472 /* Hide subtypes if sel_cargo does not match */
473 if (this->sel_cargo != i && refit.subtype != 0xFF) continue;
475 if (row == click_row) {
476 this->sel_cargo = i;
477 this->sel_subcargo = j;
478 return refit.cargo;
481 row++;
485 this->sel_cargo = CT_INVALID;
486 this->sel_subcargo = 0;
487 return CT_INVALID;
491 * Gets the #RefitOption placed in the selected index.
492 * @return Pointer to the #RefitOption currently in use.
494 RefitOption *GetRefitOption()
496 if (this->sel_cargo == CT_INVALID) return NULL;
498 SubtypeList &l = this->list[this->sel_cargo];
499 if (this->sel_subcargo >= l.Length()) return NULL;
501 return &l[this->sel_subcargo];
504 RefitWindow (const WindowDesc *desc, const Vehicle *v, VehicleOrderID order,
505 bool auto_refit, CargoMask cargo_mask = 0) :
506 Window (desc),
507 sel_cargo (CT_INVALID), sel_subcargo (0), cargo (NULL),
508 order (order), information_width (0),
509 vscroll (NULL), hscroll (NULL), vehicle_width (0),
510 sprite_left (0), sprite_right (0), vehicle_margin (0),
511 click_x (0), selected_vehicle (0), num_vehicles (0),
512 auto_refit (auto_refit), cargo_mask (cargo_mask)
514 assert (!auto_refit || (order != INVALID_VEH_ORDER_ID));
516 memset (this->list, 0, sizeof(this->list));
518 this->CreateNestedTree();
520 this->vscroll = this->GetScrollbar(WID_VR_SCROLLBAR);
521 this->hscroll = (v->IsGroundVehicle() ? this->GetScrollbar(WID_VR_HSCROLLBAR) : NULL);
522 this->GetWidget<NWidgetCore>(WID_VR_SELECT_HEADER)->tool_tip = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
523 this->GetWidget<NWidgetCore>(WID_VR_MATRIX)->tool_tip = STR_REFIT_TRAIN_LIST_TOOLTIP + v->type;
524 NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_VR_REFIT);
525 nwi->widget_data = STR_REFIT_TRAIN_REFIT_BUTTON + v->type;
526 nwi->tool_tip = STR_REFIT_TRAIN_REFIT_TOOLTIP + v->type;
527 this->GetWidget<NWidgetStacked>(WID_VR_SHOW_HSCROLLBAR)->SetDisplayedPlane(v->IsGroundVehicle() ? 0 : SZSP_HORIZONTAL);
528 this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY)->tool_tip = (v->type == VEH_TRAIN) ? STR_REFIT_SELECT_VEHICLES_TOOLTIP : STR_NULL;
530 this->InitNested(v->index);
531 this->owner = v->owner;
533 this->SetWidgetDisabledState (WID_VR_REFIT, !auto_refit && (this->sel_cargo == CT_INVALID));
536 virtual void OnInit()
538 if (this->cargo != NULL) {
539 /* Store the RefitOption currently in use. */
540 RefitOption current_refit_option = *(this->cargo);
542 /* Rebuild the refit list */
543 this->BuildRefitList();
544 this->sel_cargo = CT_INVALID;
545 this->sel_subcargo = 0;
546 this->cargo = NULL;
547 for (uint i = 0; this->cargo == NULL && i < NUM_CARGO; i++) {
548 for (uint j = 0; j < list[i].Length(); j++) {
549 if (list[i][j] == current_refit_option) {
550 this->sel_cargo = i;
551 this->sel_subcargo = j;
552 this->cargo = &list[i][j];
553 break;
558 this->SetWidgetDisabledState (WID_VR_REFIT, !this->auto_refit && (this->sel_cargo == CT_INVALID));
559 this->RefreshScrollbar();
560 } else {
561 /* Rebuild the refit list */
562 this->OnInvalidateData(VIWD_CONSIST_CHANGED);
566 void OnPaint (BlitArea *dpi) OVERRIDE
568 /* Determine amount of items for scroller. */
569 if (this->hscroll != NULL) this->hscroll->SetCount(this->vehicle_width);
571 /* Calculate sprite position. */
572 NWidgetCore *vehicle_panel_display = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY);
573 int sprite_width = max(0, ((int)vehicle_panel_display->current_x - this->vehicle_width) / 2);
574 this->sprite_left = vehicle_panel_display->pos_x;
575 this->sprite_right = vehicle_panel_display->pos_x + vehicle_panel_display->current_x - 1;
576 if (_current_text_dir == TD_RTL) {
577 this->sprite_right -= sprite_width;
578 this->vehicle_margin = vehicle_panel_display->current_x - sprite_right;
579 } else {
580 this->sprite_left += sprite_width;
581 this->vehicle_margin = sprite_left;
584 this->DrawWidgets (dpi);
587 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
589 switch (widget) {
590 case WID_VR_MATRIX:
591 resize->height = WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM;
592 size->height = resize->height * 8;
593 break;
595 case WID_VR_VEHICLE_PANEL_DISPLAY:
596 size->height = ScaleGUITrad(GetVehicleHeight(Vehicle::Get(this->window_number)->type));
597 break;
599 case WID_VR_INFO:
600 size->width = WD_FRAMERECT_LEFT + this->information_width + WD_FRAMERECT_RIGHT;
601 break;
603 case WID_VR_CLEAR:
604 if (!auto_refit) {
605 size->width = 0;
606 resize->width = 0;
607 fill->width = 0;
609 break;
613 virtual void SetStringParameters(int widget) const
615 if (widget == WID_VR_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
619 * Gets the #StringID to use for displaying capacity.
620 * @param Cargo and cargo subtype to check for capacity.
621 * @return INVALID_STRING_ID if there is no capacity. StringID to use in any other case.
622 * @post String parameters have been set.
624 StringID GetCapacityString(RefitOption *option) const
626 assert(_current_company == _local_company);
627 Vehicle *v = Vehicle::Get(this->window_number);
628 CommandCost cost = DoCommand(v->tile, this->selected_vehicle, option->cargo | (int)this->auto_refit << 6 | option->subtype << 8 |
629 this->num_vehicles << 16, DC_QUERY_COST, CMD_REFIT_VEHICLE);
631 if (cost.Failed()) return INVALID_STRING_ID;
633 SetDParam(0, option->cargo);
634 SetDParam(1, _returned_refit_capacity);
636 Money money = cost.GetCost();
637 if (_returned_mail_refit_capacity > 0) {
638 SetDParam(2, CT_MAIL);
639 SetDParam(3, _returned_mail_refit_capacity);
640 if (this->order != INVALID_VEH_ORDER_ID) {
641 /* No predictable cost */
642 return STR_PURCHASE_INFO_AIRCRAFT_CAPACITY;
643 } else if (money <= 0) {
644 SetDParam(4, -money);
645 return STR_REFIT_NEW_CAPACITY_INCOME_FROM_AIRCRAFT_REFIT;
646 } else {
647 SetDParam(4, money);
648 return STR_REFIT_NEW_CAPACITY_COST_OF_AIRCRAFT_REFIT;
650 } else {
651 if (this->order != INVALID_VEH_ORDER_ID) {
652 /* No predictable cost */
653 SetDParam(2, STR_EMPTY);
654 return STR_PURCHASE_INFO_CAPACITY;
655 } else if (money <= 0) {
656 SetDParam(2, -money);
657 return STR_REFIT_NEW_CAPACITY_INCOME_FROM_REFIT;
658 } else {
659 SetDParam(2, money);
660 return STR_REFIT_NEW_CAPACITY_COST_OF_REFIT;
666 * Draw the list of available refit options for a consist and highlight the selected refit option (if any).
667 * @param dpi Area to draw on.
668 * @param r Rectangle of the matrix widget.
670 void DrawRefitWidget (BlitArea *dpi, const Rect &r) const
672 const SubtypeList *list = this->list;
673 CargoID sel_cargo = this->sel_cargo;
674 uint pos = this->vscroll->GetPosition();
675 uint limit = pos + this->vscroll->GetCapacity();
676 uint delta = this->resize.step_height;
678 uint y = r.top + WD_MATRIX_TOP;
679 uint current = 0;
681 SpriteID spr1, spr2;
682 if (this->auto_refit) {
683 spr1 = SPR_BOX_EMPTY;
684 spr2 = SPR_BOX_CHECKED;
685 } else {
686 spr1 = SPR_CIRCLE_FOLDED;
687 spr2 = SPR_CIRCLE_UNFOLDED;
689 uint iconwidth = max (GetSpriteSize(spr1).width, GetSpriteSize(spr2).width);
690 uint iconheight = GetSpriteSize(spr1).height;
691 int linecolour = _colour_gradient[COLOUR_ORANGE][4];
693 bool rtl = _current_text_dir == TD_RTL;
695 int iconleft = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth : r.left + WD_MATRIX_LEFT;
696 int iconcenter = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth / 2 : r.left + WD_MATRIX_LEFT + iconwidth / 2;
697 int iconinner = rtl ? r.right - WD_MATRIX_RIGHT - iconwidth : r.left + WD_MATRIX_LEFT + iconwidth;
699 int textleft = r.left + WD_MATRIX_LEFT + (rtl ? 0 : iconwidth + 4);
700 int textright = r.right - WD_MATRIX_RIGHT - (rtl ? iconwidth + 4 : 0);
702 /* Draw the list of subtypes for each cargo, and find the selected refit option (by its position). */
703 for (uint i = 0; current < limit && i < NUM_CARGO; i++) {
704 for (uint j = 0; current < limit && j < list[i].Length(); j++) {
705 const RefitOption &refit = list[i][j];
707 /* Hide subtypes if sel_cargo does not match */
708 if (sel_cargo != i && refit.subtype != 0xFF) continue;
710 /* Refit options with a position smaller than pos don't have to be drawn. */
711 if (current < pos) {
712 current++;
713 continue;
716 if (this->auto_refit) {
717 assert (list[i].Length() == 1);
718 assert (refit.subtype == 0xFF);
719 /* Draw checkbox */
720 DrawSprite (dpi, HasBit (this->cargo_mask, refit.cargo) ? SPR_BOX_CHECKED : SPR_BOX_EMPTY, PAL_NONE, iconleft, y + (FONT_HEIGHT_NORMAL - iconheight) / 2);
721 } else if (list[i].Length() > 1) {
722 if (refit.subtype != 0xFF) {
723 /* Draw tree lines */
724 int ycenter = y + FONT_HEIGHT_NORMAL / 2;
725 GfxDrawLine (dpi, iconcenter, y - WD_MATRIX_TOP, iconcenter, j == list[i].Length() - 1 ? ycenter : y - WD_MATRIX_TOP + delta - 1, linecolour);
726 GfxDrawLine (dpi, iconcenter, ycenter, iconinner, ycenter, linecolour);
727 } else {
728 /* Draw expand/collapse icon */
729 DrawSprite (dpi, sel_cargo == i ? SPR_CIRCLE_UNFOLDED : SPR_CIRCLE_FOLDED, PAL_NONE, iconleft, y + (FONT_HEIGHT_NORMAL - iconheight) / 2);
733 TextColour colour = (sel_cargo == i && this->sel_subcargo == j) ? TC_WHITE : TC_BLACK;
734 /* Get the cargo name. */
735 SetDParam(0, CargoSpec::Get(refit.cargo)->name);
736 SetDParam(1, refit.string);
737 DrawString (dpi, textleft, textright, y, STR_JUST_STRING_STRING, colour);
739 y += delta;
740 current++;
745 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
747 switch (widget) {
748 case WID_VR_VEHICLE_PANEL_DISPLAY: {
749 Vehicle *v = Vehicle::Get(this->window_number);
750 DrawVehicleImage (v, dpi, this->sprite_left + WD_FRAMERECT_LEFT, this->sprite_right - WD_FRAMERECT_RIGHT,
751 r.top + WD_FRAMERECT_TOP, EIT_IN_DETAILS, this->hscroll != NULL ? this->hscroll->GetPosition() : 0);
753 /* Highlight selected vehicles. */
754 if (this->order != INVALID_VEH_ORDER_ID) break;
755 int x = 0;
756 switch (v->type) {
757 case VEH_TRAIN: {
758 VehicleSet vehicles_to_refit;
759 GetVehicleSet(vehicles_to_refit, Vehicle::Get(this->selected_vehicle), this->num_vehicles);
761 int left = INT32_MIN;
762 int width = 0;
764 for (Train *u = Train::From(v); u != NULL; u = u->Next()) {
765 /* Start checking. */
766 if (vehicles_to_refit.Contains(u->index) && left == INT32_MIN) {
767 left = x - this->hscroll->GetPosition() + r.left + this->vehicle_margin;
768 width = 0;
771 /* Draw a selection. */
772 if ((!vehicles_to_refit.Contains(u->index) || u->Next() == NULL) && left != INT32_MIN) {
773 if (u->Next() == NULL && vehicles_to_refit.Contains(u->index)) {
774 int current_width = u->GetDisplayImageWidth();
775 width += current_width;
776 x += current_width;
779 int right = Clamp(left + width, 0, r.right);
780 left = max(0, left);
782 if (_current_text_dir == TD_RTL) {
783 right = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY)->current_x - left;
784 left = right - width;
787 if (left != right) {
788 DrawFrameRect (dpi, left, r.top + WD_FRAMERECT_TOP, right, r.top + WD_FRAMERECT_TOP + ScaleGUITrad(14) - 1, COLOUR_WHITE, FR_BORDERONLY);
791 left = INT32_MIN;
794 int current_width = u->GetDisplayImageWidth();
795 width += current_width;
796 x += current_width;
798 break;
801 default: break;
803 break;
806 case WID_VR_MATRIX:
807 this->DrawRefitWidget (dpi, r);
808 break;
810 case WID_VR_INFO:
811 if (this->cargo != NULL) {
812 StringID string = this->GetCapacityString(this->cargo);
813 if (string != INVALID_STRING_ID) {
814 DrawStringMultiLine (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT,
815 r.top + WD_FRAMERECT_TOP, r.bottom - WD_FRAMERECT_BOTTOM, string);
818 break;
822 /* Get the width of the current vehicle in pixels. */
823 int GetVehicleWidth (void) const;
826 * Some data on this window has become invalid.
827 * @param data Information about the changed data.
828 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
830 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
832 switch (data) {
833 case VIWD_AUTOREPLACE: // Autoreplace replaced the vehicle; selected_vehicle became invalid.
834 case VIWD_CONSIST_CHANGED: { // The consist has changed; rebuild the entire list.
835 /* Clear the selection. */
836 Vehicle *v = Vehicle::Get(this->window_number);
837 this->selected_vehicle = v->index;
838 this->num_vehicles = UINT8_MAX;
839 /* FALL THROUGH */
842 case 2: { // The vehicle selection has changed; rebuild the entire list.
843 if (!gui_scope) break;
844 this->BuildRefitList();
846 /* The vehicle width has changed too. */
847 this->vehicle_width = this->GetVehicleWidth();
848 uint max_width = 0;
850 /* Check the width of all cargo information strings. */
851 for (uint i = 0; i < NUM_CARGO; i++) {
852 for (uint j = 0; j < this->list[i].Length(); j++) {
853 StringID string = this->GetCapacityString(&list[i][j]);
854 if (string != INVALID_STRING_ID) {
855 Dimension dim = GetStringBoundingBox(string);
856 max_width = max(dim.width, max_width);
861 if (this->information_width < max_width) {
862 this->information_width = max_width;
863 this->ReInit();
865 /* FALL THROUGH */
868 case 1: // A new cargo has been selected.
869 if (!gui_scope) break;
870 this->cargo = GetRefitOption();
871 this->RefreshScrollbar();
872 break;
876 int GetClickPosition(int click_x)
878 const NWidgetCore *matrix_widget = this->GetWidget<NWidgetCore>(WID_VR_VEHICLE_PANEL_DISPLAY);
879 if (_current_text_dir == TD_RTL) click_x = matrix_widget->current_x - click_x;
880 click_x -= this->vehicle_margin;
881 if (this->hscroll != NULL) click_x += this->hscroll->GetPosition();
883 return click_x;
886 void SetSelectedVehicles(int drag_x)
888 drag_x = GetClickPosition(drag_x);
890 int left_x = min(this->click_x, drag_x);
891 int right_x = max(this->click_x, drag_x);
892 this->num_vehicles = 0;
894 Vehicle *v = Vehicle::Get(this->window_number);
895 /* Find the vehicle part that was clicked. */
896 switch (v->type) {
897 case VEH_TRAIN: {
898 /* Don't select anything if we are not clicking in the vehicle. */
899 if (left_x >= 0) {
900 const Train *u = Train::From(v);
901 bool start_counting = false;
902 for (; u != NULL; u = u->Next()) {
903 int current_width = u->GetDisplayImageWidth();
904 left_x -= current_width;
905 right_x -= current_width;
907 if (left_x < 0 && !start_counting) {
908 this->selected_vehicle = u->index;
909 start_counting = true;
911 /* Count the first vehicle, even if articulated part */
912 this->num_vehicles++;
913 } else if (start_counting && !u->IsArticulatedPart()) {
914 /* Do not count articulated parts */
915 this->num_vehicles++;
918 if (right_x < 0) break;
922 /* If the selection is not correct, clear it. */
923 if (this->num_vehicles != 0) {
924 if (_ctrl_pressed) this->num_vehicles = UINT8_MAX;
925 break;
927 /* FALL THROUGH */
930 default:
931 /* Clear the selection. */
932 this->selected_vehicle = v->index;
933 this->num_vehicles = UINT8_MAX;
934 break;
938 virtual void OnClick(Point pt, int widget, int click_count)
940 switch (widget) {
941 case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
942 if (this->order != INVALID_VEH_ORDER_ID) break;
943 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
944 this->click_x = GetClickPosition(pt.x - nwi->pos_x);
945 this->SetSelectedVehicles(pt.x - nwi->pos_x);
946 this->SetWidgetDirty(WID_VR_VEHICLE_PANEL_DISPLAY);
947 if (!_ctrl_pressed) {
948 SetPointerMode (POINTER_DRAG, this, SPR_CURSOR_MOUSE);
949 } else {
950 /* The vehicle selection has changed. */
951 this->InvalidateData(2);
953 break;
956 case WID_VR_MATRIX: { // listbox
957 uint row = this->vscroll->GetScrolledRowFromWidget (pt.y, this, WID_VR_MATRIX);
958 CargoID c = this->SetSelection(row);
960 if (this->auto_refit) {
961 if (c != CT_INVALID) {
962 const NWidgetBase *wid = this->GetWidget<NWidgetBase> (WID_VR_MATRIX);
963 uint offset = pt.x - wid->pos_x;
964 uint width = max (GetSpriteSize(SPR_BOX_EMPTY).width, GetSpriteSize(SPR_BOX_CHECKED).width) + 5;
965 if (_current_text_dir == TD_RTL ? offset > wid->current_x - width : offset < width) {
966 ToggleBit (this->cargo_mask, c);
969 this->InvalidateData (1);
970 break;
973 /* not auto-refit */
974 this->SetWidgetDisabledState(WID_VR_REFIT, this->sel_cargo == CT_INVALID);
975 this->InvalidateData(1);
977 if (click_count == 1) break;
978 /* FALL THROUGH */
981 case WID_VR_REFIT: // refit button
982 if (this->auto_refit) {
983 const Vehicle *v = Vehicle::Get(this->window_number);
984 if (DoCommandP(v->tile, v->index | this->order << 24, this->cargo_mask, CMD_ORDER_REFIT)) this->Delete();
985 } else if (this->cargo != NULL) {
986 const Vehicle *v = Vehicle::Get(this->window_number);
988 if (this->order == INVALID_VEH_ORDER_ID) {
989 bool delete_window = this->selected_vehicle == v->index && this->num_vehicles == UINT8_MAX;
990 if (DoCommandP(v->tile, this->selected_vehicle, this->cargo->cargo | this->cargo->subtype << 8 | this->num_vehicles << 16, CMD_REFIT_VEHICLE) && delete_window) this->Delete();
991 } else {
992 if (DoCommandP(v->tile, v->index | this->order << 24, 1 << this->cargo->cargo, CMD_ORDER_REFIT)) this->Delete();
995 break;
997 case WID_VR_CLEAR: // clear button
998 assert (this->auto_refit);
999 this->cargo_mask = 0;
1000 this->InvalidateData (1);
1001 break;
1005 virtual void OnMouseDrag(Point pt, int widget)
1007 switch (widget) {
1008 case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
1009 if (this->order != INVALID_VEH_ORDER_ID) break;
1010 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
1011 this->SetSelectedVehicles(pt.x - nwi->pos_x);
1012 this->SetWidgetDirty(WID_VR_VEHICLE_PANEL_DISPLAY);
1013 break;
1018 virtual void OnDragDrop(Point pt, int widget)
1020 switch (widget) {
1021 case WID_VR_VEHICLE_PANEL_DISPLAY: { // Vehicle image.
1022 if (this->order != INVALID_VEH_ORDER_ID) break;
1023 NWidgetBase *nwi = this->GetWidget<NWidgetBase>(WID_VR_VEHICLE_PANEL_DISPLAY);
1024 this->SetSelectedVehicles(pt.x - nwi->pos_x);
1025 this->InvalidateData(2);
1026 break;
1031 virtual void OnResize()
1033 this->vehicle_width = this->GetVehicleWidth();
1034 this->vscroll->SetCapacityFromWidget(this, WID_VR_MATRIX);
1035 if (this->hscroll != NULL) this->hscroll->SetCapacityFromWidget(this, WID_VR_VEHICLE_PANEL_DISPLAY);
1040 * Get the width of a vehicle (part) in pixels.
1041 * @param v Vehicle to get the width for.
1042 * @return Width of the vehicle.
1044 static int GetSingleVehicleWidth (const Vehicle *v, EngineImageType image_type)
1046 switch (v->type) {
1047 case VEH_TRAIN:
1048 return Train::From(v)->GetDisplayImageWidth();
1050 case VEH_ROAD:
1051 return RoadVehicle::From(v)->GetDisplayImageWidth();
1053 default:
1054 bool rtl = _current_text_dir == TD_RTL;
1055 VehicleSpriteSeq seq;
1056 v->GetImage (rtl ? DIR_E : DIR_W, image_type, &seq);
1057 Rect rec;
1058 seq.GetBounds(&rec);
1059 return UnScaleGUI (rec.right - rec.left + 1);
1064 * Get the width of the vehicle (including all parts of the consist) in pixels.
1065 * @return Width of the vehicle.
1067 int RefitWindow::GetVehicleWidth (void) const
1069 const Vehicle *v = Vehicle::Get (this->window_number);
1071 if (v->type == VEH_TRAIN || v->type == VEH_ROAD) {
1072 int vehicle_width = 0;
1073 for (const Vehicle *u = v; u != NULL; u = u->Next()) {
1074 vehicle_width += GetSingleVehicleWidth (u, EIT_IN_DETAILS);
1076 return vehicle_width;
1077 } else {
1078 return GetSingleVehicleWidth (v, EIT_IN_DETAILS);
1082 static const NWidgetPart _nested_vehicle_refit_widgets[] = {
1083 NWidget(NWID_HORIZONTAL),
1084 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1085 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VR_CAPTION), SetDataTip(STR_REFIT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1086 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1087 EndContainer(),
1088 /* Vehicle display + scrollbar. */
1089 NWidget(NWID_VERTICAL),
1090 NWidget(WWT_PANEL, COLOUR_GREY, WID_VR_VEHICLE_PANEL_DISPLAY), SetMinimalSize(228, 14), SetResize(1, 0), SetScrollbar(WID_VR_HSCROLLBAR), EndContainer(),
1091 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VR_SHOW_HSCROLLBAR),
1092 NWidget(NWID_HSCROLLBAR, COLOUR_GREY, WID_VR_HSCROLLBAR),
1093 EndContainer(),
1094 EndContainer(),
1095 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_VR_SELECT_HEADER), SetDataTip(STR_REFIT_TITLE, STR_NULL), SetResize(1, 0),
1096 /* Matrix + scrollbar. */
1097 NWidget(NWID_HORIZONTAL),
1098 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),
1099 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VR_SCROLLBAR),
1100 EndContainer(),
1101 NWidget(WWT_PANEL, COLOUR_GREY, WID_VR_INFO), SetMinimalTextLines(2, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 0), EndContainer(),
1102 NWidget(NWID_HORIZONTAL),
1103 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1104 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VR_CLEAR), SetDataTip(STR_REFIT_CLEAR, STR_REFIT_CLEAR_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1105 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VR_REFIT), SetFill(1, 0), SetResize(1, 0),
1106 EndContainer(),
1107 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1108 EndContainer(),
1111 static WindowDesc::Prefs _vehicle_refit_prefs ("view_vehicle_refit");
1113 static const WindowDesc _vehicle_refit_desc(
1114 WDP_AUTO, 240, 174,
1115 WC_VEHICLE_REFIT, WC_VEHICLE_VIEW,
1116 WDF_CONSTRUCTION,
1117 _nested_vehicle_refit_widgets, lengthof(_nested_vehicle_refit_widgets),
1118 &_vehicle_refit_prefs
1122 * Show the refit window for a vehicle
1123 * @param parent the parent window of the refit window
1124 * @param *v The vehicle to show the refit window for
1125 * @param order of the vehicle to assign refit to, or INVALID_VEH_ORDER_ID to refit the vehicle now
1126 * @param auto_refit Choose cargo for auto-refitting
1127 * @param mask Initial cargo mask of allowed refits for auto-refitting
1129 void ShowVehicleRefitWindow (Window *parent, const Vehicle *v,
1130 VehicleOrderID order, bool auto_refit, CargoMask mask)
1132 DeleteWindowById(WC_VEHICLE_REFIT, v->index);
1133 RefitWindow *w = new RefitWindow (&_vehicle_refit_desc, v, order,
1134 auto_refit, mask);
1135 w->parent = parent;
1138 /** Display list of cargo types of the engine, for the purchase information window */
1139 uint ShowRefitOptionsList (BlitArea *dpi, int left, int right, int y, EngineID engine)
1141 /* List of cargo types of this engine */
1142 uint32 cmask = GetUnionOfArticulatedRefitMasks(engine, false);
1143 /* List of cargo types available in this climate */
1144 uint32 lmask = _cargo_mask;
1146 /* Draw nothing if the engine is not refittable */
1147 if (HasAtMostOneBit(cmask)) return y;
1149 if (cmask == lmask) {
1150 /* Engine can be refitted to all types in this climate */
1151 SetDParam(0, STR_PURCHASE_INFO_ALL_TYPES);
1152 } else {
1153 /* Check if we are able to refit to more cargo types and unable to. If
1154 * so, invert the cargo types to list those that we can't refit to. */
1155 if (CountBits(cmask ^ lmask) < CountBits(cmask) && CountBits(cmask ^ lmask) <= 7) {
1156 cmask ^= lmask;
1157 SetDParam(0, STR_PURCHASE_INFO_ALL_BUT);
1158 } else {
1159 SetDParam(0, STR_JUST_CARGO_LIST);
1161 SetDParam(1, cmask);
1164 return DrawStringMultiLine (dpi, left, right, y, INT32_MAX, STR_PURCHASE_INFO_REFITTABLE_TO);
1167 /** Get the cargo subtype text from NewGRF for the vehicle details window. */
1168 StringID GetCargoSubtypeText(const Vehicle *v)
1170 if (HasBit(EngInfo(v->engine_type)->callback_mask, CBM_VEHICLE_CARGO_SUFFIX)) {
1171 uint16 cb = GetVehicleCallback(CBID_VEHICLE_CARGO_SUFFIX, 0, 0, v->engine_type, v);
1172 if (cb != CALLBACK_FAILED) {
1173 if (cb > 0x400) ErrorUnknownCallbackResult(v->GetGRFID(), CBID_VEHICLE_CARGO_SUFFIX, cb);
1174 if (cb >= 0x400 || (v->GetGRF()->grf_version < 8 && cb == 0xFF)) cb = CALLBACK_FAILED;
1176 if (cb != CALLBACK_FAILED) {
1177 return GetGRFStringID(v->GetGRFID(), 0xD000 + cb);
1180 return STR_EMPTY;
1183 /** Sort vehicles by their number */
1184 static int CDECL VehicleNumberSorter(const Vehicle * const *a, const Vehicle * const *b)
1186 return (*a)->unitnumber - (*b)->unitnumber;
1189 /** Sort vehicles by their name */
1190 static int CDECL VehicleNameSorter(const Vehicle * const *a, const Vehicle * const *b)
1192 static char last_name[2][64];
1194 if (*a != _last_vehicle[0]) {
1195 _last_vehicle[0] = *a;
1196 SetDParam(0, (*a)->index);
1197 GetString (last_name[0], STR_VEHICLE_NAME);
1200 if (*b != _last_vehicle[1]) {
1201 _last_vehicle[1] = *b;
1202 SetDParam(0, (*b)->index);
1203 GetString (last_name[1], STR_VEHICLE_NAME);
1206 int r = strnatcmp(last_name[0], last_name[1]); // Sort by name (natural sorting).
1207 return (r != 0) ? r : VehicleNumberSorter(a, b);
1210 /** Sort vehicles by their age */
1211 static int CDECL VehicleAgeSorter(const Vehicle * const *a, const Vehicle * const *b)
1213 int r = (*a)->age - (*b)->age;
1214 return (r != 0) ? r : VehicleNumberSorter(a, b);
1217 /** Sort vehicles by this year profit */
1218 static int CDECL VehicleProfitThisYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1220 int r = ClampToI32((*a)->GetDisplayProfitThisYear() - (*b)->GetDisplayProfitThisYear());
1221 return (r != 0) ? r : VehicleNumberSorter(a, b);
1224 /** Sort vehicles by last year profit */
1225 static int CDECL VehicleProfitLastYearSorter(const Vehicle * const *a, const Vehicle * const *b)
1227 int r = ClampToI32((*a)->GetDisplayProfitLastYear() - (*b)->GetDisplayProfitLastYear());
1228 return (r != 0) ? r : VehicleNumberSorter(a, b);
1231 /** Sort vehicles by their cargo */
1232 static int CDECL VehicleCargoSorter(const Vehicle * const *a, const Vehicle * const *b)
1234 const Vehicle *v;
1235 CargoArray diff;
1237 /* Append the cargo of the connected waggons */
1238 for (v = *a; v != NULL; v = v->Next()) diff[v->cargo_type] += v->cargo_cap;
1239 for (v = *b; v != NULL; v = v->Next()) diff[v->cargo_type] -= v->cargo_cap;
1241 int r = 0;
1242 for (CargoID i = 0; i < NUM_CARGO; i++) {
1243 r = diff[i];
1244 if (r != 0) break;
1247 return (r != 0) ? r : VehicleNumberSorter(a, b);
1250 /** Sort vehicles by their reliability */
1251 static int CDECL VehicleReliabilitySorter(const Vehicle * const *a, const Vehicle * const *b)
1253 int r = (*a)->reliability - (*b)->reliability;
1254 return (r != 0) ? r : VehicleNumberSorter(a, b);
1257 /** Sort vehicles by their max speed */
1258 static int CDECL VehicleMaxSpeedSorter(const Vehicle * const *a, const Vehicle * const *b)
1260 int r = (*a)->vcache.cached_max_speed - (*b)->vcache.cached_max_speed;
1261 return (r != 0) ? r : VehicleNumberSorter(a, b);
1264 /** Sort vehicles by model */
1265 static int CDECL VehicleModelSorter(const Vehicle * const *a, const Vehicle * const *b)
1267 int r = (*a)->engine_type - (*b)->engine_type;
1268 return (r != 0) ? r : VehicleNumberSorter(a, b);
1271 /** Sort vehicles by their value */
1272 static int CDECL VehicleValueSorter(const Vehicle * const *a, const Vehicle * const *b)
1274 const Vehicle *u;
1275 Money diff = 0;
1277 for (u = *a; u != NULL; u = u->Next()) diff += u->value;
1278 for (u = *b; u != NULL; u = u->Next()) diff -= u->value;
1280 int r = ClampToI32(diff);
1281 return (r != 0) ? r : VehicleNumberSorter(a, b);
1284 /** Sort vehicles by their length */
1285 static int CDECL VehicleLengthSorter(const Vehicle * const *a, const Vehicle * const *b)
1287 int r = GroundVehicleBase::From(*a)->gcache.cached_total_length - GroundVehicleBase::From(*b)->gcache.cached_total_length;
1288 return (r != 0) ? r : VehicleNumberSorter(a, b);
1291 /** Sort vehicles by the time they can still live */
1292 static int CDECL VehicleTimeToLiveSorter(const Vehicle * const *a, const Vehicle * const *b)
1294 int r = ClampToI32(((*a)->max_age - (*a)->age) - ((*b)->max_age - (*b)->age));
1295 return (r != 0) ? r : VehicleNumberSorter(a, b);
1298 /** Sort vehicles by the timetable delay */
1299 static int CDECL VehicleTimetableDelaySorter(const Vehicle * const *a, const Vehicle * const *b)
1301 int r = (*a)->lateness_counter - (*b)->lateness_counter;
1302 return (r != 0) ? r : VehicleNumberSorter(a, b);
1305 void InitializeGUI()
1307 MemSetT(&_sorting, 0);
1311 * Assign a vehicle window a new vehicle
1312 * @param window_class WindowClass to search for
1313 * @param from_index the old vehicle ID
1314 * @param to_index the new vehicle ID
1316 static inline void ChangeVehicleWindow(WindowClass window_class, VehicleID from_index, VehicleID to_index)
1318 Window *w = FindWindowById(window_class, from_index);
1319 if (w != NULL) {
1320 /* Update window_number */
1321 w->window_number = to_index;
1322 if (w->viewport != NULL) w->viewport->follow_vehicle = to_index;
1324 /* Update vehicle drag data */
1325 if (_thd.window_class == window_class && _thd.window_number == (WindowNumber)from_index) {
1326 _thd.window_number = to_index;
1329 /* Notify the window. */
1330 w->InvalidateData(VIWD_AUTOREPLACE, false);
1335 * Report a change in vehicle IDs (due to autoreplace) to affected vehicle windows.
1336 * @param from_index the old vehicle ID
1337 * @param to_index the new vehicle ID
1339 void ChangeVehicleViewWindow(VehicleID from_index, VehicleID to_index)
1341 ChangeVehicleWindow(WC_VEHICLE_VIEW, from_index, to_index);
1342 ChangeVehicleWindow(WC_VEHICLE_ORDERS, from_index, to_index);
1343 ChangeVehicleWindow(WC_VEHICLE_REFIT, from_index, to_index);
1344 ChangeVehicleWindow(WC_VEHICLE_DETAILS, from_index, to_index);
1345 ChangeVehicleWindow(WC_VEHICLE_TIMETABLE, from_index, to_index);
1348 static const NWidgetPart _nested_vehicle_list[] = {
1349 NWidget(NWID_HORIZONTAL),
1350 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1351 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VL_CAPTION),
1352 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1353 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1354 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1355 EndContainer(),
1357 NWidget(NWID_HORIZONTAL),
1358 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_SORT_ORDER), SetMinimalSize(81, 12), SetFill(0, 1), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
1359 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_SORT_BY_PULLDOWN), SetMinimalSize(167, 12), SetFill(0, 1), SetDataTip(0x0, STR_TOOLTIP_SORT_CRITERIA),
1360 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(12, 12), SetFill(1, 1), SetResize(1, 0),
1361 EndContainer(),
1362 EndContainer(),
1364 NWidget(NWID_HORIZONTAL),
1365 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),
1366 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VL_SCROLLBAR),
1367 EndContainer(),
1369 NWidget(NWID_HORIZONTAL),
1370 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VL_HIDE_BUTTONS),
1371 NWidget(NWID_HORIZONTAL),
1372 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VL_AVAILABLE_VEHICLES), SetMinimalSize(106, 12), SetFill(0, 1),
1373 SetDataTip(STR_BLACK_STRING, STR_VEHICLE_LIST_AVAILABLE_ENGINES_TOOLTIP),
1374 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
1375 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VL_MANAGE_VEHICLES_DROPDOWN), SetMinimalSize(118, 12), SetFill(0, 1),
1376 SetDataTip(STR_VEHICLE_LIST_MANAGE_LIST, STR_VEHICLE_LIST_MANAGE_LIST_TOOLTIP),
1377 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_STOP_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1378 SetDataTip(SPR_FLAG_VEH_STOPPED, STR_VEHICLE_LIST_MASS_STOP_LIST_TOOLTIP),
1379 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VL_START_ALL), SetMinimalSize(12, 12), SetFill(0, 1),
1380 SetDataTip(SPR_FLAG_VEH_RUNNING, STR_VEHICLE_LIST_MASS_START_LIST_TOOLTIP),
1381 EndContainer(),
1382 /* Widget to be shown for other companies hiding the previous 5 widgets. */
1383 NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1384 EndContainer(),
1385 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1386 EndContainer(),
1389 static void DrawSmallOrderList (const Vehicle *v, BlitArea *dpi,
1390 int left, int right, int y, VehicleOrderID start = 0)
1392 const Order *order = v->GetOrder(start);
1393 if (order == NULL) return;
1395 bool rtl = _current_text_dir == TD_RTL;
1396 int l_offset = rtl ? 0 : ScaleGUITrad(6);
1397 int r_offset = rtl ? ScaleGUITrad(6) : 0;
1398 int i = 0;
1399 VehicleOrderID oid = start;
1401 do {
1402 if (oid == v->cur_real_order_index) DrawString (dpi, left, right, y, STR_TINY_RIGHT_ARROW, TC_BLACK);
1404 if (order->IsType(OT_GOTO_STATION)) {
1405 SetDParam(0, order->GetDestination());
1406 DrawString (dpi, left + l_offset, right - r_offset, y, STR_TINY_BLACK_STATION);
1408 y += FONT_HEIGHT_SMALL;
1409 if (++i == 4) break;
1412 oid++;
1413 order = order->next;
1414 if (order == NULL) {
1415 order = v->orders.list->GetFirstOrder();
1416 oid = 0;
1418 } while (oid != start);
1422 * Draws an image of a vehicle chain
1423 * @param v Front vehicle
1424 * @param dpi The area to draw on
1425 * @param left The minimum horizontal position
1426 * @param right The maximum horizontal position
1427 * @param y Vertical position to draw at
1428 * @param image_type Draw this image type
1429 * @param skip Number of pixels to skip at the front (for scrolling)
1430 * @param selection Selected vehicle to draw a frame around
1432 void DrawVehicleImage (const Vehicle *v, BlitArea *dpi, int left, int right,
1433 int y, EngineImageType image_type, int skip, VehicleID selection)
1435 switch (v->type) {
1436 case VEH_TRAIN: DrawTrainImage (Train::From(v), dpi, left, right, y, selection, true, image_type, skip); break;
1437 case VEH_ROAD: DrawRoadVehImage (v, dpi, left, right, y, selection, image_type, skip); break;
1438 case VEH_SHIP: DrawShipImage (v, dpi, left, right, y, selection, image_type); break;
1439 case VEH_AIRCRAFT: DrawAircraftImage (v, dpi, left, right, y, selection, image_type); break;
1440 default: NOT_REACHED();
1445 * Get the height of a vehicle in the vehicle list GUIs.
1446 * @param type the vehicle type to look at
1447 * @param divisor the resulting height must be dividable by this
1448 * @return the height
1450 uint GetVehicleListHeight(VehicleType type, uint divisor)
1452 /* Name + vehicle + profit */
1453 uint base = ScaleGUITrad(GetVehicleHeight(type)) + 2 * FONT_HEIGHT_SMALL;
1454 /* Drawing of the 4 small orders + profit*/
1455 if (type >= VEH_SHIP) base = max(base, 5U * FONT_HEIGHT_SMALL);
1457 if (divisor == 1) return base;
1459 /* Make sure the height is dividable by divisor */
1460 uint rem = base % divisor;
1461 return base + (rem == 0 ? 0 : divisor - rem);
1465 * Draw all the vehicle list items.
1466 * @param dpi The area to draw on.
1467 * @param selected_vehicle The vehicle that is to be highlighted.
1468 * @param line_height Height of a single item line.
1469 * @param r Rectangle with edge positions of the matrix widget.
1471 void BaseVehicleListWindow::DrawVehicleListItems (BlitArea *dpi,
1472 VehicleID selected_vehicle, int line_height, const Rect &r) const
1474 int left = r.left + WD_MATRIX_LEFT;
1475 int right = r.right - WD_MATRIX_RIGHT;
1476 int width = right - left;
1477 bool rtl = _current_text_dir == TD_RTL;
1479 int text_offset = max<int>(GetSpriteSize(SPR_PROFIT_LOT).width, GetDigitWidth() * this->unitnumber_digits) + WD_FRAMERECT_RIGHT;
1480 int text_left = left + (rtl ? 0 : text_offset);
1481 int text_right = right - (rtl ? text_offset : 0);
1483 bool show_orderlist = this->vli.vtype >= VEH_SHIP;
1484 int orderlist_left = left + (rtl ? 0 : max(ScaleGUITrad(100) + text_offset, width / 2));
1485 int orderlist_right = right - (rtl ? max(ScaleGUITrad(100) + text_offset, width / 2) : 0);
1487 int image_left = (rtl && show_orderlist) ? orderlist_right : text_left;
1488 int image_right = (!rtl && show_orderlist) ? orderlist_left : text_right;
1490 int vehicle_button_x = rtl ? right - GetSpriteSize(SPR_PROFIT_LOT).width : left;
1492 int y = r.top;
1493 uint max = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehicles.Length());
1494 for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
1495 const Vehicle *v = this->vehicles[i];
1496 StringID str;
1498 SetDParam(0, v->GetDisplayProfitThisYear());
1499 SetDParam(1, v->GetDisplayProfitLastYear());
1501 DrawVehicleImage (v, dpi, image_left, image_right, y + FONT_HEIGHT_SMALL - 1, EIT_IN_LIST, 0, selected_vehicle);
1502 DrawString (dpi, text_left, text_right, y + line_height - FONT_HEIGHT_SMALL - WD_FRAMERECT_BOTTOM - 1, STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR);
1504 if (v->name != NULL) {
1505 /* The vehicle got a name so we will print it */
1506 SetDParam(0, v->index);
1507 DrawString (dpi, text_left, text_right, y, STR_TINY_BLACK_VEHICLE);
1508 } else if (v->group_id != DEFAULT_GROUP) {
1509 /* The vehicle has no name, but is member of a group, so print group name */
1510 SetDParam(0, v->group_id);
1511 DrawString (dpi, text_left, text_right, y, STR_TINY_GROUP, TC_BLACK);
1514 if (show_orderlist) DrawSmallOrderList (v, dpi, orderlist_left, orderlist_right, y, v->cur_real_order_index);
1516 if (v->IsChainInDepot()) {
1517 str = STR_BLUE_COMMA;
1518 } else {
1519 str = (v->age > v->max_age - DAYS_IN_LEAP_YEAR) ? STR_RED_COMMA : STR_BLACK_COMMA;
1522 SetDParam(0, v->unitnumber);
1523 DrawString (dpi, left, right, y + 2, str);
1525 /* Draw profit-based coloured icons. */
1526 DrawSprite (dpi,
1527 (v->age <= VEHICLE_PROFIT_MIN_AGE) ? SPR_PROFIT_NA :
1528 (v->GetDisplayProfitLastYear() < 0) ? SPR_PROFIT_NEGATIVE :
1529 (v->GetDisplayProfitLastYear() < VEHICLE_PROFIT_THRESHOLD) ?
1530 SPR_PROFIT_SOME : SPR_PROFIT_LOT,
1531 PAL_NONE, vehicle_button_x,
1532 y + FONT_HEIGHT_NORMAL + 3);
1534 y += line_height;
1539 * Window for the (old) vehicle listing.
1541 * bitmask for w->window_number
1542 * 0-7 CompanyID (owner)
1543 * 8-10 window type (use flags in vehicle_gui.h)
1544 * 11-15 vehicle type (using VEH_, but can be compressed to fewer bytes if needed)
1545 * 16-31 StationID or OrderID depending on window type (bit 8-10)
1547 struct VehicleListWindow : public BaseVehicleListWindow {
1548 private:
1549 /** Enumeration of planes of the button row at the bottom. */
1550 enum ButtonPlanes {
1551 BP_SHOW_BUTTONS, ///< Show the buttons.
1552 BP_HIDE_BUTTONS, ///< Show the empty panel.
1555 public:
1556 VehicleListWindow (const WindowDesc *desc, WindowNumber window_number) : BaseVehicleListWindow(desc, window_number)
1558 /* Set up sorting. Make the window-specific _sorting variable
1559 * point to the correct global _sorting struct so we are freed
1560 * from having conditionals during window operation */
1561 switch (this->vli.vtype) {
1562 case VEH_TRAIN: this->sorting = &_sorting.train; break;
1563 case VEH_ROAD: this->sorting = &_sorting.roadveh; break;
1564 case VEH_SHIP: this->sorting = &_sorting.ship; break;
1565 case VEH_AIRCRAFT: this->sorting = &_sorting.aircraft; break;
1566 default: NOT_REACHED();
1569 this->CreateNestedTree();
1571 this->vscroll = this->GetScrollbar(WID_VL_SCROLLBAR);
1573 this->vehicles.SetListing(*this->sorting);
1574 this->vehicles.ForceRebuild();
1575 this->vehicles.NeedResort();
1576 this->BuildVehicleList();
1577 this->SortVehicleList();
1579 /* Set up the window widgets */
1580 this->GetWidget<NWidgetCore>(WID_VL_LIST)->tool_tip = STR_VEHICLE_LIST_TRAIN_LIST_TOOLTIP + this->vli.vtype;
1582 if (this->vli.type == VL_SHARED_ORDERS) {
1583 this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_SHARED_ORDERS_LIST_CAPTION;
1584 } else {
1585 this->GetWidget<NWidgetCore>(WID_VL_CAPTION)->widget_data = STR_VEHICLE_LIST_TRAIN_CAPTION + this->vli.vtype;
1588 this->InitNested(window_number);
1589 if (this->vli.company != OWNER_NONE) this->owner = this->vli.company;
1592 void OnDelete (void) FINAL_OVERRIDE
1594 *this->sorting = this->vehicles.GetListing();
1597 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1599 switch (widget) {
1600 case WID_VL_LIST:
1601 resize->height = GetVehicleListHeight(this->vli.vtype, 1);
1603 switch (this->vli.vtype) {
1604 case VEH_TRAIN:
1605 case VEH_ROAD:
1606 size->height = 6 * resize->height;
1607 break;
1608 case VEH_SHIP:
1609 case VEH_AIRCRAFT:
1610 size->height = 4 * resize->height;
1611 break;
1612 default: NOT_REACHED();
1614 break;
1616 case WID_VL_SORT_ORDER: {
1617 Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
1618 d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
1619 d.height += padding.height;
1620 *size = maxdim(*size, d);
1621 break;
1624 case WID_VL_MANAGE_VEHICLES_DROPDOWN: {
1625 Dimension d = this->GetActionDropdownSize(this->vli.type == VL_STANDARD, false);
1626 d.height += padding.height;
1627 d.width += padding.width;
1628 *size = maxdim(*size, d);
1629 break;
1634 virtual void SetStringParameters(int widget) const
1636 switch (widget) {
1637 case WID_VL_AVAILABLE_VEHICLES:
1638 SetDParam(0, STR_VEHICLE_LIST_AVAILABLE_TRAINS + this->vli.vtype);
1639 break;
1641 case WID_VL_CAPTION: {
1642 switch (this->vli.type) {
1643 case VL_SHARED_ORDERS: // Shared Orders
1644 if (this->vehicles.Length() == 0) {
1645 /* We can't open this window without vehicles using this order
1646 * and we should close the window when deleting the order. */
1647 NOT_REACHED();
1649 SetDParam(0, this->vscroll->GetCount());
1650 break;
1652 case VL_STANDARD: // Company Name
1653 SetDParam(0, STR_COMPANY_NAME);
1654 SetDParam(1, this->vli.index);
1655 SetDParam(3, this->vscroll->GetCount());
1656 break;
1658 case VL_STATION_LIST: // Station/Waypoint Name
1659 SetDParam(0, BaseStation::Get(this->vli.index)->IsWaypoint() ? STR_WAYPOINT_NAME : STR_STATION_NAME);
1660 SetDParam(1, this->vli.index);
1661 SetDParam(3, this->vscroll->GetCount());
1662 break;
1664 case VL_DEPOT_LIST:
1665 SetDParam(0, STR_DEPOT_CAPTION);
1666 SetDParam(1, this->vli.vtype);
1667 SetDParam(2, this->vli.index);
1668 SetDParam(3, this->vscroll->GetCount());
1669 break;
1670 default: NOT_REACHED();
1672 break;
1677 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
1679 switch (widget) {
1680 case WID_VL_SORT_ORDER:
1681 /* draw arrow pointing up/down for ascending/descending sorting */
1682 this->DrawSortButtonState (dpi, widget, this->vehicles.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
1683 break;
1685 case WID_VL_LIST:
1686 this->DrawVehicleListItems (dpi, INVALID_VEHICLE, this->resize.step_height, r);
1687 break;
1691 void OnPaint (BlitArea *dpi) OVERRIDE
1693 this->BuildVehicleList();
1694 this->SortVehicleList();
1696 if (this->vehicles.Length() == 0 && this->IsWidgetLowered(WID_VL_MANAGE_VEHICLES_DROPDOWN)) {
1697 HideDropDownMenu(this);
1700 /* Hide the widgets that we will not use in this window
1701 * Some windows contains actions only fit for the owner */
1702 int plane_to_show = (this->owner == _local_company) ? BP_SHOW_BUTTONS : BP_HIDE_BUTTONS;
1703 NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VL_HIDE_BUTTONS);
1704 if (plane_to_show != nwi->shown_plane) {
1705 nwi->SetDisplayedPlane(plane_to_show);
1706 nwi->SetDirty(this);
1708 if (this->owner == _local_company) {
1709 this->SetWidgetDisabledState(WID_VL_AVAILABLE_VEHICLES, this->vli.type != VL_STANDARD);
1710 this->SetWidgetsDisabledState(this->vehicles.Length() == 0,
1711 WID_VL_MANAGE_VEHICLES_DROPDOWN,
1712 WID_VL_STOP_ALL,
1713 WID_VL_START_ALL,
1714 WIDGET_LIST_END);
1717 /* Set text of sort by dropdown widget. */
1718 this->GetWidget<NWidgetCore>(WID_VL_SORT_BY_PULLDOWN)->widget_data = this->vehicle_sorter_names[this->vehicles.SortType()];
1720 this->DrawWidgets (dpi);
1723 virtual void OnClick(Point pt, int widget, int click_count)
1725 switch (widget) {
1726 case WID_VL_SORT_ORDER: // Flip sorting method ascending/descending
1727 this->vehicles.ToggleSortOrder();
1728 this->SetDirty();
1729 break;
1731 case WID_VL_SORT_BY_PULLDOWN:// Select sorting criteria dropdown menu
1732 ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_VL_SORT_BY_PULLDOWN, 0,
1733 (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10));
1734 return;
1736 case WID_VL_LIST: { // Matrix to show vehicles
1737 uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_VL_LIST);
1738 if (id_v >= this->vehicles.Length()) return; // click out of list bound
1740 const Vehicle *v = this->vehicles[id_v];
1741 if (!VehicleClicked(v)) ShowVehicleViewWindow(v);
1742 break;
1745 case WID_VL_AVAILABLE_VEHICLES:
1746 ShowBuildVehicleWindow(INVALID_TILE, this->vli.vtype);
1747 break;
1749 case WID_VL_MANAGE_VEHICLES_DROPDOWN: {
1750 DropDownList *list = this->BuildActionDropdownList(VehicleListIdentifier::UnPack(this->window_number).type == VL_STANDARD, false);
1751 ShowDropDownList(this, list, 0, WID_VL_MANAGE_VEHICLES_DROPDOWN);
1752 break;
1755 case WID_VL_STOP_ALL:
1756 case WID_VL_START_ALL:
1757 DoCommandP(0, (1 << 1) | (widget == WID_VL_START_ALL ? (1 << 0) : 0), this->window_number, CMD_MASS_START_STOP);
1758 break;
1762 virtual void OnDropdownSelect(int widget, int index)
1764 switch (widget) {
1765 case WID_VL_SORT_BY_PULLDOWN:
1766 this->vehicles.SetSortType(index);
1767 break;
1768 case WID_VL_MANAGE_VEHICLES_DROPDOWN:
1769 assert(this->vehicles.Length() != 0);
1771 switch (index) {
1772 case ADI_REPLACE: // Replace window
1773 ShowReplaceGroupVehicleWindow(ALL_GROUP, this->vli.vtype);
1774 break;
1775 case ADI_SERVICE: // Send for servicing
1776 case ADI_DEPOT: // Send to Depots
1777 DoCommandP(0, DEPOT_MASS_SEND | (index == ADI_SERVICE ? DEPOT_SERVICE : (DepotCommand)0), this->window_number, CMD_SEND_VEHICLE_TO_DEPOT);
1778 break;
1780 default: NOT_REACHED();
1782 break;
1783 default: NOT_REACHED();
1785 this->SetDirty();
1788 virtual void OnTick()
1790 if (_pause_mode != PM_UNPAUSED) return;
1791 if (this->vehicles.NeedResort()) {
1792 StationID station = (this->vli.type == VL_STATION_LIST) ? this->vli.index : INVALID_STATION;
1794 DEBUG(misc, 3, "Periodic resort %d list company %d at station %d", this->vli.vtype, this->owner, station);
1795 this->SetDirty();
1799 virtual void OnResize()
1801 this->vscroll->SetCapacityFromWidget(this, WID_VL_LIST);
1805 * Some data on this window has become invalid.
1806 * @param data Information about the changed data.
1807 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1809 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1811 if (!gui_scope && HasBit(data, 31) && this->vli.type == VL_SHARED_ORDERS) {
1812 /* Needs to be done in command-scope, so everything stays valid */
1813 this->vli.index = GB(data, 0, 20);
1814 this->window_number = this->vli.Pack();
1815 this->vehicles.ForceRebuild();
1816 return;
1819 if (data == 0) {
1820 /* This needs to be done in command-scope to enforce rebuilding before resorting invalid data */
1821 this->vehicles.ForceRebuild();
1822 } else {
1823 this->vehicles.ForceResort();
1828 static WindowDesc::Prefs _vehicle_list_other_prefs ("list_vehicles");
1830 static WindowDesc::Prefs _vehicle_list_train_prefs ("list_vehicles_train");
1832 static const WindowDesc _vehicle_list_train_desc(
1833 WDP_AUTO, 325, 246,
1834 WC_TRAINS_LIST, WC_NONE,
1836 _nested_vehicle_list, lengthof(_nested_vehicle_list),
1837 &_vehicle_list_train_prefs
1840 static const WindowDesc _vehicle_list_roadveh_desc(
1841 WDP_AUTO, 260, 246,
1842 WC_ROADVEH_LIST, WC_NONE,
1844 _nested_vehicle_list, lengthof(_nested_vehicle_list),
1845 &_vehicle_list_other_prefs
1848 static const WindowDesc _vehicle_list_ship_desc(
1849 WDP_AUTO, 260, 246,
1850 WC_SHIPS_LIST, WC_NONE,
1852 _nested_vehicle_list, lengthof(_nested_vehicle_list),
1853 &_vehicle_list_other_prefs
1856 static const WindowDesc _vehicle_list_aircraft_desc(
1857 WDP_AUTO, 260, 246,
1858 WC_AIRCRAFT_LIST, WC_NONE,
1860 _nested_vehicle_list, lengthof(_nested_vehicle_list),
1861 &_vehicle_list_other_prefs
1864 static void ShowVehicleListWindowLocal(CompanyID company, VehicleListType vlt, VehicleType vehicle_type, uint32 unique_number)
1866 static const WindowDesc *const descs[VEH_COMPANY_END] = {
1867 &_vehicle_list_train_desc, // VEH_TRAIN
1868 &_vehicle_list_roadveh_desc, // VEH_ROAD
1869 &_vehicle_list_ship_desc, // VEH_SHIP
1870 &_vehicle_list_aircraft_desc, // VEH_AIRCRAFT
1873 if (!Company::IsValidID(company) && company != OWNER_NONE) return;
1875 WindowNumber num = VehicleListIdentifier(vlt, vehicle_type, company, unique_number).Pack();
1876 AllocateWindowDescFront<VehicleListWindow> (descs[vehicle_type], num);
1879 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type)
1881 /* If _settings_client.gui.advanced_vehicle_list > 1, display the Advanced list
1882 * if _settings_client.gui.advanced_vehicle_list == 1, display Advanced list only for local company
1883 * if _ctrl_pressed, do the opposite action (Advanced list x Normal list)
1886 if ((_settings_client.gui.advanced_vehicle_list > (uint)(company != _local_company)) != _ctrl_pressed) {
1887 ShowCompanyGroup(company, vehicle_type);
1888 } else {
1889 ShowVehicleListWindowLocal(company, VL_STANDARD, vehicle_type, company);
1893 void ShowVehicleListWindow(const Vehicle *v)
1895 ShowVehicleListWindowLocal(v->owner, VL_SHARED_ORDERS, v->type, v->FirstShared()->index);
1898 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, StationID station)
1900 ShowVehicleListWindowLocal(company, VL_STATION_LIST, vehicle_type, station);
1903 void ShowVehicleListWindow(CompanyID company, VehicleType vehicle_type, TileIndex depot_tile)
1905 uint16 depot_airport_index;
1907 if (vehicle_type == VEH_AIRCRAFT) {
1908 depot_airport_index = GetStationIndex(depot_tile);
1909 } else {
1910 depot_airport_index = GetDepotIndex(depot_tile);
1912 ShowVehicleListWindowLocal(company, VL_DEPOT_LIST, vehicle_type, depot_airport_index);
1916 /* Unified vehicle GUI - Vehicle Details Window */
1918 assert_compile(WID_VD_DETAILS_CARGO_CARRIED == WID_VD_DETAILS_CARGO_CARRIED + TDW_TAB_CARGO );
1919 assert_compile(WID_VD_DETAILS_TRAIN_VEHICLES == WID_VD_DETAILS_CARGO_CARRIED + TDW_TAB_INFO );
1920 assert_compile(WID_VD_DETAILS_CAPACITY_OF_EACH == WID_VD_DETAILS_CARGO_CARRIED + TDW_TAB_CAPACITY);
1921 assert_compile(WID_VD_DETAILS_TOTAL_CARGO == WID_VD_DETAILS_CARGO_CARRIED + TDW_TAB_TOTALS );
1923 /** Vehicle details widgets (other than train). */
1924 static const NWidgetPart _nested_nontrain_vehicle_details_widgets[] = {
1925 NWidget(NWID_HORIZONTAL),
1926 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1927 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1928 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 */),
1929 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1930 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1931 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1932 EndContainer(),
1933 NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetMinimalSize(405, 42), SetResize(1, 0), EndContainer(),
1934 NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_MIDDLE_DETAILS), SetMinimalSize(405, 45), SetResize(1, 0), EndContainer(),
1935 NWidget(NWID_HORIZONTAL),
1936 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_VD_DECREASE_SERVICING_INTERVAL), SetFill(0, 1),
1937 SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1938 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_VD_INCREASE_SERVICING_INTERVAL), SetFill(0, 1),
1939 SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_INCREASE_SERVICING_INTERVAL_TOOLTIP),
1940 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VD_SERVICE_INTERVAL_DROPDOWN), SetFill(0, 1),
1941 SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1942 NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1943 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1944 EndContainer(),
1947 /** Train details widgets. */
1948 static const NWidgetPart _nested_train_vehicle_details_widgets[] = {
1949 NWidget(NWID_HORIZONTAL),
1950 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1951 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VD_CAPTION), SetDataTip(STR_VEHICLE_DETAILS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1952 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 */),
1953 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1954 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1955 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1956 EndContainer(),
1957 NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_TOP_DETAILS), SetResize(1, 0), SetMinimalSize(405, 42), EndContainer(),
1958 NWidget(NWID_HORIZONTAL),
1959 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),
1960 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VD_SCROLLBAR),
1961 EndContainer(),
1962 NWidget(NWID_HORIZONTAL),
1963 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_VD_DECREASE_SERVICING_INTERVAL), SetFill(0, 1),
1964 SetDataTip(AWV_DECREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1965 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_VD_INCREASE_SERVICING_INTERVAL), SetFill(0, 1),
1966 SetDataTip(AWV_INCREASE, STR_VEHICLE_DETAILS_DECREASE_SERVICING_INTERVAL_TOOLTIP),
1967 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VD_SERVICE_INTERVAL_DROPDOWN), SetFill(0, 1),
1968 SetDataTip(STR_EMPTY, STR_SERVICE_INTERVAL_DROPDOWN_TOOLTIP),
1969 NWidget(WWT_PANEL, COLOUR_GREY, WID_VD_SERVICING_INTERVAL), SetFill(1, 1), SetResize(1, 0), EndContainer(),
1970 EndContainer(),
1971 NWidget(NWID_HORIZONTAL),
1972 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_DETAILS_CARGO_CARRIED), SetMinimalSize(96, 12),
1973 SetDataTip(STR_VEHICLE_DETAIL_TAB_CARGO, STR_VEHICLE_DETAILS_TRAIN_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1974 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_DETAILS_TRAIN_VEHICLES), SetMinimalSize(99, 12),
1975 SetDataTip(STR_VEHICLE_DETAIL_TAB_INFORMATION, STR_VEHICLE_DETAILS_TRAIN_INFORMATION_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1976 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_DETAILS_CAPACITY_OF_EACH), SetMinimalSize(99, 12),
1977 SetDataTip(STR_VEHICLE_DETAIL_TAB_CAPACITIES, STR_VEHICLE_DETAILS_TRAIN_CAPACITIES_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1978 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VD_DETAILS_TOTAL_CARGO), SetMinimalSize(99, 12),
1979 SetDataTip(STR_VEHICLE_DETAIL_TAB_TOTAL_CARGO, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CARGO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1980 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1981 EndContainer(),
1985 extern int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab);
1986 extern void DrawTrainDetails (const Train *v, BlitArea *dpi, int left, int right, int y, int vscroll_pos, uint16 vscroll_cap, TrainDetailsWindowTabs det_tab);
1987 extern void DrawRoadVehDetails (const Vehicle *v, BlitArea *dpi, int left, int right, int y);
1988 extern void DrawShipDetails (const Vehicle *v, BlitArea *dpi, int left, int right, int y);
1989 extern void DrawAircraftDetails (const Aircraft *v, BlitArea *dpi, int left, int right, int y);
1991 static StringID _service_interval_dropdown[] = {
1992 STR_VEHICLE_DETAILS_DEFAULT,
1993 STR_VEHICLE_DETAILS_DAYS,
1994 STR_VEHICLE_DETAILS_PERCENT,
1995 INVALID_STRING_ID,
1998 /** Class for managing the vehicle details window. */
1999 struct VehicleDetailsWindow : Window {
2000 TrainDetailsWindowTabs tab; ///< For train vehicles: which tab is displayed.
2001 Scrollbar *vscroll;
2003 /** Initialize a newly created vehicle details window */
2004 VehicleDetailsWindow (const WindowDesc *desc, WindowNumber window_number) :
2005 Window (desc), tab (TDW_TAB_CARGO), vscroll (NULL)
2007 const Vehicle *v = Vehicle::Get(window_number);
2009 this->CreateNestedTree();
2010 this->vscroll = (v->type == VEH_TRAIN ? this->GetScrollbar(WID_VD_SCROLLBAR) : NULL);
2011 this->InitNested(window_number);
2013 this->GetWidget<NWidgetCore>(WID_VD_RENAME_VEHICLE)->tool_tip = STR_VEHICLE_DETAILS_TRAIN_RENAME + v->type;
2015 this->owner = v->owner;
2019 * Some data on this window has become invalid.
2020 * @param data Information about the changed data.
2021 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
2023 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2025 if (data == VIWD_AUTOREPLACE) {
2026 /* Autoreplace replaced the vehicle.
2027 * Nothing to do for this window. */
2028 return;
2030 if (!gui_scope) return;
2031 const Vehicle *v = Vehicle::Get(this->window_number);
2032 if (v->type == VEH_ROAD) {
2033 const NWidgetBase *nwid_info = this->GetWidget<NWidgetBase>(WID_VD_MIDDLE_DETAILS);
2034 uint aimed_height = this->GetRoadVehDetailsHeight(v);
2035 /* If the number of articulated parts changes, the size of the window must change too. */
2036 if (aimed_height != nwid_info->current_y) {
2037 this->ReInit();
2043 * Gets the desired height for the road vehicle details panel.
2044 * @param v Road vehicle being shown.
2045 * @return Desired height in pixels.
2047 uint GetRoadVehDetailsHeight(const Vehicle *v)
2049 uint desired_height;
2050 if (v->HasArticulatedPart()) {
2051 /* An articulated RV has its text drawn under the sprite instead of after it, hence 15 pixels extra. */
2052 desired_height = WD_FRAMERECT_TOP + ScaleGUITrad(15) + 3 * FONT_HEIGHT_NORMAL + 2 + WD_FRAMERECT_BOTTOM;
2053 /* Add space for the cargo amount for each part. */
2054 for (const Vehicle *u = v; u != NULL; u = u->Next()) {
2055 if (u->cargo_cap != 0) desired_height += FONT_HEIGHT_NORMAL + 1;
2057 } else {
2058 desired_height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
2060 return desired_height;
2063 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2065 switch (widget) {
2066 case WID_VD_TOP_DETAILS: {
2067 Dimension dim = { 0, 0 };
2068 size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
2070 for (uint i = 0; i < 4; i++) SetDParamMaxValue(i, INT16_MAX);
2071 static const StringID info_strings[] = {
2072 STR_VEHICLE_INFO_MAX_SPEED,
2073 STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED,
2074 STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE,
2075 STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR,
2076 STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS
2078 for (uint i = 0; i < lengthof(info_strings); i++) {
2079 dim = maxdim(dim, GetStringBoundingBox(info_strings[i]));
2081 SetDParam(0, STR_VEHICLE_INFO_AGE);
2082 dim = maxdim(dim, GetStringBoundingBox(STR_VEHICLE_INFO_AGE_RUNNING_COST_YR));
2083 size->width = dim.width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
2084 break;
2087 case WID_VD_MIDDLE_DETAILS: {
2088 const Vehicle *v = Vehicle::Get(this->window_number);
2089 switch (v->type) {
2090 case VEH_ROAD:
2091 size->height = this->GetRoadVehDetailsHeight(v);
2092 break;
2094 case VEH_SHIP:
2095 size->height = WD_FRAMERECT_TOP + 4 * FONT_HEIGHT_NORMAL + 3 + WD_FRAMERECT_BOTTOM;
2096 break;
2098 case VEH_AIRCRAFT:
2099 size->height = WD_FRAMERECT_TOP + 5 * FONT_HEIGHT_NORMAL + 4 + WD_FRAMERECT_BOTTOM;
2100 break;
2102 default:
2103 NOT_REACHED(); // Train uses WID_VD_MATRIX instead.
2105 break;
2108 case WID_VD_MATRIX:
2109 resize->height = max(ScaleGUITrad(14), WD_MATRIX_TOP + FONT_HEIGHT_NORMAL + WD_MATRIX_BOTTOM);
2110 size->height = 4 * resize->height;
2111 break;
2113 case WID_VD_SERVICE_INTERVAL_DROPDOWN: {
2114 StringID *strs = _service_interval_dropdown;
2115 while (*strs != INVALID_STRING_ID) {
2116 *size = maxdim(*size, GetStringBoundingBox(*strs++));
2118 size->width += padding.width;
2119 size->height = FONT_HEIGHT_NORMAL + WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM;
2120 break;
2123 case WID_VD_SERVICING_INTERVAL:
2124 SetDParamMaxValue(0, MAX_SERVINT_DAYS); // Roughly the maximum interval
2125 SetDParamMaxValue(1, MAX_YEAR * DAYS_IN_YEAR); // Roughly the maximum year
2126 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;
2127 size->height = WD_FRAMERECT_TOP + FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
2128 break;
2132 /** Checks whether service interval is enabled for the vehicle. */
2133 static bool IsVehicleServiceIntervalEnabled(const VehicleType vehicle_type, CompanyID company_id)
2135 const VehicleDefaultSettings *vds = &Company::Get(company_id)->settings.vehicle;
2136 switch (vehicle_type) {
2137 default: NOT_REACHED();
2138 case VEH_TRAIN: return vds->servint_trains != 0;
2139 case VEH_ROAD: return vds->servint_roadveh != 0;
2140 case VEH_SHIP: return vds->servint_ships != 0;
2141 case VEH_AIRCRAFT: return vds->servint_aircraft != 0;
2146 * Draw the details for the given vehicle at the position of the Details windows
2148 * @param v current vehicle
2149 * @param dpi The area to draw on
2150 * @param left The left most coordinate to draw
2151 * @param right The right most coordinate to draw
2152 * @param y The y coordinate
2153 * @param vscroll_pos Position of scrollbar (train only)
2154 * @param vscroll_cap Number of lines currently displayed (train only)
2155 * @param det_tab Selected details tab (train only)
2157 static void DrawVehicleDetails (const Vehicle *v, BlitArea *dpi, int left, int right, int y, int vscroll_pos, uint vscroll_cap, TrainDetailsWindowTabs det_tab)
2159 switch (v->type) {
2160 case VEH_TRAIN: DrawTrainDetails (Train::From(v), dpi, left, right, y, vscroll_pos, vscroll_cap, det_tab); break;
2161 case VEH_ROAD: DrawRoadVehDetails (v, dpi, left, right, y); break;
2162 case VEH_SHIP: DrawShipDetails (v, dpi, left, right, y); break;
2163 case VEH_AIRCRAFT: DrawAircraftDetails (Aircraft::From(v), dpi, left, right, y); break;
2164 default: NOT_REACHED();
2168 virtual void SetStringParameters(int widget) const
2170 if (widget == WID_VD_CAPTION) SetDParam(0, Vehicle::Get(this->window_number)->index);
2173 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
2175 const Vehicle *v = Vehicle::Get(this->window_number);
2177 switch (widget) {
2178 case WID_VD_TOP_DETAILS: {
2179 int y = r.top + WD_FRAMERECT_TOP;
2181 /* Draw running cost */
2182 SetDParam(1, v->age / DAYS_IN_LEAP_YEAR);
2183 SetDParam(0, (v->age + DAYS_IN_YEAR < v->max_age) ? STR_VEHICLE_INFO_AGE : STR_VEHICLE_INFO_AGE_RED);
2184 SetDParam(2, v->max_age / DAYS_IN_LEAP_YEAR);
2185 SetDParam(3, v->GetDisplayRunningCost());
2186 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_AGE_RUNNING_COST_YR);
2187 y += FONT_HEIGHT_NORMAL;
2189 /* Draw max speed */
2190 StringID string;
2191 if (v->type == VEH_TRAIN ||
2192 (v->type == VEH_ROAD && _settings_game.vehicle.roadveh_acceleration_model != AM_ORIGINAL)) {
2193 const GroundVehicleCache *gcache = &GroundVehicleBase::From(v)->gcache;
2194 SetDParam(2, v->GetDisplayMaxSpeed());
2195 SetDParam(1, gcache->cached_power);
2196 SetDParam(0, gcache->cached_weight);
2197 SetDParam(3, gcache->cached_max_te / 1000);
2198 if (v->type == VEH_TRAIN && (_settings_game.vehicle.train_acceleration_model == AM_ORIGINAL ||
2199 GetRailTypeInfo(Train::From(v)->railtype)->acceleration_type == 2)) {
2200 string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED;
2201 } else {
2202 string = STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE;
2204 } else {
2205 SetDParam(0, v->GetDisplayMaxSpeed());
2206 if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->GetRange() > 0) {
2207 SetDParam(1, Aircraft::From(v)->GetRange());
2208 string = STR_VEHICLE_INFO_MAX_SPEED_RANGE;
2209 } else {
2210 string = STR_VEHICLE_INFO_MAX_SPEED;
2213 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, string);
2214 y += FONT_HEIGHT_NORMAL;
2216 /* Draw profit */
2217 SetDParam(0, v->GetDisplayProfitThisYear());
2218 SetDParam(1, v->GetDisplayProfitLastYear());
2219 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR);
2220 y += FONT_HEIGHT_NORMAL;
2222 /* Draw breakdown & reliability */
2223 SetDParam(0, ToPercent16(v->reliability));
2224 SetDParam(1, v->breakdowns_since_last_service);
2225 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS);
2226 break;
2229 case WID_VD_MATRIX:
2230 /* For trains only. */
2231 DrawVehicleDetails (v, dpi, r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, r.top + WD_MATRIX_TOP, this->vscroll->GetPosition(), this->vscroll->GetCapacity(), this->tab);
2232 break;
2234 case WID_VD_MIDDLE_DETAILS: {
2235 /* For other vehicles, at the place of the matrix. */
2236 bool rtl = _current_text_dir == TD_RTL;
2237 uint sprite_width = GetSingleVehicleWidth(v, EIT_IN_DETAILS) + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
2239 uint text_left = r.left + (rtl ? 0 : sprite_width);
2240 uint text_right = r.right - (rtl ? sprite_width : 0);
2242 /* Articulated road vehicles use a complete line. */
2243 if (v->type == VEH_ROAD && v->HasArticulatedPart()) {
2244 DrawVehicleImage (v, dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, EIT_IN_DETAILS);
2245 } else {
2246 uint sprite_left = rtl ? text_right : r.left;
2247 uint sprite_right = rtl ? r.right : text_left;
2249 DrawVehicleImage (v, dpi, sprite_left + WD_FRAMERECT_LEFT, sprite_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, EIT_IN_DETAILS);
2251 DrawVehicleDetails (v, dpi, text_left + WD_FRAMERECT_LEFT, text_right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, 0, 0, this->tab);
2252 break;
2255 case WID_VD_SERVICING_INTERVAL:
2256 /* Draw service interval text */
2257 SetDParam(0, v->GetServiceInterval());
2258 SetDParam(1, v->date_of_last_service);
2259 DrawString (dpi, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + (r.bottom - r.top + 1 - FONT_HEIGHT_NORMAL) / 2,
2260 v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT : STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS);
2261 break;
2265 /** Repaint vehicle details window. */
2266 void OnPaint (BlitArea *dpi) OVERRIDE
2268 const Vehicle *v = Vehicle::Get(this->window_number);
2270 this->SetWidgetDisabledState(WID_VD_RENAME_VEHICLE, v->owner != _local_company);
2272 if (v->type == VEH_TRAIN) {
2273 this->DisableWidget(this->tab + WID_VD_DETAILS_CARGO_CARRIED);
2274 this->vscroll->SetCount(GetTrainDetailsWndVScroll(v->index, this->tab));
2277 /* Disable service-scroller when interval is set to disabled */
2278 this->SetWidgetsDisabledState(!IsVehicleServiceIntervalEnabled(v->type, v->owner),
2279 WID_VD_INCREASE_SERVICING_INTERVAL,
2280 WID_VD_DECREASE_SERVICING_INTERVAL,
2281 WIDGET_LIST_END);
2283 StringID str = v->ServiceIntervalIsCustom() ?
2284 (v->ServiceIntervalIsPercent() ? STR_VEHICLE_DETAILS_PERCENT : STR_VEHICLE_DETAILS_DAYS) :
2285 STR_VEHICLE_DETAILS_DEFAULT;
2286 this->GetWidget<NWidgetCore>(WID_VD_SERVICE_INTERVAL_DROPDOWN)->widget_data = str;
2288 this->DrawWidgets (dpi);
2291 virtual void OnClick(Point pt, int widget, int click_count)
2293 switch (widget) {
2294 case WID_VD_RENAME_VEHICLE: { // rename
2295 const Vehicle *v = Vehicle::Get(this->window_number);
2296 SetDParam(0, v->index);
2297 ShowQueryString(STR_VEHICLE_NAME, STR_QUERY_RENAME_TRAIN_CAPTION + v->type,
2298 MAX_LENGTH_VEHICLE_NAME_CHARS, this, CS_ALPHANUMERAL, QSF_ENABLE_DEFAULT | QSF_LEN_IN_CHARS);
2299 break;
2302 case WID_VD_INCREASE_SERVICING_INTERVAL: // increase int
2303 case WID_VD_DECREASE_SERVICING_INTERVAL: { // decrease int
2304 int mod = _ctrl_pressed ? 5 : 10;
2305 const Vehicle *v = Vehicle::Get(this->window_number);
2307 mod = (widget == WID_VD_DECREASE_SERVICING_INTERVAL) ? -mod : mod;
2308 mod = GetServiceIntervalClamped(mod + v->GetServiceInterval(), v->ServiceIntervalIsPercent());
2309 if (mod == v->GetServiceInterval()) return;
2311 DoCommandP(v->tile, v->index, mod | (1 << 16) | (v->ServiceIntervalIsPercent() << 17), CMD_CHANGE_SERVICE_INT);
2312 break;
2315 case WID_VD_SERVICE_INTERVAL_DROPDOWN: {
2316 const Vehicle *v = Vehicle::Get(this->window_number);
2317 ShowDropDownMenu(this, _service_interval_dropdown, v->ServiceIntervalIsCustom() ? (v->ServiceIntervalIsPercent() ? 2 : 1) : 0, widget, 0, 0);
2318 break;
2321 case WID_VD_DETAILS_CARGO_CARRIED:
2322 case WID_VD_DETAILS_TRAIN_VEHICLES:
2323 case WID_VD_DETAILS_CAPACITY_OF_EACH:
2324 case WID_VD_DETAILS_TOTAL_CARGO:
2325 this->SetWidgetsDisabledState(false,
2326 WID_VD_DETAILS_CARGO_CARRIED,
2327 WID_VD_DETAILS_TRAIN_VEHICLES,
2328 WID_VD_DETAILS_CAPACITY_OF_EACH,
2329 WID_VD_DETAILS_TOTAL_CARGO,
2330 widget,
2331 WIDGET_LIST_END);
2333 this->tab = (TrainDetailsWindowTabs)(widget - WID_VD_DETAILS_CARGO_CARRIED);
2334 this->SetDirty();
2335 break;
2339 virtual void OnDropdownSelect(int widget, int index)
2341 switch (widget) {
2342 case WID_VD_SERVICE_INTERVAL_DROPDOWN: {
2343 const Vehicle *v = Vehicle::Get(this->window_number);
2344 bool iscustom = index != 0;
2345 bool ispercent = iscustom ? (index == 2) : Company::Get(v->owner)->settings.vehicle.servint_ispercent;
2346 uint16 interval = GetServiceIntervalClamped(v->GetServiceInterval(), ispercent);
2347 DoCommandP(v->tile, v->index, interval | (iscustom << 16) | (ispercent << 17), CMD_CHANGE_SERVICE_INT);
2348 break;
2353 virtual void OnQueryTextFinished(char *str)
2355 if (str == NULL) return;
2357 DoCommandP(0, this->window_number, 0, CMD_RENAME_VEHICLE, str);
2360 virtual void OnResize()
2362 NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_VD_MATRIX);
2363 if (nwi != NULL) {
2364 this->vscroll->SetCapacityFromWidget(this, WID_VD_MATRIX);
2369 /** Vehicle details window preferences. */
2370 static WindowDesc::Prefs _train_vehicle_details_prefs ("view_vehicle_details_train");
2372 /** Vehicle details window descriptor. */
2373 static const WindowDesc _train_vehicle_details_desc(
2374 WDP_AUTO, 405, 178,
2375 WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW,
2377 _nested_train_vehicle_details_widgets, lengthof(_nested_train_vehicle_details_widgets),
2378 &_train_vehicle_details_prefs
2381 /** Vehicle details window preferences for other vehicles than a train. */
2382 static WindowDesc::Prefs _nontrain_vehicle_details_prefs ("view_vehicle_details");
2384 /** Vehicle details window descriptor for other vehicles than a train. */
2385 static const WindowDesc _nontrain_vehicle_details_desc(
2386 WDP_AUTO, 405, 113,
2387 WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW,
2389 _nested_nontrain_vehicle_details_widgets, lengthof(_nested_nontrain_vehicle_details_widgets),
2390 &_nontrain_vehicle_details_prefs
2393 /** Shows the vehicle details window of the given vehicle. */
2394 static void ShowVehicleDetailsWindow(const Vehicle *v)
2396 DeleteWindowById(WC_VEHICLE_ORDERS, v->index, false);
2397 DeleteWindowById(WC_VEHICLE_TIMETABLE, v->index, false);
2398 AllocateWindowDescFront<VehicleDetailsWindow>((v->type == VEH_TRAIN) ? &_train_vehicle_details_desc : &_nontrain_vehicle_details_desc, v->index);
2402 /* Unified vehicle GUI - Vehicle View Window */
2404 /** Vehicle view widgets. */
2405 static const NWidgetPart _nested_vehicle_view_widgets[] = {
2406 NWidget(NWID_HORIZONTAL),
2407 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
2408 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VV_CAPTION), SetDataTip(STR_VEHICLE_VIEW_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2409 NWidget(WWT_DEBUGBOX, COLOUR_GREY),
2410 NWidget(WWT_SHADEBOX, COLOUR_GREY),
2411 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
2412 NWidget(WWT_STICKYBOX, COLOUR_GREY),
2413 EndContainer(),
2414 NWidget(NWID_HORIZONTAL),
2415 NWidget(WWT_PANEL, COLOUR_GREY),
2416 NWidget(WWT_INSET, COLOUR_GREY), SetPadding(2, 2, 2, 2),
2417 NWidget(NWID_VIEWPORT, INVALID_COLOUR, WID_VV_VIEWPORT), SetMinimalSize(226, 84), SetResize(1, 1), SetPadding(1, 1, 1, 1),
2418 EndContainer(),
2419 EndContainer(),
2420 NWidget(NWID_VERTICAL),
2421 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CENTER_MAIN_VIEW), SetMinimalSize(18, 18), SetDataTip(SPR_CENTRE_VIEW_VEHICLE, 0x0 /* filled later */),
2422 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VV_SELECT_DEPOT_CLONE),
2423 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_GOTO_DEPOT), SetMinimalSize(18, 18), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2424 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_CLONE), SetMinimalSize(18, 18), SetDataTip(0x0 /* filled later */, 0x0 /* filled later */),
2425 EndContainer(),
2426 /* For trains only, 'ignore signal' button. */
2427 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_FORCE_PROCEED), SetMinimalSize(18, 18),
2428 SetDataTip(SPR_IGNORE_SIGNALS, STR_VEHICLE_VIEW_TRAIN_IGNORE_SIGNAL_TOOLTIP),
2429 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VV_SELECT_REFIT_TURN),
2430 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_REFIT), SetMinimalSize(18, 18), SetDataTip(SPR_REFIT_VEHICLE, 0x0 /* filled later */),
2431 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_TURN_AROUND), SetMinimalSize(18, 18),
2432 SetDataTip(SPR_FORCE_VEHICLE_TURN, STR_VEHICLE_VIEW_ROAD_VEHICLE_REVERSE_TOOLTIP),
2433 EndContainer(),
2434 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_ORDERS), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_ORDERS, 0x0 /* filled later */),
2435 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_VV_SHOW_DETAILS), SetMinimalSize(18, 18), SetDataTip(SPR_SHOW_VEHICLE_DETAILS, 0x0 /* filled later */),
2436 NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(18, 0), SetResize(0, 1), EndContainer(),
2437 EndContainer(),
2438 EndContainer(),
2439 NWidget(NWID_HORIZONTAL),
2440 NWidget(WWT_PUSHBTN, COLOUR_GREY, WID_VV_START_STOP), SetMinimalTextLines(1, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 2), SetResize(1, 0), SetFill(1, 0),
2441 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
2442 EndContainer(),
2445 /** Vehicle view window preferences for all vehicles but trains. */
2446 static WindowDesc::Prefs _vehicle_view_prefs ("view_vehicle");
2448 /** Vehicle view window descriptor for all vehicles but trains. */
2449 static const WindowDesc _vehicle_view_desc(
2450 WDP_AUTO, 250, 116,
2451 WC_VEHICLE_VIEW, WC_NONE,
2453 _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets),
2454 &_vehicle_view_prefs
2457 /** Vehicle view window preferences for trains. */
2458 static WindowDesc::Prefs _train_view_prefs ("view_vehicle_train");
2461 * Vehicle view window descriptor for trains. Only minimum_height and
2462 * default_height are different for train view.
2464 static const WindowDesc _train_view_desc(
2465 WDP_AUTO, 250, 134,
2466 WC_VEHICLE_VIEW, WC_NONE,
2468 _nested_vehicle_view_widgets, lengthof(_nested_vehicle_view_widgets),
2469 &_train_view_prefs
2473 /* Just to make sure, nobody has changed the vehicle type constants, as we are
2474 using them for array indexing in a number of places here. */
2475 assert_compile(VEH_TRAIN == 0);
2476 assert_compile(VEH_ROAD == 1);
2477 assert_compile(VEH_SHIP == 2);
2478 assert_compile(VEH_AIRCRAFT == 3);
2480 /** Zoom levels for vehicle views indexed by vehicle type. */
2481 static const ZoomLevel _vehicle_view_zoom_levels[] = {
2482 ZOOM_LVL_TRAIN,
2483 ZOOM_LVL_ROADVEH,
2484 ZOOM_LVL_SHIP,
2485 ZOOM_LVL_AIRCRAFT,
2488 /* Constants for geometry of vehicle view viewport */
2489 static const int VV_INITIAL_VIEWPORT_WIDTH = 226;
2490 static const int VV_INITIAL_VIEWPORT_HEIGHT = 84;
2491 static const int VV_INITIAL_VIEWPORT_HEIGHT_TRAIN = 102;
2493 /** Error strings when starting/stopping a vehicle indexed by vehicle type. */
2494 static const StringID _vehicle_string_startstop_table[4] = {
2495 STR_ERROR_CAN_T_STOP_START_TRAIN,
2496 STR_ERROR_CAN_T_STOP_START_ROAD_VEHICLE,
2497 STR_ERROR_CAN_T_STOP_START_SHIP,
2498 STR_ERROR_CAN_T_STOP_START_AIRCRAFT,
2502 * This is the Callback method after attempting to start/stop a vehicle
2503 * @param result the result of the start/stop command
2504 * @param tile unused
2505 * @param p1 vehicle ID
2506 * @param p2 unused
2508 void CcStartStopVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2510 if (result.Failed()) return;
2512 if (p2 == 0) return;
2514 const Vehicle *v = Vehicle::GetIfValid(p1);
2515 if (v == NULL || !v->IsPrimaryVehicle() || v->owner != _local_company) return;
2517 StringID msg = (v->vehstatus & VS_STOPPED) ? STR_VEHICLE_COMMAND_STOPPED : STR_VEHICLE_COMMAND_STARTED;
2518 Point pt = RemapCoords(v->x_pos, v->y_pos, v->z_pos);
2519 AddTextEffect(msg, pt.x, pt.y, DAY_TICKS, TE_RISING);
2523 * Executes #CMD_START_STOP_VEHICLE for given vehicle.
2524 * @param v Vehicle to start/stop
2525 * @param texteffect Should a texteffect be shown?
2527 void StartStopVehicle(const Vehicle *v, bool texteffect)
2529 assert(v->IsPrimaryVehicle());
2530 DoCommandP(v->tile, v->index, texteffect ? 1 : 0, CMD_START_STOP_VEHICLE);
2533 /** Checks whether the vehicle may be refitted at the moment.*/
2534 static bool IsVehicleRefitable(const Vehicle *v)
2536 if (!v->IsStoppedInDepot()) return false;
2538 do {
2539 if (IsEngineRefittable(v->engine_type)) return true;
2540 } while (v->IsGroundVehicle() && (v = v->Next()) != NULL);
2542 return false;
2545 /** Window manager class for viewing a vehicle. */
2546 struct VehicleViewWindow : Window {
2547 private:
2548 /** Display planes available in the vehicle view window. */
2549 enum PlaneSelections {
2550 SEL_DC_GOTO_DEPOT, ///< Display 'goto depot' button in #WID_VV_SELECT_DEPOT_CLONE stacked widget.
2551 SEL_DC_CLONE, ///< Display 'clone vehicle' button in #WID_VV_SELECT_DEPOT_CLONE stacked widget.
2553 SEL_RT_REFIT, ///< Display 'refit' button in #WID_VV_SELECT_REFIT_TURN stacked widget.
2554 SEL_RT_TURN_AROUND, ///< Display 'turn around' button in #WID_VV_SELECT_REFIT_TURN stacked widget.
2556 SEL_DC_BASEPLANE = SEL_DC_GOTO_DEPOT, ///< First plane of the #WID_VV_SELECT_DEPOT_CLONE stacked widget.
2557 SEL_RT_BASEPLANE = SEL_RT_REFIT, ///< First plane of the #WID_VV_SELECT_REFIT_TURN stacked widget.
2561 * Display a plane in the window.
2562 * @param plane Plane to show.
2564 void SelectPlane(PlaneSelections plane)
2566 switch (plane) {
2567 case SEL_DC_GOTO_DEPOT:
2568 case SEL_DC_CLONE:
2569 this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE)->SetDisplayedPlane(plane - SEL_DC_BASEPLANE);
2570 break;
2572 case SEL_RT_REFIT:
2573 case SEL_RT_TURN_AROUND:
2574 this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN)->SetDisplayedPlane(plane - SEL_RT_BASEPLANE);
2575 break;
2577 default:
2578 NOT_REACHED();
2582 public:
2583 VehicleViewWindow (const WindowDesc *desc, WindowNumber window_number) : Window(desc)
2585 this->CreateNestedTree();
2587 /* Sprites for the 'send to depot' button indexed by vehicle type. */
2588 static const SpriteID vehicle_view_goto_depot_sprites[] = {
2589 SPR_SEND_TRAIN_TODEPOT,
2590 SPR_SEND_ROADVEH_TODEPOT,
2591 SPR_SEND_SHIP_TODEPOT,
2592 SPR_SEND_AIRCRAFT_TODEPOT,
2594 const Vehicle *v = Vehicle::Get(window_number);
2595 this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->widget_data = vehicle_view_goto_depot_sprites[v->type];
2597 /* Sprites for the 'clone vehicle' button indexed by vehicle type. */
2598 static const SpriteID vehicle_view_clone_sprites[] = {
2599 SPR_CLONE_TRAIN,
2600 SPR_CLONE_ROADVEH,
2601 SPR_CLONE_SHIP,
2602 SPR_CLONE_AIRCRAFT,
2604 this->GetWidget<NWidgetCore>(WID_VV_CLONE)->widget_data = vehicle_view_clone_sprites[v->type];
2606 switch (v->type) {
2607 case VEH_TRAIN:
2608 this->GetWidget<NWidgetCore>(WID_VV_TURN_AROUND)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REVERSE_TOOLTIP;
2609 break;
2611 case VEH_ROAD:
2612 break;
2614 case VEH_SHIP:
2615 case VEH_AIRCRAFT:
2616 this->SelectPlane(SEL_RT_REFIT);
2617 break;
2619 default: NOT_REACHED();
2621 this->InitNested(window_number);
2622 this->owner = v->owner;
2623 this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT)->InitializeViewport(this, this->window_number | (1 << 31), _vehicle_view_zoom_levels[v->type]);
2625 this->GetWidget<NWidgetCore>(WID_VV_START_STOP)->tool_tip = STR_VEHICLE_VIEW_TRAIN_STATE_START_STOP_TOOLTIP + v->type;
2626 this->GetWidget<NWidgetCore>(WID_VV_CENTER_MAIN_VIEW)->tool_tip = STR_VEHICLE_VIEW_TRAIN_LOCATION_TOOLTIP + v->type;
2627 this->GetWidget<NWidgetCore>(WID_VV_REFIT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_REFIT_TOOLTIP + v->type;
2628 this->GetWidget<NWidgetCore>(WID_VV_GOTO_DEPOT)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SEND_TO_DEPOT_TOOLTIP + v->type;
2629 this->GetWidget<NWidgetCore>(WID_VV_SHOW_ORDERS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_ORDERS_TOOLTIP + v->type;
2630 this->GetWidget<NWidgetCore>(WID_VV_SHOW_DETAILS)->tool_tip = STR_VEHICLE_VIEW_TRAIN_SHOW_DETAILS_TOOLTIP + v->type;
2631 this->GetWidget<NWidgetCore>(WID_VV_CLONE)->tool_tip = STR_VEHICLE_VIEW_CLONE_TRAIN_INFO + v->type;
2634 void OnDelete (void) FINAL_OVERRIDE
2636 DeleteWindowById(WC_VEHICLE_ORDERS, this->window_number, false);
2637 DeleteWindowById(WC_VEHICLE_REFIT, this->window_number, false);
2638 DeleteWindowById(WC_VEHICLE_DETAILS, this->window_number, false);
2639 DeleteWindowById(WC_VEHICLE_TIMETABLE, this->window_number, false);
2642 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
2644 const Vehicle *v = Vehicle::Get(this->window_number);
2645 switch (widget) {
2646 case WID_VV_START_STOP:
2647 size->height = max(size->height, max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).height, GetSpriteSize(SPR_FLAG_VEH_RUNNING).height) + WD_IMGBTN_TOP + WD_IMGBTN_BOTTOM);
2648 break;
2650 case WID_VV_FORCE_PROCEED:
2651 if (v->type != VEH_TRAIN) {
2652 size->height = 0;
2653 size->width = 0;
2655 break;
2657 case WID_VV_VIEWPORT:
2658 size->width = VV_INITIAL_VIEWPORT_WIDTH;
2659 size->height = (v->type == VEH_TRAIN) ? VV_INITIAL_VIEWPORT_HEIGHT_TRAIN : VV_INITIAL_VIEWPORT_HEIGHT;
2660 break;
2664 void OnPaint (BlitArea *dpi) OVERRIDE
2666 const Vehicle *v = Vehicle::Get(this->window_number);
2667 bool is_localcompany = v->owner == _local_company;
2668 bool refitable_and_stopped_in_depot = IsVehicleRefitable(v);
2670 this->SetWidgetDisabledState(WID_VV_GOTO_DEPOT, !is_localcompany);
2671 this->SetWidgetDisabledState(WID_VV_REFIT, !refitable_and_stopped_in_depot || !is_localcompany);
2672 this->SetWidgetDisabledState(WID_VV_CLONE, !is_localcompany);
2674 if (v->type == VEH_TRAIN) {
2675 this->SetWidgetLoweredState(WID_VV_FORCE_PROCEED, Train::From(v)->force_proceed == TFP_SIGNAL);
2676 this->SetWidgetDisabledState(WID_VV_FORCE_PROCEED, !is_localcompany);
2677 this->SetWidgetDisabledState(WID_VV_TURN_AROUND, !is_localcompany);
2680 this->DrawWidgets (dpi);
2683 virtual void SetStringParameters(int widget) const
2685 if (widget != WID_VV_CAPTION) return;
2687 const Vehicle *v = Vehicle::Get(this->window_number);
2688 SetDParam(0, v->index);
2691 void DrawWidget (BlitArea *dpi, const Rect &r, int widget) const OVERRIDE
2693 if (widget != WID_VV_START_STOP) return;
2695 const Vehicle *v = Vehicle::Get(this->window_number);
2696 StringID str;
2697 if (v->vehstatus & VS_CRASHED) {
2698 str = STR_VEHICLE_STATUS_CRASHED;
2699 } else if (v->type != VEH_AIRCRAFT && v->breakdown_ctr == 1) { // check for aircraft necessary?
2700 str = STR_VEHICLE_STATUS_BROKEN_DOWN;
2701 } else if (v->vehstatus & VS_STOPPED) {
2702 if (v->type == VEH_TRAIN) {
2703 if (v->cur_speed == 0) {
2704 if (Train::From(v)->gcache.cached_power == 0) {
2705 str = STR_VEHICLE_STATUS_TRAIN_NO_POWER;
2706 } else {
2707 str = STR_VEHICLE_STATUS_STOPPED;
2709 } else {
2710 SetDParam(0, v->GetDisplaySpeed());
2711 str = STR_VEHICLE_STATUS_TRAIN_STOPPING_VEL;
2713 } else { // no train
2714 str = STR_VEHICLE_STATUS_STOPPED;
2716 } else if (v->type == VEH_TRAIN && HasBit(Train::From(v)->flags, VRF_TRAIN_STUCK) && !v->current_order.IsType(OT_LOADING)) {
2717 str = STR_VEHICLE_STATUS_TRAIN_STUCK;
2718 } else if (v->type == VEH_AIRCRAFT && HasBit(Aircraft::From(v)->flags, VAF_DEST_TOO_FAR) && !v->current_order.IsType(OT_LOADING)) {
2719 str = STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR;
2720 } else { // vehicle is in a "normal" state, show current order
2721 switch (v->current_order.GetType()) {
2722 case OT_GOTO_STATION: {
2723 SetDParam(0, v->current_order.GetDestination());
2724 SetDParam(1, v->GetDisplaySpeed());
2725 str = STR_VEHICLE_STATUS_HEADING_FOR_STATION_VEL;
2726 break;
2729 case OT_GOTO_DEPOT: {
2730 SetDParam(0, v->type);
2731 SetDParam(1, v->current_order.GetDestination());
2732 SetDParam(2, v->GetDisplaySpeed());
2733 if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) {
2734 /* This case *only* happens when multiple nearest depot orders
2735 * follow each other (including an order list only one order: a
2736 * nearest depot order) and there are no reachable depots.
2737 * It is primarily to guard for the case that there is no
2738 * depot with index 0, which would be used as fallback for
2739 * evaluating the string in the status bar. */
2740 str = STR_EMPTY;
2741 } else if (v->current_order.GetDepotActionType() & ODATFB_HALT) {
2742 str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL;
2743 } else {
2744 str = STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL;
2746 break;
2749 case OT_LOADING:
2750 str = STR_VEHICLE_STATUS_LOADING_UNLOADING;
2751 break;
2753 case OT_GOTO_WAYPOINT: {
2754 assert(v->type == VEH_TRAIN || v->type == VEH_SHIP);
2755 SetDParam(0, v->current_order.GetDestination());
2756 str = STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL;
2757 SetDParam(1, v->GetDisplaySpeed());
2758 break;
2761 case OT_LEAVESTATION:
2762 if (v->type != VEH_AIRCRAFT) {
2763 str = STR_VEHICLE_STATUS_LEAVING;
2764 break;
2766 /* FALL THROUGH, if aircraft. Does this even happen? */
2768 default:
2769 if (v->GetNumManualOrders() == 0) {
2770 str = STR_VEHICLE_STATUS_NO_ORDERS_VEL;
2771 SetDParam(0, v->GetDisplaySpeed());
2772 } else {
2773 str = STR_EMPTY;
2775 break;
2779 /* Draw the flag plus orders. */
2780 bool rtl = (_current_text_dir == TD_RTL);
2781 uint text_offset = max(GetSpriteSize(SPR_FLAG_VEH_STOPPED).width, GetSpriteSize(SPR_FLAG_VEH_RUNNING).width) + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
2782 int text_left = r.left + (rtl ? (uint)WD_FRAMERECT_LEFT : text_offset);
2783 int text_right = r.right - (rtl ? text_offset : (uint)WD_FRAMERECT_RIGHT);
2784 int image_left = (rtl ? text_right + 1 : r.left) + WD_IMGBTN_LEFT;
2785 int image = ((v->vehstatus & VS_STOPPED) != 0) ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING;
2786 int lowered = this->IsWidgetLowered(WID_VV_START_STOP) ? 1 : 0;
2787 DrawSprite (dpi, image, PAL_NONE, image_left + lowered, r.top + WD_IMGBTN_TOP + lowered);
2788 DrawString (dpi, text_left + lowered, text_right + lowered, r.top + WD_FRAMERECT_TOP + lowered, str, TC_FROMSTRING, SA_HOR_CENTER);
2791 virtual void OnClick(Point pt, int widget, int click_count)
2793 const Vehicle *v = Vehicle::Get(this->window_number);
2795 switch (widget) {
2796 case WID_VV_START_STOP: // start stop
2797 if (_ctrl_pressed) {
2798 /* Scroll to current order destination */
2799 TileIndex tile = v->current_order.GetLocation(v);
2800 if (tile != INVALID_TILE) ScrollMainWindowToTile(tile);
2801 } else {
2802 /* Start/Stop */
2803 StartStopVehicle(v, false);
2805 break;
2806 case WID_VV_CENTER_MAIN_VIEW: {// center main view
2807 const Window *mainwindow = FindWindowById(WC_MAIN_WINDOW, 0);
2808 /* code to allow the main window to 'follow' the vehicle if the ctrl key is pressed */
2809 if (_ctrl_pressed && mainwindow->viewport->zoom <= ZOOM_LVL_OUT_4X) {
2810 mainwindow->viewport->follow_vehicle = v->index;
2811 } else {
2812 ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos);
2814 break;
2817 case WID_VV_GOTO_DEPOT: // goto hangar
2818 DoCommandP(v->tile, v->index | (_ctrl_pressed ? DEPOT_SERVICE : 0U), 0, CMD_SEND_VEHICLE_TO_DEPOT);
2819 break;
2820 case WID_VV_REFIT: // refit
2821 ShowVehicleRefitWindow (this, v);
2822 break;
2823 case WID_VV_SHOW_ORDERS: // show orders
2824 if (_ctrl_pressed) {
2825 ShowTimetableWindow(v);
2826 } else {
2827 ShowOrdersWindow(v);
2829 break;
2830 case WID_VV_SHOW_DETAILS: // show details
2831 ShowVehicleDetailsWindow(v);
2832 break;
2833 case WID_VV_CLONE: // clone vehicle
2834 /* Suppress the vehicle GUI when share-cloning.
2835 * There is no point to it except for starting the vehicle.
2836 * For starting the vehicle the player has to open the depot GUI, which is
2837 * most likely already open, but is also visible in the vehicle viewport. */
2838 DoCommandP(v->tile, v->index, _ctrl_pressed ? (1 | (1 << 31)) : 0, CMD_CLONE_VEHICLE);
2839 break;
2840 case WID_VV_TURN_AROUND: // turn around
2841 assert(v->IsGroundVehicle());
2842 if (v->type == VEH_TRAIN) {
2843 DoCommandP(v->tile, v->index, 0, CMD_REVERSE_TRAIN_DIRECTION);
2844 } else {
2845 DoCommandP(v->tile, v->index, 0, CMD_TURN_ROADVEH);
2847 break;
2848 case WID_VV_FORCE_PROCEED: // force proceed
2849 assert(v->type == VEH_TRAIN);
2850 DoCommandP(v->tile, v->index, 0, CMD_FORCE_TRAIN_PROCEED);
2851 break;
2855 virtual void OnResize()
2857 if (this->viewport != NULL) {
2858 NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(WID_VV_VIEWPORT);
2859 nvp->UpdateViewportCoordinates(this);
2863 virtual void OnTick()
2865 const Vehicle *v = Vehicle::Get(this->window_number);
2866 bool veh_stopped = v->IsStoppedInDepot();
2868 /* Widget WID_VV_GOTO_DEPOT must be hidden if the vehicle is already stopped in depot.
2869 * Widget WID_VV_CLONE_VEH should then be shown, since cloning is allowed only while in depot and stopped.
2871 PlaneSelections plane = veh_stopped ? SEL_DC_CLONE : SEL_DC_GOTO_DEPOT;
2872 NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_DEPOT_CLONE); // Selection widget 'send to depot' / 'clone'.
2873 if (nwi->shown_plane + SEL_DC_BASEPLANE != plane) {
2874 this->SelectPlane(plane);
2875 this->SetWidgetDirty(WID_VV_SELECT_DEPOT_CLONE);
2877 /* The same system applies to widget WID_VV_REFIT_VEH and VVW_WIDGET_TURN_AROUND.*/
2878 if (v->IsGroundVehicle()) {
2879 PlaneSelections plane = veh_stopped ? SEL_RT_REFIT : SEL_RT_TURN_AROUND;
2880 NWidgetStacked *nwi = this->GetWidget<NWidgetStacked>(WID_VV_SELECT_REFIT_TURN);
2881 if (nwi->shown_plane + SEL_RT_BASEPLANE != plane) {
2882 this->SelectPlane(plane);
2883 this->SetWidgetDirty(WID_VV_SELECT_REFIT_TURN);
2889 * Some data on this window has become invalid.
2890 * @param data Information about the changed data.
2891 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
2893 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
2895 if (data == VIWD_AUTOREPLACE) {
2896 /* Autoreplace replaced the vehicle.
2897 * Nothing to do for this window. */
2898 return;
2902 virtual bool IsNewGRFInspectable() const
2904 return ::IsNewGRFInspectable(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2907 virtual void ShowNewGRFInspectWindow() const
2909 ::ShowNewGRFInspectWindow(GetGrfSpecFeature(Vehicle::Get(this->window_number)->type), this->window_number);
2914 /** Shows the vehicle view window of the given vehicle. */
2915 void ShowVehicleViewWindow(const Vehicle *v)
2917 AllocateWindowDescFront<VehicleViewWindow>((v->type == VEH_TRAIN) ? &_train_view_desc : &_vehicle_view_desc, v->index);
2921 * Dispatch a "vehicle selected" event if any window waits for it.
2922 * @param v selected vehicle;
2923 * @return did any window accept vehicle selection?
2925 bool VehicleClicked(const Vehicle *v)
2927 assert(v != NULL);
2928 if (_pointer_mode < POINTER_VEHICLE) return false;
2930 v = v->First();
2931 if (!v->IsPrimaryVehicle()) return false;
2933 return _thd.GetCallbackWnd()->OnVehicleSelect(v);
2936 void StopGlobalFollowVehicle(const Vehicle *v)
2938 Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
2939 if (w != NULL && w->viewport->follow_vehicle == v->index) {
2940 ScrollMainWindowTo(v->x_pos, v->y_pos, v->z_pos, true); // lock the main view on the vehicle's last position
2941 w->viewport->follow_vehicle = INVALID_VEHICLE;
2947 * Callback for building a vehicle
2948 * @param result The result of the command.
2949 * @param tile The tile the command was executed on.
2950 * @param p1 Additional data for the command (for the #CommandProc)
2951 * @param p2 Additional data for the command (for the #CommandProc)
2953 void CcBuildVehicle(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
2955 if (result.Failed()) return;
2957 if (!IsRailDepotTile(tile) || RailVehInfo(p1)->railveh_type != RAILVEH_WAGON) {
2958 /* not a wagon */
2959 const Vehicle *v = Vehicle::Get(_new_vehicle_id);
2960 ShowVehicleViewWindow(v);
2961 return;
2964 /* find a locomotive in the depot. */
2965 const Vehicle *found = NULL;
2966 const Train *t;
2967 FOR_ALL_TRAINS(t) {
2968 if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) {
2969 if (found != NULL) return; // must be exactly one.
2970 found = t;
2974 /* if we found a loco, */
2975 if (found != NULL) {
2976 found = found->Last();
2977 /* put the new wagon at the end of the loco. */
2978 DoCommandP(0, _new_vehicle_id | (1 << 31), found->index, CMD_MOVE_RAIL_VEHICLE);
2979 InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
2984 * Get the error string to show for building a vehicle
2985 * @param tile The tile the command was executed on.
2986 * @param p1 Additional data for the command (for the #CommandProc)
2987 * @param p2 Additional data for the command (for the #CommandProc)
2988 * @param text Unused
2990 StringID GetErrBuildVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
2992 return GetErrBuildVeh(Engine::Get(GB(p1, 0, 16))->type);
2996 * Get the error string to show for building a vehicle
2997 * @param tile The tile the command was executed on.
2998 * @param p1 Additional data for the command (for the #CommandProc)
2999 * @param p2 Additional data for the command (for the #CommandProc)
3000 * @param text Unused
3002 StringID GetErrSellVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3004 return GetErrSellVeh(Vehicle::Get(GB(p1, 0, 20)));
3008 * Get the error string to show for refitting a vehicle
3009 * @param tile The tile the command was executed on.
3010 * @param p1 Additional data for the command (for the #CommandProc)
3011 * @param p2 Additional data for the command (for the #CommandProc)
3012 * @param text Unused
3014 StringID GetErrRefitVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3016 return GetErrRefitVeh(Vehicle::Get(p1));
3020 * Get the error string to show for cloning a vehicle
3021 * @param tile The tile the command was executed on.
3022 * @param p1 Additional data for the command (for the #CommandProc)
3023 * @param p2 Additional data for the command (for the #CommandProc)
3024 * @param text Unused
3026 StringID GetErrCloneVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3028 return GetErrBuildVeh(Vehicle::Get(p1));
3032 * Get the error string to show for renaming a vehicle
3033 * @param tile The tile the command was executed on.
3034 * @param p1 Additional data for the command (for the #CommandProc)
3035 * @param p2 Additional data for the command (for the #CommandProc)
3036 * @param text Unused
3038 StringID GetErrRenameVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3040 return STR_ERROR_CAN_T_RENAME_TRAIN + Vehicle::Get(p1)->type;
3044 * Get the error string to show for starting/stopping a vehicle
3045 * @param tile The tile the command was executed on.
3046 * @param p1 Additional data for the command (for the #CommandProc)
3047 * @param p2 Additional data for the command (for the #CommandProc)
3048 * @param text Unused
3050 StringID GetErrStartStopVehicle (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3052 return _vehicle_string_startstop_table[Vehicle::Get(p1)->type];
3056 * Get the error string to show for sending a vehicle to the depot
3057 * @param tile The tile the command was executed on.
3058 * @param p1 Additional data for the command (for the #CommandProc)
3059 * @param p2 Additional data for the command (for the #CommandProc)
3060 * @param text Unused
3062 StringID GetErrSendVehicleToDepot (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3064 VehicleType type;
3066 if (p1 & DEPOT_MASS_SEND) {
3067 /* Mass goto depot requested */
3068 VehicleListIdentifier vli;
3069 if (!vli.UnpackIfValid(p2)) return 0;
3070 type = vli.vtype;
3071 } else {
3072 type = Vehicle::Get(GB(p1, 0, 20))->type;
3075 return GetErrSendToDepot(type);
3079 * Get the error string to show for reversing a train
3080 * @param tile The tile the command was executed on.
3081 * @param p1 Additional data for the command (for the #CommandProc)
3082 * @param p2 Additional data for the command (for the #CommandProc)
3083 * @param text Unused
3085 StringID GetErrReverseTrain (TileIndex tile, uint32 p1, uint32 p2, const char *text)
3087 return p2 ? STR_ERROR_CAN_T_REVERSE_DIRECTION_RAIL_VEHICLE : STR_ERROR_CAN_T_REVERSE_DIRECTION_TRAIN;
3092 * Set the mouse cursor to look like a vehicle.
3093 * @param v Vehicle
3094 * @param image_type Type of vehicle image to use.
3096 void SetMouseCursorVehicle(const Vehicle *v, EngineImageType image_type)
3098 bool rtl = _current_text_dir == TD_RTL;
3100 _cursor.sprite_count = 0;
3101 int total_width = 0;
3102 for (;;) {
3103 PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
3104 VehicleSpriteSeq seq;
3105 v->GetImage(rtl ? DIR_E : DIR_W, image_type, &seq);
3106 if (_cursor.sprite_count + seq.count > lengthof(_cursor.sprite_seq)) break;
3108 for (uint i = 0; i < seq.count; ++i) {
3109 PaletteID pal2 = (v->vehstatus & VS_CRASHED) || !seq.seq[i].pal ? pal : seq.seq[i].pal;
3110 _cursor.sprite_seq[_cursor.sprite_count].sprite = seq.seq[i].sprite;
3111 _cursor.sprite_seq[_cursor.sprite_count].pal = pal2;
3112 _cursor.sprite_seq[_cursor.sprite_count].pos = rtl ? -total_width : total_width;
3113 _cursor.sprite_count++;
3116 total_width += GetSingleVehicleWidth(v, image_type);
3117 if (total_width >= 2 * (int)VEHICLEINFO_FULL_VEHICLE_WIDTH) break;
3119 v = v->Next();
3120 if ((v == NULL) || !v->IsArticulatedPart()) break;
3123 int offs = ((int)VEHICLEINFO_FULL_VEHICLE_WIDTH - total_width) / 2;
3124 if (rtl) offs = -offs;
3125 for (uint i = 0; i < _cursor.sprite_count; ++i) {
3126 _cursor.sprite_seq[i].pos += offs;
3129 UpdateCursorSize();