Update readme.md
[openttd-joker.git] / src / departures_gui.cpp
blob3ef89ea06797817a52338eab4ba2be3ddc998962
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 departures_gui.cpp Scheduled departures from a station. */
12 #include "stdafx.h"
13 #include "debug.h"
14 #include "gui.h"
15 #include "textbuf_gui.h"
16 #include "strings_func.h"
17 #include "window_func.h"
18 #include "vehicle_func.h"
19 #include "string_func.h"
20 #include "window_gui.h"
21 #include "timetable.h"
22 #include "vehiclelist.h"
23 #include "company_base.h"
24 #include "date_func.h"
25 #include "departures_gui.h"
26 #include "station_base.h"
27 #include "vehicle_gui_base.h"
28 #include "vehicle_base.h"
29 #include "vehicle_gui.h"
30 #include "order_base.h"
31 #include "settings_type.h"
32 #include "core/smallvec_type.hpp"
33 #include "date_type.h"
34 #include "company_type.h"
35 #include "departures_func.h"
36 #include "cargotype.h"
38 #include "table/sprites.h"
39 #include "table/strings.h"
41 static const NWidgetPart _nested_departures_list[] = {
42 NWidget(NWID_HORIZONTAL),
43 NWidget(WWT_CLOSEBOX, COLOUR_BROWN),
44 NWidget(WWT_CAPTION, COLOUR_BROWN, WID_DB_CAPTION), SetDataTip(STR_DEPARTURES_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
45 NWidget(WWT_SHADEBOX, COLOUR_BROWN),
46 NWidget(WWT_DEFSIZEBOX, COLOUR_BROWN),
47 NWidget(WWT_STICKYBOX, COLOUR_BROWN),
48 EndContainer(),
50 NWidget(NWID_HORIZONTAL),
51 NWidget(WWT_MATRIX, COLOUR_BROWN, WID_DB_LIST), SetMinimalSize(0, 0), SetFill(1, 0), SetResize(1, 1), SetScrollbar(WID_DB_SCROLLBAR),
52 NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_DB_SCROLLBAR),
53 EndContainer(),
55 NWidget(NWID_HORIZONTAL),
56 NWidget(WWT_PANEL, COLOUR_BROWN), SetMinimalSize(0, 12), SetResize(1, 0), SetFill(1, 1), EndContainer(),
57 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_PAX), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_PAX, STR_DEPARTURES_PAX_TOOLTIP),
58 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_FREIGHT), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_FREIGHT, STR_DEPARTURES_FREIGHT_TOOLTIP),
59 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_ARRS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_ARRIVALS, STR_DEPARTURES_ARRIVALS_TOOLTIP),
60 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_DEPS), SetMinimalSize(6, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_DEPARTURES, STR_DEPARTURES_DEPARTURES_TOOLTIP),
61 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_VIA), SetMinimalSize(11, 12), SetFill(0, 1), SetDataTip(STR_DEPARTURES_VIA_BUTTON, STR_DEPARTURES_VIA_TOOLTIP),
62 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_TRAINS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_TRAIN, STR_STATION_VIEW_SCHEDULED_TRAINS_TOOLTIP),
63 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_ROADVEHS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_LORRY, STR_STATION_VIEW_SCHEDULED_ROAD_VEHICLES_TOOLTIP),
64 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_SHIPS), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_SHIP, STR_STATION_VIEW_SCHEDULED_SHIPS_TOOLTIP),
65 NWidget(WWT_TEXTBTN, COLOUR_BROWN, WID_DB_SHOW_PLANES), SetMinimalSize(14, 12), SetFill(0, 1), SetDataTip(STR_PLANE, STR_STATION_VIEW_SCHEDULED_AIRCRAFT_TOOLTIP),
66 NWidget(WWT_RESIZEBOX, COLOUR_BROWN),
67 EndContainer(),
70 static WindowDesc _departures_desc(
71 WDP_AUTO, nullptr, 260, 246,
72 WC_DEPARTURES_BOARD, WC_NONE,
74 _nested_departures_list, lengthof(_nested_departures_list)
77 static uint cached_date_width = 0; ///< The cached maximum width required to display a date.
78 static uint cached_status_width = 0; ///< The cached maximum width required to show the status field.
79 static uint cached_date_arrow_width = 0; ///< The cached width of the red/green arrows that may be displayed alongside times.
80 static bool cached_arr_dep_display_method; ///< Whether to show departures and arrivals on a single line.
82 struct DeparturesWindow : public Window {
83 protected:
84 StationID station; ///< The station whose departures we're showing.
85 DepartureList *departures; ///< The current list of departures from this station.
86 DepartureList *arrivals; ///< The current list of arrivals from this station.
87 uint entry_height; ///< The height of an entry in the departures list.
88 uint tick_count; ///< The number of ticks that have elapsed since the window was created. Used for scrolling text.
89 int calc_tick_countdown; ///< The number of ticks to wait until recomputing the departure list. Signed in case it goes below zero.
90 bool show_types[4]; ///< The vehicle types to show in the departure list.
91 bool departure_types[3]; ///< The types of departure to show in the departure list.
92 bool show_pax; ///< Show passenger vehicles
93 bool show_freight; ///< Show freight vehicles
94 bool cargo_buttons_disabled;///< Show pax/freight buttons disabled
95 uint min_width; ///< The minimum width of this window.
96 Scrollbar *vscroll;
98 virtual uint GetMinWidth() const;
99 static void RecomputeDateWidth();
100 virtual void DrawDeparturesListItems(const Rect &r) const;
101 void DeleteDeparturesList(DepartureList* list);
103 void ToggleCargoFilter(int widget, bool &flag)
105 flag = !flag;
106 this->SetWidgetLoweredState(widget, flag);
107 /* We need to recompute the departures list. */
108 this->calc_tick_countdown = 0;
109 /* We need to redraw the button that was pressed. */
110 this->SetWidgetDirty(widget);
113 void SetCargoFilterDisabledState()
115 this->cargo_buttons_disabled = _settings_client.gui.departure_only_passengers;
116 this->SetWidgetDisabledState(WID_DB_SHOW_PAX, cargo_buttons_disabled);
117 this->SetWidgetDisabledState(WID_DB_SHOW_FREIGHT, cargo_buttons_disabled);
118 if (this->cargo_buttons_disabled) {
119 this->show_pax = true;
120 this->LowerWidget(WID_DB_SHOW_PAX);
121 this->show_freight = false;
122 this->RaiseWidget(WID_DB_SHOW_FREIGHT);
126 public:
128 DeparturesWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc),
129 station(window_number),
130 departures(new DepartureList()),
131 arrivals(new DepartureList()),
132 tick_count(0),
133 calc_tick_countdown(0),
134 min_width(400)
136 const Dimension arrow_dimensions = GetSpriteSize(SPR_ARROW_DOWN);
137 this->entry_height = 1 + std::max(arrow_dimensions.height, uint(FONT_HEIGHT_NORMAL)) + 1 +
138 (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1;
139 this->CreateNestedTree();
140 this->vscroll = this->GetScrollbar(WID_DB_SCROLLBAR);
141 this->FinishInitNested(window_number);
143 /* By default, only show departures. */
144 departure_types[0] = true;
145 departure_types[1] = false;
146 departure_types[2] = false;
147 show_pax = true;
148 show_freight = true;
149 this->LowerWidget(WID_DB_SHOW_DEPS);
150 this->RaiseWidget(WID_DB_SHOW_ARRS);
151 this->RaiseWidget(WID_DB_SHOW_VIA);
152 this->LowerWidget(WID_DB_SHOW_PAX);
153 this->LowerWidget(WID_DB_SHOW_FREIGHT);
154 this->SetCargoFilterDisabledState();
156 for (uint i = 0; i < 4; ++i) {
157 show_types[i] = true;
158 this->LowerWidget(WID_DB_SHOW_TRAINS + i);
162 virtual ~DeparturesWindow()
164 this->DeleteDeparturesList(departures);
165 this->DeleteDeparturesList(this->arrivals);
168 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
170 switch (widget) {
171 case WID_DB_LIST:
172 resize->height = DeparturesWindow::entry_height;
173 size->height = 2 * resize->height;
174 break;
178 virtual void SetStringParameters(int widget) const
180 if (widget == WID_DB_CAPTION) {
181 const Station *st = Station::Get(this->station);
182 SetDParam(0, st->index);
186 virtual void OnClick(Point pt, int widget, int click_count)
188 switch (widget) {
189 case WID_DB_SHOW_TRAINS: // Show trains to this station
190 case WID_DB_SHOW_ROADVEHS: // Show road vehicles to this station
191 case WID_DB_SHOW_SHIPS: // Show ships to this station
192 case WID_DB_SHOW_PLANES: { // Show aircraft to this station
193 if (_ctrl_pressed) {
194 for (int w = WID_DB_SHOW_TRAINS; w <= WID_DB_SHOW_PLANES; w++) {
195 if (w == widget) {
196 this->show_types[w - WID_DB_SHOW_TRAINS] = true;
197 this->LowerWidget(w);
198 } else {
199 this->show_types[w - WID_DB_SHOW_TRAINS] = false;
200 this->RaiseWidget(w);
203 } else {
204 this->show_types[widget - WID_DB_SHOW_TRAINS] = !this->show_types[widget - WID_DB_SHOW_TRAINS];
205 if (this->show_types[widget - WID_DB_SHOW_TRAINS]) {
206 this->LowerWidget(widget);
208 else {
209 this->RaiseWidget(widget);
212 /* We need to recompute the departures list. */
213 this->calc_tick_countdown = 0;
214 /* We need to redraw the button that was pressed. */
215 this->SetWidgetDirty(widget);
216 break;
219 case WID_DB_SHOW_DEPS:
220 case WID_DB_SHOW_ARRS:
221 if (_settings_client.gui.departure_show_both) break;
222 /* FALL THROUGH */
224 case WID_DB_SHOW_VIA:
226 this->departure_types[widget - WID_DB_SHOW_DEPS] = !this->departure_types[widget - WID_DB_SHOW_DEPS];
227 if (this->departure_types[widget - WID_DB_SHOW_DEPS]) {
228 this->LowerWidget(widget);
229 } else {
230 this->RaiseWidget(widget);
233 if (!this->departure_types[0]) {
234 this->RaiseWidget(WID_DB_SHOW_VIA);
235 this->DisableWidget(WID_DB_SHOW_VIA);
236 } else {
237 this->EnableWidget(WID_DB_SHOW_VIA);
239 if (this->departure_types[2]) {
240 this->LowerWidget(WID_DB_SHOW_VIA);
243 /* We need to recompute the departures list. */
244 this->calc_tick_countdown = 0;
245 /* We need to redraw the button that was pressed. */
246 this->SetWidgetDirty(widget);
247 break;
249 case WID_DB_LIST: { // Matrix to show departures
250 /* We need to find the departure corresponding to where the user clicked. */
251 uint32 id_v = (pt.y - this->GetWidget<NWidgetBase>(WID_DB_LIST)->pos_y) / this->entry_height;
253 if (id_v >= this->vscroll->GetCapacity()) return; // click out of bounds
255 id_v += this->vscroll->GetPosition();
257 if (id_v >= (this->departures->Length() + this->arrivals->Length())) return; // click out of list bound
259 uint departure = 0;
260 uint arrival = 0;
262 /* Draw each departure. */
263 for (uint i = 0; i <= id_v; ++i) {
264 const Departure *d;
266 if (arrival == this->arrivals->Length()) {
267 d = (*(this->departures))[departure++];
268 } else if (departure == this->departures->Length()) {
269 d = (*(this->arrivals))[arrival++];
270 } else {
271 d = (*(this->departures))[departure];
272 const Departure *a = (*(this->arrivals))[arrival];
274 if (a->scheduled_date < d->scheduled_date) {
275 d = a;
276 arrival++;
277 } else {
278 departure++;
282 if (i == id_v) {
283 const Vehicle* vehicle = Vehicle::GetIfValid(d->vehicle);
284 assert(vehicle != nullptr);
285 ShowVehicleViewWindow(vehicle);
286 break;
290 break;
293 case WID_DB_SHOW_PAX:
294 this->ToggleCargoFilter(widget, this->show_pax);
295 break;
297 case WID_DB_SHOW_FREIGHT:
298 this->ToggleCargoFilter(widget, this->show_freight);
300 break;
304 virtual void OnTick()
306 if (_pause_mode == PM_UNPAUSED) {
307 this->tick_count += 1;
308 this->calc_tick_countdown -= 1;
311 /* Recompute the minimum date display width if the cached one is no longer valid. */
312 if (cached_date_width == 0 || _settings_client.gui.departure_show_both != cached_arr_dep_display_method) {
313 this->RecomputeDateWidth();
316 if (this->cargo_buttons_disabled != _settings_client.gui.departure_only_passengers) {
317 this->SetCargoFilterDisabledState();
318 this->calc_tick_countdown = 0;
319 this->SetWidgetDirty(WID_DB_SHOW_PAX);
320 this->SetWidgetDirty(WID_DB_SHOW_FREIGHT);
323 /* We need to redraw the scrolling text in its new position. */
324 this->SetWidgetDirty(WID_DB_LIST);
326 /* Recompute the list of departures if we're due to. */
327 if (this->calc_tick_countdown <= 0) {
328 this->calc_tick_countdown = _settings_client.gui.departure_calc_frequency;
329 this->DeleteDeparturesList(this->departures);
330 this->DeleteDeparturesList(this->arrivals);
331 bool show_pax = _settings_client.gui.departure_only_passengers ? true : this->show_pax;
332 bool show_freight = _settings_client.gui.departure_only_passengers ? false : this->show_freight;
333 this->departures = (this->departure_types[0] ? MakeDepartureList(this->station, this->show_types, D_DEPARTURE, this->departure_types[2], show_pax, show_freight) : new DepartureList());
334 this->arrivals = (this->departure_types[1] && !_settings_client.gui.departure_show_both ? MakeDepartureList(this->station, this->show_types, D_ARRIVAL, false, show_pax, show_freight) : new DepartureList());
335 this->SetWidgetDirty(WID_DB_LIST);
338 uint new_width = this->GetMinWidth();
340 if (new_width != this->min_width) {
341 NWidgetCore *n = this->GetWidget<NWidgetCore>(WID_DB_LIST);
342 n->SetMinimalSize(new_width, 0);
343 this->ReInit();
344 this->min_width = new_width;
347 uint new_height = 1 + FONT_HEIGHT_NORMAL + 1 + (_settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL) + 1 + 1;
349 if (new_height != this->entry_height) {
350 this->entry_height = new_height;
351 this->SetWidgetDirty(WID_DB_LIST);
352 this->ReInit();
356 virtual void OnPaint()
358 if (_settings_client.gui.departure_show_both) {
359 this->DisableWidget(WID_DB_SHOW_ARRS);
360 this->DisableWidget(WID_DB_SHOW_DEPS);
361 } else {
362 this->EnableWidget(WID_DB_SHOW_ARRS);
363 this->EnableWidget(WID_DB_SHOW_DEPS);
366 this->vscroll->SetCount(min(_settings_client.gui.max_departures, this->departures->Length() + this->arrivals->Length()));
367 this->DrawWidgets();
370 virtual void DrawWidget(const Rect &r, int widget) const
372 switch (widget) {
373 case WID_DB_LIST:
374 this->DrawDeparturesListItems(r);
375 break;
379 virtual void OnResize()
381 this->vscroll->SetCapacityFromWidget(this, WID_DB_LIST);
382 this->GetWidget<NWidgetCore>(WID_DB_LIST)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
387 * Shows a window of scheduled departures for a station.
388 * @param station the station to show a departures window for
390 void ShowStationDepartures(StationID station)
392 AllocateWindowDescFront<DeparturesWindow>(&_departures_desc, station);
395 void DeparturesWindow::RecomputeDateWidth()
397 cached_date_width = 0;
398 cached_status_width = 0;
399 cached_arr_dep_display_method = _settings_client.gui.departure_show_both;
401 cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME)).width, cached_status_width);
402 cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_DELAYED)).width, cached_status_width);
403 cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED)).width, cached_status_width);
405 uint interval = _settings_client.gui.ticks_per_minute;
407 uint count = 24*60;
409 for (uint i = 0; i < count; ++i) {
410 SetDParam(0, INT_MAX - (i*interval));
411 SetDParam(1, INT_MAX - (i*interval));
412 cached_date_width = max(GetStringBoundingBox(cached_arr_dep_display_method ? STR_DEPARTURES_TIME_BOTH : STR_DEPARTURES_TIME_DEP).width, cached_date_width);
413 cached_status_width = max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED)).width, cached_status_width);
416 SetDParam(0, 0);
417 cached_date_arrow_width = GetStringBoundingBox(STR_DEPARTURES_TIME_DEP).width - GetStringBoundingBox(STR_DEPARTURES_TIME).width;
419 if (!_settings_client.gui.departure_show_both) {
420 cached_date_width -= cached_date_arrow_width;
424 uint DeparturesWindow::GetMinWidth() const
426 uint result = 0;
428 /* Time */
429 result = cached_date_width;
431 /* Vehicle type icon */
432 result += _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
434 /* Status */
435 result += cached_status_width;
437 /* Find the maximum company name width. */
438 int toc_width = 0;
440 /* Find the maximum company name width. */
441 int group_width = 0;
443 /* Find the maximum vehicle name width. */
444 int veh_width = 0;
446 if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
447 for (uint i = 0; i < 4; ++i) {
448 VehicleList vehicles;
450 /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
451 if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station))) {
452 /* Something went wrong: panic! */
453 continue;
456 for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
457 SetDParam(0, (uint64)((*v)->index));
458 int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
459 if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
461 if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
462 SetDParam(0, (uint64)((*v)->group_id));
463 width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
464 if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
467 SetDParam(0, (uint64)((*v)->owner));
468 width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
469 if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
474 result += toc_width + veh_width + group_width;
476 return result + 140;
480 * Deletes this window's departure list.
482 void DeparturesWindow::DeleteDeparturesList(DepartureList *list)
484 /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */
485 for (uint i = 0; i < list->Length(); ++i) {
486 Departure **d = list->Get(i);
487 delete *d;
488 /* Make sure a double free doesn't happen. */
489 *d = nullptr;
491 list->Reset();
492 delete list;
493 list = nullptr;
497 * Draws a list of departures.
499 void DeparturesWindow::DrawDeparturesListItems(const Rect &r) const
501 int left = r.left + WD_MATRIX_LEFT;
502 int right = r.right - WD_MATRIX_RIGHT;
504 bool rtl = _current_text_dir == TD_RTL;
505 bool ltr = !rtl;
507 int text_offset = WD_FRAMERECT_RIGHT;
508 int text_left = left + (rtl ? 0 : text_offset);
509 int text_right = right - (rtl ? text_offset : 0);
511 int y = r.top + 1;
512 uint max_departures = min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->departures->Length() + this->arrivals->Length());
514 if (max_departures > _settings_client.gui.max_departures) {
515 max_departures = _settings_client.gui.max_departures;
518 byte small_font_size = _settings_client.gui.departure_larger_font ? FONT_HEIGHT_NORMAL : FONT_HEIGHT_SMALL;
520 /* Draw the black background. */
521 GfxFillRect(r.left + 1, r.top, r.right - 1, r.bottom, PC_BLACK);
523 /* Nothing selected? Then display the information text. */
524 bool none_selected[2] = {true, true};
525 for (uint i = 0; i < 4; ++i)
527 if (this->show_types[i]) {
528 none_selected[0] = false;
529 break;
533 for (uint i = 0; i < 2; ++i)
535 if (this->departure_types[i]) {
536 none_selected[1] = false;
537 break;
541 if (none_selected[0] || none_selected[1]) {
542 DrawString(text_left, text_right, y + 1, STR_DEPARTURES_NONE_SELECTED);
543 return;
546 /* No scheduled departures? Then display the information text. */
547 if (max_departures == 0) {
548 DrawString(text_left, text_right, y + 1, STR_DEPARTURES_EMPTY);
549 return;
552 /* Find the maximum possible width of the departure time and "Expt <time>" fields. */
553 int time_width = cached_date_width;
555 if (!_settings_client.gui.departure_show_both) {
556 time_width += (departure_types[0] && departure_types[1] ? cached_date_arrow_width : 0);
559 /* Vehicle type icon */
560 int type_width = _settings_client.gui.departure_show_vehicle_type ? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE)).width : 0;
562 /* Find the maximum width of the status field */
563 int status_width = cached_status_width;
565 /* Find the width of the "Calling at:" field. */
566 int calling_at_width = (GetStringBoundingBox(_settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)).width;
568 /* Find the maximum company name width. */
569 int toc_width = 0;
571 /* Find the maximum group name width. */
572 int group_width = 0;
574 /* Find the maximum vehicle name width. */
575 int veh_width = 0;
577 if (_settings_client.gui.departure_show_vehicle || _settings_client.gui.departure_show_company || _settings_client.gui.departure_show_group) {
578 for (uint i = 0; i < 4; ++i) {
579 VehicleList vehicles;
581 /* MAX_COMPANIES is probably the wrong thing to put here, but it works. GenerateVehicleSortList doesn't check the company when the type of list is VL_STATION_LIST (r20801). */
582 if (!GenerateVehicleSortList(&vehicles, VehicleListIdentifier(VL_STATION_LIST, (VehicleType)(VEH_TRAIN + i), MAX_COMPANIES, station))) {
583 /* Something went wrong: panic! */
584 continue;
587 for (const Vehicle **v = vehicles.Begin(); v != vehicles.End(); v++) {
588 SetDParam(0, (uint64)((*v)->index));
589 int width = (GetStringBoundingBox(STR_DEPARTURES_VEH)).width;
590 if (_settings_client.gui.departure_show_vehicle && width > veh_width) veh_width = width;
592 if ((*v)->group_id != INVALID_GROUP && (*v)->group_id != DEFAULT_GROUP) {
593 SetDParam(0, (uint64)((*v)->group_id));
594 width = (GetStringBoundingBox(STR_DEPARTURES_GROUP)).width;
595 if (_settings_client.gui.departure_show_group && width > group_width) group_width = width;
598 SetDParam(0, (uint64)((*v)->owner));
599 width = (GetStringBoundingBox(STR_DEPARTURES_TOC)).width;
600 if (_settings_client.gui.departure_show_company && width > toc_width) toc_width = width;
605 uint departure = 0;
606 uint arrival = 0;
608 /* Draw each departure. */
609 for (uint i = 0; i < max_departures; ++i) {
610 const Departure *d;
612 if (arrival == this->arrivals->Length()) {
613 assert(departure < this->departures->Length());
614 d = (*(this->departures))[departure++];
616 else if (departure == this->departures->Length()) {
617 assert(arrival < this->arrivals->Length());
618 d = (*(this->arrivals))[arrival++];
620 else {
621 assert(departure < this->departures->Length());
622 assert(arrival < this->arrivals->Length());
623 d = (*(this->departures))[departure];
624 const Departure *a = (*(this->arrivals))[arrival];
626 if (a->scheduled_date < d->scheduled_date) {
627 d = a;
628 arrival++;
630 else {
631 departure++;
635 if (i < this->vscroll->GetPosition()) {
636 continue;
639 /* If for some reason the departure is too far in the future or is at a negative time, skip it. */
640 if ((d->scheduled_date / DEFAULT_DAY_TICKS) > MAX_DAY ||
641 d->scheduled_date < 0) {
642 continue;
645 if (d->terminus.station == INVALID_STATION) continue;
647 StringID time_str = (departure_types[0] && departure_types[1]) ? (d->type == D_DEPARTURE ? STR_DEPARTURES_TIME_DEP : STR_DEPARTURES_TIME_ARR) : STR_DEPARTURES_TIME;
649 if (_settings_client.gui.departure_show_both) time_str = STR_DEPARTURES_TIME_BOTH;
651 /* Time */
652 const Order* departure_order = Order::GetIfValid(d->order);
653 const Vehicle* departure_vehicle = Vehicle::GetIfValid(d->vehicle);
655 if (departure_order == nullptr || departure_vehicle == nullptr) continue;
657 SetDParam(0, d->scheduled_date);
658 SetDParam(1, d->scheduled_date - departure_order->GetWaitTime());
659 ltr ? DrawString(text_left, text_left + time_width, y + 1, time_str)
660 : DrawString(text_right - time_width, text_right, y + 1, time_str);
662 /* Vehicle type icon, with thanks to sph */
663 if (_settings_client.gui.departure_show_vehicle_type) {
664 StringID type = STR_DEPARTURES_TYPE_TRAIN;
665 int offset = (_settings_client.gui.departure_show_vehicle_color ? 1 : 0);
667 switch (departure_vehicle->type) {
668 case VEH_TRAIN:
669 type = STR_DEPARTURES_TYPE_TRAIN;
670 break;
671 case VEH_ROAD:
672 type = IsCargoInClass(departure_vehicle->cargo_type, CC_PASSENGERS) ? STR_DEPARTURES_TYPE_BUS : STR_DEPARTURES_TYPE_LORRY;
673 break;
674 case VEH_SHIP:
675 type = STR_DEPARTURES_TYPE_SHIP;
676 break;
677 case VEH_AIRCRAFT:
678 type = STR_DEPARTURES_TYPE_PLANE;
679 break;
680 default:
681 break;
684 type += offset;
686 DrawString(text_left + time_width + 3, text_left + time_width + type_width + 3, y, type);
689 /* The icons to show with the destination and via stations. */
690 StringID icon = STR_DEPARTURES_STATION_NONE;
691 StringID icon_via = STR_DEPARTURES_STATION_NONE;
693 const Station* terminus_station = Station::GetIfValid(d->terminus.station);
695 if (_settings_client.gui.departure_destination_type && terminus_station != nullptr) {
696 if (terminus_station->facilities & FACIL_DOCK &&
697 terminus_station->facilities & FACIL_AIRPORT &&
698 departure_vehicle->type != VEH_SHIP &&
699 departure_vehicle->type != VEH_AIRCRAFT) {
700 icon = STR_DEPARTURES_STATION_PORTAIRPORT;
702 else if (terminus_station->facilities & FACIL_DOCK &&
703 departure_vehicle->type != VEH_SHIP) {
704 icon = STR_DEPARTURES_STATION_PORT;
706 else if (terminus_station->facilities & FACIL_AIRPORT &&
707 departure_vehicle->type != VEH_AIRCRAFT) {
708 icon = STR_DEPARTURES_STATION_AIRPORT;
712 const Station *via_station = Station::GetIfValid(d->via);
714 if (_settings_client.gui.departure_destination_type && via_station != nullptr) {
715 if (via_station->facilities & FACIL_DOCK &&
716 via_station->facilities & FACIL_AIRPORT &&
717 departure_vehicle->type != VEH_SHIP &&
718 departure_vehicle->type != VEH_AIRCRAFT) {
719 icon_via = STR_DEPARTURES_STATION_PORTAIRPORT;
721 else if (via_station->facilities & FACIL_DOCK &&
722 departure_vehicle->type != VEH_SHIP) {
723 icon_via = STR_DEPARTURES_STATION_PORT;
725 else if (via_station->facilities & FACIL_AIRPORT &&
726 departure_vehicle->type != VEH_AIRCRAFT) {
727 icon_via = STR_DEPARTURES_STATION_AIRPORT;
731 /* Destination */
732 if (d->via == INVALID_STATION) {
733 /* Only show the terminus. */
734 SetDParam(0, d->terminus.station);
735 SetDParam(1, icon);
736 ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS)
737 : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS);
738 } else {
739 /* Show the terminus and the via station. */
740 SetDParam(0, d->terminus.station);
741 SetDParam(1, icon);
742 SetDParam(2, d->via);
743 SetDParam(3, icon_via);
744 int text_width = (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION)).width;
746 if (text_width < text_right - status_width - (toc_width + veh_width + group_width + 2) - 2 - (text_left + time_width + type_width + 6)) {
747 /* They will both fit, so show them both. */
748 SetDParam(0, d->terminus.station);
749 SetDParam(1, icon);
750 SetDParam(2, d->via);
751 SetDParam(3, icon_via);
752 ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION)
753 : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA_STATION);
754 } else {
755 /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
756 if (this->tick_count & (1 << 7)) {
757 SetDParam(0, d->via);
758 SetDParam(1, icon_via);
759 ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_VIA)
760 : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_VIA);
761 } else {
762 SetDParam(0, d->terminus.station);
763 SetDParam(1, icon);
764 ltr ? DrawString( text_left + time_width + type_width + 6, text_right - status_width - (toc_width + veh_width + group_width + 2) - 2, y + 1, STR_DEPARTURES_TERMINUS_VIA)
765 : DrawString(text_left + status_width + (toc_width + veh_width + group_width + 2) + 2, text_right - time_width - type_width - 6, y + 1, STR_DEPARTURES_TERMINUS_VIA);
770 /* Status */
772 int status_left = ltr ? text_right - status_width - 2 - (toc_width + veh_width + group_width + 2) : text_left + (toc_width + veh_width + group_width + 2) + 2;
773 int status_right = ltr ? text_right - (toc_width + veh_width + group_width + 2) + 2 : text_left + status_width + 2 + (toc_width + veh_width + group_width + 2);
775 if (d->status == D_ARRIVED) {
776 /* The vehicle has arrived. */
777 DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ARRIVED);
778 } else if(d->status == D_CANCELLED) {
779 /* The vehicle has been cancelled. */
780 DrawString(status_left, status_right, y + 1, STR_DEPARTURES_CANCELLED);
781 } else{
782 if (d->lateness <= _settings_client.gui.ticks_per_minute && d->scheduled_date > GetCurrentTickCount()) {
783 /* We have no evidence that the vehicle is late, so assume it is on time. */
784 DrawString(status_left, status_right, y + 1, STR_DEPARTURES_ON_TIME);
785 } else {
786 if ((d->scheduled_date + d->lateness) < GetCurrentTickCount()) {
787 /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */
788 /* We assume that the train stays at least a day at a station so it won't accidentally be marked as delayed for a fraction of a day. */
789 DrawString(status_left, status_right, y + 1, STR_DEPARTURES_DELAYED);
790 } else {
791 /* The vehicle is expected to be late and is not yet due to arrive. */
792 SetDParam(0, d->scheduled_date + d->lateness);
793 DrawString(status_left, status_right, y + 1, STR_DEPARTURES_EXPECTED);
799 /* Vehicle name */
801 if (_settings_client.gui.departure_show_vehicle) {
802 SetDParam(0, (uint64)(departure_vehicle->index));
803 ltr ? DrawString(text_right - (toc_width + veh_width + group_width + 2), text_right - toc_width - group_width - 2, y + 1, STR_DEPARTURES_VEH)
804 : DrawString( text_left + toc_width + group_width + 2, text_left + (toc_width + veh_width + group_width + 2), y + 1, STR_DEPARTURES_VEH);
807 /* Group name */
809 if (_settings_client.gui.departure_show_group && departure_vehicle->group_id != INVALID_GROUP && departure_vehicle->group_id != DEFAULT_GROUP) {
810 SetDParam(0, (uint64)(departure_vehicle->group_id));
811 ltr ? DrawString(text_right - (toc_width + group_width + 2), text_right - toc_width - 2, y + 1, STR_DEPARTURES_GROUP)
812 : DrawString( text_left + toc_width + 2, text_left + (toc_width + group_width + 2), y + 1, STR_DEPARTURES_GROUP);
815 /* Operating company */
816 if (_settings_client.gui.departure_show_company) {
817 SetDParam(0, (uint64)(departure_vehicle->owner));
818 ltr ? DrawString(text_right - toc_width, text_right, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_RIGHT)
819 : DrawString( text_left, text_left + toc_width, y + 1, STR_DEPARTURES_TOC, TC_FROMSTRING, SA_LEFT);
822 int bottom_y = y + this->entry_height - small_font_size - (_settings_client.gui.departure_larger_font ? 1 : 3);
824 /* Calling at */
825 ltr ? DrawString( text_left, text_left + calling_at_width, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT)
826 : DrawString(text_right - calling_at_width, text_right, bottom_y, _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LARGE : STR_DEPARTURES_CALLING_AT);
828 /* List of stations */
829 /* RTL languages can be handled in the language file, e.g. by having the following: */
830 /* STR_DEPARTURES_CALLING_AT_STATION :{STATION}, {RAW_STRING} */
831 /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
832 char buffer[512], scratch[512];
834 if (d->calling_at.Length() != 0) {
835 SetDParam(0, (uint64)(*d->calling_at.Get(0)).station);
836 GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
838 StationID continuesTo = INVALID_STATION;
840 if (d->calling_at.Get(0)->station == d->terminus.station && d->calling_at.Length() > 1) {
841 continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
842 } else if (d->calling_at.Length() > 1) {
843 /* There's more than one stop. */
845 uint i;
846 /* For all but the last station, write out ", <station>". */
847 for (i = 1; i < d->calling_at.Length() - 1; ++i) {
848 StationID s = d->calling_at.Get(i)->station;
849 if (s == d->terminus.station) {
850 continuesTo = d->calling_at.Get(d->calling_at.Length() - 1)->station;
851 break;
853 SetDParam(0, (uint64)scratch);
854 SetDParam(1, (uint64)s);
855 GetString(buffer, STR_DEPARTURES_CALLING_AT_STATION, lastof(buffer));
856 strncpy(scratch, buffer, sizeof(scratch));
859 /* Finally, finish off with " and <station>". */
860 SetDParam(0, (uint64)scratch);
861 SetDParam(1, (uint64)d->calling_at.Get(i)->station);
862 GetString(buffer, STR_DEPARTURES_CALLING_AT_LAST_STATION, lastof(buffer));
863 strncpy(scratch, buffer, sizeof(scratch));
866 SetDParam(0, (uint64)scratch);
867 StringID string;
868 if (continuesTo == INVALID_STATION) {
869 string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_LARGE : STR_DEPARTURES_CALLING_AT_LIST;
870 } else {
871 SetDParam(1, continuesTo);
872 string = _settings_client.gui.departure_larger_font ? STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE : STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS;
874 GetString(buffer, string, lastof(buffer));
875 } else {
876 buffer[0] = 0;
879 int list_width = (GetStringBoundingBox(buffer, _settings_client.gui.departure_larger_font ? FS_NORMAL : FS_SMALL)).width;
881 /* Draw the whole list if it will fit. Otherwise scroll it. */
882 if (!_settings_client.gui.departure_always_scroll &&
883 (list_width < text_right - (text_left + calling_at_width + 2))) {
884 ltr ? DrawString(text_left + calling_at_width + 2, text_right, bottom_y, buffer)
885 : DrawString( text_left, text_right - calling_at_width - 2, bottom_y, buffer);
886 } else {
887 DrawPixelInfo tmp_dpi;
888 if (ltr
889 ? !FillDrawPixelInfo(&tmp_dpi, text_left + calling_at_width + 2, bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)
890 : !FillDrawPixelInfo(&tmp_dpi, text_left , bottom_y, text_right - (text_left + calling_at_width + 2), small_font_size + 3)) {
891 y += this->entry_height;
892 continue;
894 DrawPixelInfo *old_dpi = _cur_dpi;
895 _cur_dpi = &tmp_dpi;
897 /* The scrolling text starts out of view at the right of the screen and finishes when it is out of view at the left of the screen. */
898 int pos = ltr
899 ? text_right - (this->tick_count % (list_width + text_right - text_left))
900 : text_left + (this->tick_count % (list_width + text_right - text_left));
902 ltr ? DrawString( pos, INT16_MAX, 0, buffer, TC_FROMSTRING, SA_LEFT | SA_FORCE)
903 : DrawString(-INT16_MAX, pos, 0, buffer, TC_FROMSTRING, SA_RIGHT | SA_FORCE);
905 _cur_dpi = old_dpi;
908 y += this->entry_height;