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/>.
10 /** @file departures_gui.cpp Scheduled departures from a station. */
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
),
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
),
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
),
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
{
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.
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
)
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
);
128 DeparturesWindow(WindowDesc
*desc
, WindowNumber window_number
) : Window(desc
),
129 station(window_number
),
130 departures(new DepartureList()),
131 arrivals(new DepartureList()),
132 entry_height(1 + FONT_HEIGHT_NORMAL
+ 1 + (_settings_client
.gui
.departure_larger_font
? FONT_HEIGHT_NORMAL
: FONT_HEIGHT_SMALL
) + 1 + 1),
134 calc_tick_countdown(0),
137 this->CreateNestedTree();
138 this->vscroll
= this->GetScrollbar(WID_DB_SCROLLBAR
);
139 this->FinishInitNested(window_number
);
141 /* By default, only show departures. */
142 departure_types
[0] = true;
143 departure_types
[1] = false;
144 departure_types
[2] = false;
147 this->LowerWidget(WID_DB_SHOW_DEPS
);
148 this->RaiseWidget(WID_DB_SHOW_ARRS
);
149 this->RaiseWidget(WID_DB_SHOW_VIA
);
150 this->LowerWidget(WID_DB_SHOW_PAX
);
151 this->LowerWidget(WID_DB_SHOW_FREIGHT
);
152 this->SetCargoFilterDisabledState();
154 for (uint i
= 0; i
< 4; ++i
) {
155 show_types
[i
] = true;
156 this->LowerWidget(WID_DB_SHOW_TRAINS
+ i
);
160 virtual ~DeparturesWindow()
162 this->DeleteDeparturesList(departures
);
163 this->DeleteDeparturesList(this->arrivals
);
166 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
170 resize
->height
= DeparturesWindow::entry_height
;
171 size
->height
= 2 * resize
->height
;
176 virtual void SetStringParameters(int widget
) const
178 if (widget
== WID_DB_CAPTION
) {
179 const Station
*st
= Station::Get(this->station
);
180 SetDParam(0, st
->index
);
184 virtual void OnClick(Point pt
, int widget
, int click_count
)
187 case WID_DB_SHOW_TRAINS
: // Show trains to this station
188 case WID_DB_SHOW_ROADVEHS
: // Show road vehicles to this station
189 case WID_DB_SHOW_SHIPS
: // Show ships to this station
190 case WID_DB_SHOW_PLANES
: { // Show aircraft to this station
192 for (int w
= WID_DB_SHOW_TRAINS
; w
<= WID_DB_SHOW_PLANES
; w
++) {
194 this->show_types
[w
- WID_DB_SHOW_TRAINS
] = true;
195 this->LowerWidget(w
);
197 this->show_types
[w
- WID_DB_SHOW_TRAINS
] = false;
198 this->RaiseWidget(w
);
202 this->show_types
[widget
- WID_DB_SHOW_TRAINS
] = !this->show_types
[widget
- WID_DB_SHOW_TRAINS
];
203 if (this->show_types
[widget
- WID_DB_SHOW_TRAINS
]) {
204 this->LowerWidget(widget
);
207 this->RaiseWidget(widget
);
210 /* We need to recompute the departures list. */
211 this->calc_tick_countdown
= 0;
212 /* We need to redraw the button that was pressed. */
213 this->SetWidgetDirty(widget
);
217 case WID_DB_SHOW_DEPS
:
218 case WID_DB_SHOW_ARRS
:
219 if (_settings_client
.gui
.departure_show_both
) break;
222 case WID_DB_SHOW_VIA
:
224 this->departure_types
[widget
- WID_DB_SHOW_DEPS
] = !this->departure_types
[widget
- WID_DB_SHOW_DEPS
];
225 if (this->departure_types
[widget
- WID_DB_SHOW_DEPS
]) {
226 this->LowerWidget(widget
);
228 this->RaiseWidget(widget
);
231 if (!this->departure_types
[0]) {
232 this->RaiseWidget(WID_DB_SHOW_VIA
);
233 this->DisableWidget(WID_DB_SHOW_VIA
);
235 this->EnableWidget(WID_DB_SHOW_VIA
);
237 if (this->departure_types
[2]) {
238 this->LowerWidget(WID_DB_SHOW_VIA
);
241 /* We need to recompute the departures list. */
242 this->calc_tick_countdown
= 0;
243 /* We need to redraw the button that was pressed. */
244 this->SetWidgetDirty(widget
);
247 case WID_DB_LIST
: { // Matrix to show departures
248 /* We need to find the departure corresponding to where the user clicked. */
249 uint32 id_v
= (pt
.y
- this->GetWidget
<NWidgetBase
>(WID_DB_LIST
)->pos_y
) / this->entry_height
;
251 if (id_v
>= this->vscroll
->GetCapacity()) return; // click out of bounds
253 id_v
+= this->vscroll
->GetPosition();
255 if (id_v
>= (this->departures
->Length() + this->arrivals
->Length())) return; // click out of list bound
260 /* Draw each departure. */
261 for (uint i
= 0; i
<= id_v
; ++i
) {
264 if (arrival
== this->arrivals
->Length()) {
265 d
= (*(this->departures
))[departure
++];
266 } else if (departure
== this->departures
->Length()) {
267 d
= (*(this->arrivals
))[arrival
++];
269 d
= (*(this->departures
))[departure
];
270 const Departure
*a
= (*(this->arrivals
))[arrival
];
272 if (a
->scheduled_date
< d
->scheduled_date
) {
281 const Vehicle
* vehicle
= Vehicle::GetIfValid(d
->vehicle
);
282 assert(vehicle
!= nullptr);
283 ShowVehicleViewWindow(vehicle
);
291 case WID_DB_SHOW_PAX
:
292 this->ToggleCargoFilter(widget
, this->show_pax
);
295 case WID_DB_SHOW_FREIGHT
:
296 this->ToggleCargoFilter(widget
, this->show_freight
);
302 virtual void OnTick()
304 if (_pause_mode
== PM_UNPAUSED
) {
305 this->tick_count
+= 1;
306 this->calc_tick_countdown
-= 1;
309 /* Recompute the minimum date display width if the cached one is no longer valid. */
310 if (cached_date_width
== 0 || _settings_client
.gui
.departure_show_both
!= cached_arr_dep_display_method
) {
311 this->RecomputeDateWidth();
314 if (this->cargo_buttons_disabled
!= _settings_client
.gui
.departure_only_passengers
) {
315 this->SetCargoFilterDisabledState();
316 this->calc_tick_countdown
= 0;
317 this->SetWidgetDirty(WID_DB_SHOW_PAX
);
318 this->SetWidgetDirty(WID_DB_SHOW_FREIGHT
);
321 /* We need to redraw the scrolling text in its new position. */
322 this->SetWidgetDirty(WID_DB_LIST
);
324 /* Recompute the list of departures if we're due to. */
325 if (this->calc_tick_countdown
<= 0) {
326 this->calc_tick_countdown
= _settings_client
.gui
.departure_calc_frequency
;
327 this->DeleteDeparturesList(this->departures
);
328 this->DeleteDeparturesList(this->arrivals
);
329 bool show_pax
= _settings_client
.gui
.departure_only_passengers
? true : this->show_pax
;
330 bool show_freight
= _settings_client
.gui
.departure_only_passengers
? false : this->show_freight
;
331 this->departures
= (this->departure_types
[0] ? MakeDepartureList(this->station
, this->show_types
, D_DEPARTURE
, this->departure_types
[2], show_pax
, show_freight
) : new DepartureList());
332 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());
333 this->SetWidgetDirty(WID_DB_LIST
);
336 uint new_width
= this->GetMinWidth();
338 if (new_width
!= this->min_width
) {
339 NWidgetCore
*n
= this->GetWidget
<NWidgetCore
>(WID_DB_LIST
);
340 n
->SetMinimalSize(new_width
, 0);
342 this->min_width
= new_width
;
345 uint new_height
= 1 + FONT_HEIGHT_NORMAL
+ 1 + (_settings_client
.gui
.departure_larger_font
? FONT_HEIGHT_NORMAL
: FONT_HEIGHT_SMALL
) + 1 + 1;
347 if (new_height
!= this->entry_height
) {
348 this->entry_height
= new_height
;
349 this->SetWidgetDirty(WID_DB_LIST
);
354 virtual void OnPaint()
356 if (_settings_client
.gui
.departure_show_both
) {
357 this->DisableWidget(WID_DB_SHOW_ARRS
);
358 this->DisableWidget(WID_DB_SHOW_DEPS
);
360 this->EnableWidget(WID_DB_SHOW_ARRS
);
361 this->EnableWidget(WID_DB_SHOW_DEPS
);
364 this->vscroll
->SetCount(min(_settings_client
.gui
.max_departures
, this->departures
->Length() + this->arrivals
->Length()));
368 virtual void DrawWidget(const Rect
&r
, int widget
) const
372 this->DrawDeparturesListItems(r
);
377 virtual void OnResize()
379 this->vscroll
->SetCapacityFromWidget(this, WID_DB_LIST
);
380 this->GetWidget
<NWidgetCore
>(WID_DB_LIST
)->widget_data
= (this->vscroll
->GetCapacity() << MAT_ROW_START
) + (1 << MAT_COL_START
);
385 * Shows a window of scheduled departures for a station.
386 * @param station the station to show a departures window for
388 void ShowStationDepartures(StationID station
)
390 AllocateWindowDescFront
<DeparturesWindow
>(&_departures_desc
, station
);
393 void DeparturesWindow::RecomputeDateWidth()
395 cached_date_width
= 0;
396 cached_status_width
= 0;
397 cached_arr_dep_display_method
= _settings_client
.gui
.departure_show_both
;
399 cached_status_width
= max((GetStringBoundingBox(STR_DEPARTURES_ON_TIME
)).width
, cached_status_width
);
400 cached_status_width
= max((GetStringBoundingBox(STR_DEPARTURES_DELAYED
)).width
, cached_status_width
);
401 cached_status_width
= max((GetStringBoundingBox(STR_DEPARTURES_CANCELLED
)).width
, cached_status_width
);
403 uint interval
= _settings_client
.gui
.ticks_per_minute
;
407 for (uint i
= 0; i
< count
; ++i
) {
408 SetDParam(0, INT_MAX
- (i
*interval
));
409 SetDParam(1, INT_MAX
- (i
*interval
));
410 cached_date_width
= max(GetStringBoundingBox(cached_arr_dep_display_method
? STR_DEPARTURES_TIME_BOTH
: STR_DEPARTURES_TIME_DEP
).width
, cached_date_width
);
411 cached_status_width
= max((GetStringBoundingBox(STR_DEPARTURES_EXPECTED
)).width
, cached_status_width
);
415 cached_date_arrow_width
= GetStringBoundingBox(STR_DEPARTURES_TIME_DEP
).width
- GetStringBoundingBox(STR_DEPARTURES_TIME
).width
;
417 if (!_settings_client
.gui
.departure_show_both
) {
418 cached_date_width
-= cached_date_arrow_width
;
422 uint
DeparturesWindow::GetMinWidth() const
427 result
= cached_date_width
;
429 /* Vehicle type icon */
430 result
+= _settings_client
.gui
.departure_show_vehicle_type
? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE
)).width
: 0;
433 result
+= cached_status_width
;
435 /* Find the maximum company name width. */
438 /* Find the maximum company name width. */
441 /* Find the maximum vehicle name width. */
444 if (_settings_client
.gui
.departure_show_vehicle
|| _settings_client
.gui
.departure_show_company
|| _settings_client
.gui
.departure_show_group
) {
445 for (uint i
= 0; i
< 4; ++i
) {
446 VehicleList vehicles
;
448 /* 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). */
449 if (!GenerateVehicleSortList(&vehicles
, VehicleListIdentifier(VL_STATION_LIST
, (VehicleType
)(VEH_TRAIN
+ i
), MAX_COMPANIES
, station
))) {
450 /* Something went wrong: panic! */
454 for (const Vehicle
**v
= vehicles
.Begin(); v
!= vehicles
.End(); v
++) {
455 SetDParam(0, (uint64
)((*v
)->index
));
456 int width
= (GetStringBoundingBox(STR_DEPARTURES_VEH
)).width
;
457 if (_settings_client
.gui
.departure_show_vehicle
&& width
> veh_width
) veh_width
= width
;
459 if ((*v
)->group_id
!= INVALID_GROUP
&& (*v
)->group_id
!= DEFAULT_GROUP
) {
460 SetDParam(0, (uint64
)((*v
)->group_id
));
461 width
= (GetStringBoundingBox(STR_DEPARTURES_GROUP
)).width
;
462 if (_settings_client
.gui
.departure_show_group
&& width
> group_width
) group_width
= width
;
465 SetDParam(0, (uint64
)((*v
)->owner
));
466 width
= (GetStringBoundingBox(STR_DEPARTURES_TOC
)).width
;
467 if (_settings_client
.gui
.departure_show_company
&& width
> toc_width
) toc_width
= width
;
472 result
+= toc_width
+ veh_width
+ group_width
;
478 * Deletes this window's departure list.
480 void DeparturesWindow::DeleteDeparturesList(DepartureList
*list
)
482 /* SmallVector uses free rather than delete on its contents (which doesn't invoke the destructor), so we need to delete each departure manually. */
483 for (uint i
= 0; i
< list
->Length(); ++i
) {
484 Departure
**d
= list
->Get(i
);
486 /* Make sure a double free doesn't happen. */
495 * Draws a list of departures.
497 void DeparturesWindow::DrawDeparturesListItems(const Rect
&r
) const
499 int left
= r
.left
+ WD_MATRIX_LEFT
;
500 int right
= r
.right
- WD_MATRIX_RIGHT
;
502 bool rtl
= _current_text_dir
== TD_RTL
;
505 int text_offset
= WD_FRAMERECT_RIGHT
;
506 int text_left
= left
+ (rtl
? 0 : text_offset
);
507 int text_right
= right
- (rtl
? text_offset
: 0);
510 uint max_departures
= min(this->vscroll
->GetPosition() + this->vscroll
->GetCapacity(), this->departures
->Length() + this->arrivals
->Length());
512 if (max_departures
> _settings_client
.gui
.max_departures
) {
513 max_departures
= _settings_client
.gui
.max_departures
;
516 byte small_font_size
= _settings_client
.gui
.departure_larger_font
? FONT_HEIGHT_NORMAL
: FONT_HEIGHT_SMALL
;
518 /* Draw the black background. */
519 GfxFillRect(r
.left
+ 1, r
.top
, r
.right
- 1, r
.bottom
, PC_BLACK
);
521 /* Nothing selected? Then display the information text. */
522 bool none_selected
[2] = {true, true};
523 for (uint i
= 0; i
< 4; ++i
)
525 if (this->show_types
[i
]) {
526 none_selected
[0] = false;
531 for (uint i
= 0; i
< 2; ++i
)
533 if (this->departure_types
[i
]) {
534 none_selected
[1] = false;
539 if (none_selected
[0] || none_selected
[1]) {
540 DrawString(text_left
, text_right
, y
+ 1, STR_DEPARTURES_NONE_SELECTED
);
544 /* No scheduled departures? Then display the information text. */
545 if (max_departures
== 0) {
546 DrawString(text_left
, text_right
, y
+ 1, STR_DEPARTURES_EMPTY
);
550 /* Find the maximum possible width of the departure time and "Expt <time>" fields. */
551 int time_width
= cached_date_width
;
553 if (!_settings_client
.gui
.departure_show_both
) {
554 time_width
+= (departure_types
[0] && departure_types
[1] ? cached_date_arrow_width
: 0);
557 /* Vehicle type icon */
558 int type_width
= _settings_client
.gui
.departure_show_vehicle_type
? (GetStringBoundingBox(STR_DEPARTURES_TYPE_PLANE
)).width
: 0;
560 /* Find the maximum width of the status field */
561 int status_width
= cached_status_width
;
563 /* Find the width of the "Calling at:" field. */
564 int calling_at_width
= (GetStringBoundingBox(_settings_client
.gui
.departure_larger_font
? STR_DEPARTURES_CALLING_AT_LARGE
: STR_DEPARTURES_CALLING_AT
)).width
;
566 /* Find the maximum company name width. */
569 /* Find the maximum group name width. */
572 /* Find the maximum vehicle name width. */
575 if (_settings_client
.gui
.departure_show_vehicle
|| _settings_client
.gui
.departure_show_company
|| _settings_client
.gui
.departure_show_group
) {
576 for (uint i
= 0; i
< 4; ++i
) {
577 VehicleList vehicles
;
579 /* 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). */
580 if (!GenerateVehicleSortList(&vehicles
, VehicleListIdentifier(VL_STATION_LIST
, (VehicleType
)(VEH_TRAIN
+ i
), MAX_COMPANIES
, station
))) {
581 /* Something went wrong: panic! */
585 for (const Vehicle
**v
= vehicles
.Begin(); v
!= vehicles
.End(); v
++) {
586 SetDParam(0, (uint64
)((*v
)->index
));
587 int width
= (GetStringBoundingBox(STR_DEPARTURES_VEH
)).width
;
588 if (_settings_client
.gui
.departure_show_vehicle
&& width
> veh_width
) veh_width
= width
;
590 if ((*v
)->group_id
!= INVALID_GROUP
&& (*v
)->group_id
!= DEFAULT_GROUP
) {
591 SetDParam(0, (uint64
)((*v
)->group_id
));
592 width
= (GetStringBoundingBox(STR_DEPARTURES_GROUP
)).width
;
593 if (_settings_client
.gui
.departure_show_group
&& width
> group_width
) group_width
= width
;
596 SetDParam(0, (uint64
)((*v
)->owner
));
597 width
= (GetStringBoundingBox(STR_DEPARTURES_TOC
)).width
;
598 if (_settings_client
.gui
.departure_show_company
&& width
> toc_width
) toc_width
= width
;
606 /* Draw each departure. */
607 for (uint i
= 0; i
< max_departures
; ++i
) {
610 if (arrival
== this->arrivals
->Length()) {
611 assert(departure
< this->departures
->Length());
612 d
= (*(this->departures
))[departure
++];
614 else if (departure
== this->departures
->Length()) {
615 assert(arrival
< this->arrivals
->Length());
616 d
= (*(this->arrivals
))[arrival
++];
619 assert(departure
< this->departures
->Length());
620 assert(arrival
< this->arrivals
->Length());
621 d
= (*(this->departures
))[departure
];
622 const Departure
*a
= (*(this->arrivals
))[arrival
];
624 if (a
->scheduled_date
< d
->scheduled_date
) {
633 if (i
< this->vscroll
->GetPosition()) {
637 /* If for some reason the departure is too far in the future or is at a negative time, skip it. */
638 if ((d
->scheduled_date
/ DEFAULT_DAY_TICKS
) > MAX_DAY
||
639 d
->scheduled_date
< 0) {
643 if (d
->terminus
.station
== INVALID_STATION
) continue;
645 StringID time_str
= (departure_types
[0] && departure_types
[1]) ? (d
->type
== D_DEPARTURE
? STR_DEPARTURES_TIME_DEP
: STR_DEPARTURES_TIME_ARR
) : STR_DEPARTURES_TIME
;
647 if (_settings_client
.gui
.departure_show_both
) time_str
= STR_DEPARTURES_TIME_BOTH
;
650 const Order
* departure_order
= Order::GetIfValid(d
->order
);
651 const Vehicle
* departure_vehicle
= Vehicle::GetIfValid(d
->vehicle
);
653 if (departure_order
== nullptr || departure_vehicle
== nullptr) continue;
655 SetDParam(0, d
->scheduled_date
);
656 SetDParam(1, d
->scheduled_date
- departure_order
->GetWaitTime());
657 ltr
? DrawString(text_left
, text_left
+ time_width
, y
+ 1, time_str
)
658 : DrawString(text_right
- time_width
, text_right
, y
+ 1, time_str
);
660 /* Vehicle type icon, with thanks to sph */
661 if (_settings_client
.gui
.departure_show_vehicle_type
) {
662 StringID type
= STR_DEPARTURES_TYPE_TRAIN
;
663 int offset
= (_settings_client
.gui
.departure_show_vehicle_color
? 1 : 0);
665 switch (departure_vehicle
->type
) {
667 type
= STR_DEPARTURES_TYPE_TRAIN
;
670 type
= IsCargoInClass(departure_vehicle
->cargo_type
, CC_PASSENGERS
) ? STR_DEPARTURES_TYPE_BUS
: STR_DEPARTURES_TYPE_LORRY
;
673 type
= STR_DEPARTURES_TYPE_SHIP
;
676 type
= STR_DEPARTURES_TYPE_PLANE
;
684 DrawString(text_left
+ time_width
+ 3, text_left
+ time_width
+ type_width
+ 3, y
, type
);
687 /* The icons to show with the destination and via stations. */
688 StringID icon
= STR_DEPARTURES_STATION_NONE
;
689 StringID icon_via
= STR_DEPARTURES_STATION_NONE
;
691 const Station
* terminus_station
= Station::GetIfValid(d
->terminus
.station
);
693 if (_settings_client
.gui
.departure_destination_type
&& terminus_station
!= nullptr) {
694 if (terminus_station
->facilities
& FACIL_DOCK
&&
695 terminus_station
->facilities
& FACIL_AIRPORT
&&
696 departure_vehicle
->type
!= VEH_SHIP
&&
697 departure_vehicle
->type
!= VEH_AIRCRAFT
) {
698 icon
= STR_DEPARTURES_STATION_PORTAIRPORT
;
700 else if (terminus_station
->facilities
& FACIL_DOCK
&&
701 departure_vehicle
->type
!= VEH_SHIP
) {
702 icon
= STR_DEPARTURES_STATION_PORT
;
704 else if (terminus_station
->facilities
& FACIL_AIRPORT
&&
705 departure_vehicle
->type
!= VEH_AIRCRAFT
) {
706 icon
= STR_DEPARTURES_STATION_AIRPORT
;
710 const Station
*via_station
= Station::GetIfValid(d
->via
);
712 if (_settings_client
.gui
.departure_destination_type
&& via_station
!= nullptr) {
713 if (via_station
->facilities
& FACIL_DOCK
&&
714 via_station
->facilities
& FACIL_AIRPORT
&&
715 departure_vehicle
->type
!= VEH_SHIP
&&
716 departure_vehicle
->type
!= VEH_AIRCRAFT
) {
717 icon_via
= STR_DEPARTURES_STATION_PORTAIRPORT
;
719 else if (via_station
->facilities
& FACIL_DOCK
&&
720 departure_vehicle
->type
!= VEH_SHIP
) {
721 icon_via
= STR_DEPARTURES_STATION_PORT
;
723 else if (via_station
->facilities
& FACIL_AIRPORT
&&
724 departure_vehicle
->type
!= VEH_AIRCRAFT
) {
725 icon_via
= STR_DEPARTURES_STATION_AIRPORT
;
730 if (d
->via
== INVALID_STATION
) {
731 /* Only show the terminus. */
732 SetDParam(0, d
->terminus
.station
);
734 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
)
735 : 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
);
737 /* Show the terminus and the via station. */
738 SetDParam(0, d
->terminus
.station
);
740 SetDParam(2, d
->via
);
741 SetDParam(3, icon_via
);
742 int text_width
= (GetStringBoundingBox(STR_DEPARTURES_TERMINUS_VIA_STATION
)).width
;
744 if (text_width
< text_right
- status_width
- (toc_width
+ veh_width
+ group_width
+ 2) - 2 - (text_left
+ time_width
+ type_width
+ 6)) {
745 /* They will both fit, so show them both. */
746 SetDParam(0, d
->terminus
.station
);
748 SetDParam(2, d
->via
);
749 SetDParam(3, icon_via
);
750 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
)
751 : 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
);
753 /* They won't both fit, so switch between showing the terminus and the via station approximately every 4 seconds. */
754 if (this->tick_count
& (1 << 7)) {
755 SetDParam(0, d
->via
);
756 SetDParam(1, icon_via
);
757 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
)
758 : 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
);
760 SetDParam(0, d
->terminus
.station
);
762 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
)
763 : 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 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;
771 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);
773 if (d
->status
== D_ARRIVED
) {
774 /* The vehicle has arrived. */
775 DrawString(status_left
, status_right
, y
+ 1, STR_DEPARTURES_ARRIVED
);
776 } else if(d
->status
== D_CANCELLED
) {
777 /* The vehicle has been cancelled. */
778 DrawString(status_left
, status_right
, y
+ 1, STR_DEPARTURES_CANCELLED
);
780 if (d
->lateness
<= _settings_client
.gui
.ticks_per_minute
&& d
->scheduled_date
> GetCurrentTickCount()) {
781 /* We have no evidence that the vehicle is late, so assume it is on time. */
782 DrawString(status_left
, status_right
, y
+ 1, STR_DEPARTURES_ON_TIME
);
784 if ((d
->scheduled_date
+ d
->lateness
) < GetCurrentTickCount()) {
785 /* The vehicle was expected to have arrived by now, even if we knew it was going to be late. */
786 /* 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. */
787 DrawString(status_left
, status_right
, y
+ 1, STR_DEPARTURES_DELAYED
);
789 /* The vehicle is expected to be late and is not yet due to arrive. */
790 SetDParam(0, d
->scheduled_date
+ d
->lateness
);
791 DrawString(status_left
, status_right
, y
+ 1, STR_DEPARTURES_EXPECTED
);
799 if (_settings_client
.gui
.departure_show_vehicle
) {
800 SetDParam(0, (uint64
)(departure_vehicle
->index
));
801 ltr
? DrawString(text_right
- (toc_width
+ veh_width
+ group_width
+ 2), text_right
- toc_width
- group_width
- 2, y
+ 1, STR_DEPARTURES_VEH
)
802 : DrawString( text_left
+ toc_width
+ group_width
+ 2, text_left
+ (toc_width
+ veh_width
+ group_width
+ 2), y
+ 1, STR_DEPARTURES_VEH
);
807 if (_settings_client
.gui
.departure_show_group
&& departure_vehicle
->group_id
!= INVALID_GROUP
&& departure_vehicle
->group_id
!= DEFAULT_GROUP
) {
808 SetDParam(0, (uint64
)(departure_vehicle
->group_id
));
809 ltr
? DrawString(text_right
- (toc_width
+ group_width
+ 2), text_right
- toc_width
- 2, y
+ 1, STR_DEPARTURES_GROUP
)
810 : DrawString( text_left
+ toc_width
+ 2, text_left
+ (toc_width
+ group_width
+ 2), y
+ 1, STR_DEPARTURES_GROUP
);
813 /* Operating company */
814 if (_settings_client
.gui
.departure_show_company
) {
815 SetDParam(0, (uint64
)(departure_vehicle
->owner
));
816 ltr
? DrawString(text_right
- toc_width
, text_right
, y
+ 1, STR_DEPARTURES_TOC
, TC_FROMSTRING
, SA_RIGHT
)
817 : DrawString( text_left
, text_left
+ toc_width
, y
+ 1, STR_DEPARTURES_TOC
, TC_FROMSTRING
, SA_LEFT
);
820 int bottom_y
= y
+ this->entry_height
- small_font_size
- (_settings_client
.gui
.departure_larger_font
? 1 : 3);
823 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
)
824 : 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
);
826 /* List of stations */
827 /* RTL languages can be handled in the language file, e.g. by having the following: */
828 /* STR_DEPARTURES_CALLING_AT_STATION :{STATION}, {RAW_STRING} */
829 /* STR_DEPARTURES_CALLING_AT_LAST_STATION :{STATION} & {RAW_STRING}*/
830 char buffer
[512], scratch
[512];
832 if (d
->calling_at
.Length() != 0) {
833 SetDParam(0, (uint64
)(*d
->calling_at
.Get(0)).station
);
834 GetString(scratch
, STR_DEPARTURES_CALLING_AT_FIRST_STATION
, lastof(scratch
));
836 StationID continuesTo
= INVALID_STATION
;
838 if (d
->calling_at
.Get(0)->station
== d
->terminus
.station
&& d
->calling_at
.Length() > 1) {
839 continuesTo
= d
->calling_at
.Get(d
->calling_at
.Length() - 1)->station
;
840 } else if (d
->calling_at
.Length() > 1) {
841 /* There's more than one stop. */
844 /* For all but the last station, write out ", <station>". */
845 for (i
= 1; i
< d
->calling_at
.Length() - 1; ++i
) {
846 StationID s
= d
->calling_at
.Get(i
)->station
;
847 if (s
== d
->terminus
.station
) {
848 continuesTo
= d
->calling_at
.Get(d
->calling_at
.Length() - 1)->station
;
851 SetDParam(0, (uint64
)scratch
);
852 SetDParam(1, (uint64
)s
);
853 GetString(buffer
, STR_DEPARTURES_CALLING_AT_STATION
, lastof(buffer
));
854 strncpy(scratch
, buffer
, sizeof(scratch
));
857 /* Finally, finish off with " and <station>". */
858 SetDParam(0, (uint64
)scratch
);
859 SetDParam(1, (uint64
)d
->calling_at
.Get(i
)->station
);
860 GetString(buffer
, STR_DEPARTURES_CALLING_AT_LAST_STATION
, lastof(buffer
));
861 strncpy(scratch
, buffer
, sizeof(scratch
));
864 SetDParam(0, (uint64
)scratch
);
866 if (continuesTo
== INVALID_STATION
) {
867 string
= _settings_client
.gui
.departure_larger_font
? STR_DEPARTURES_CALLING_AT_LIST_LARGE
: STR_DEPARTURES_CALLING_AT_LIST
;
869 SetDParam(1, continuesTo
);
870 string
= _settings_client
.gui
.departure_larger_font
? STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS_LARGE
: STR_DEPARTURES_CALLING_AT_LIST_SMART_TERMINUS
;
872 GetString(buffer
, string
, lastof(buffer
));
875 //SetDParam(0, d->terminus);
876 //GetString(scratch, STR_DEPARTURES_CALLING_AT_FIRST_STATION, lastof(scratch));
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 (list_width
< text_right
- (text_left
+ calling_at_width
+ 2)) {
883 ltr
? DrawString(text_left
+ calling_at_width
+ 2, text_right
, bottom_y
, buffer
)
884 : DrawString( text_left
, text_right
- calling_at_width
- 2, bottom_y
, buffer
);
886 DrawPixelInfo tmp_dpi
;
888 ? !FillDrawPixelInfo(&tmp_dpi
, text_left
+ calling_at_width
+ 2, bottom_y
, text_right
- (text_left
+ calling_at_width
+ 2), small_font_size
+ 3)
889 : !FillDrawPixelInfo(&tmp_dpi
, text_left
, bottom_y
, text_right
- (text_left
+ calling_at_width
+ 2), small_font_size
+ 3)) {
890 y
+= this->entry_height
;
893 DrawPixelInfo
*old_dpi
= _cur_dpi
;
896 /* 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 ? text_right
- (this->tick_count
% (list_width
+ text_right
- text_left
))
899 : text_left
+ (this->tick_count
% (list_width
+ text_right
- text_left
));
901 ltr
? DrawString( pos
, INT16_MAX
, 0, buffer
, TC_FROMSTRING
, SA_LEFT
| SA_FORCE
)
902 : DrawString(-INT16_MAX
, pos
, 0, buffer
, TC_FROMSTRING
, SA_RIGHT
| SA_FORCE
);
907 y
+= this->entry_height
;