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()),
133 calc_tick_countdown(0),
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;
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
)
172 resize
->height
= DeparturesWindow::entry_height
;
173 size
->height
= 2 * resize
->height
;
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
)
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
194 for (int w
= WID_DB_SHOW_TRAINS
; w
<= WID_DB_SHOW_PLANES
; w
++) {
196 this->show_types
[w
- WID_DB_SHOW_TRAINS
] = true;
197 this->LowerWidget(w
);
199 this->show_types
[w
- WID_DB_SHOW_TRAINS
] = false;
200 this->RaiseWidget(w
);
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
);
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
);
219 case WID_DB_SHOW_DEPS
:
220 case WID_DB_SHOW_ARRS
:
221 if (_settings_client
.gui
.departure_show_both
) break;
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
);
230 this->RaiseWidget(widget
);
233 if (!this->departure_types
[0]) {
234 this->RaiseWidget(WID_DB_SHOW_VIA
);
235 this->DisableWidget(WID_DB_SHOW_VIA
);
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
);
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
262 /* Draw each departure. */
263 for (uint i
= 0; i
<= id_v
; ++i
) {
266 if (arrival
== this->arrivals
->Length()) {
267 d
= (*(this->departures
))[departure
++];
268 } else if (departure
== this->departures
->Length()) {
269 d
= (*(this->arrivals
))[arrival
++];
271 d
= (*(this->departures
))[departure
];
272 const Departure
*a
= (*(this->arrivals
))[arrival
];
274 if (a
->scheduled_date
< d
->scheduled_date
) {
283 const Vehicle
* vehicle
= Vehicle::GetIfValid(d
->vehicle
);
284 assert(vehicle
!= nullptr);
285 ShowVehicleViewWindow(vehicle
);
293 case WID_DB_SHOW_PAX
:
294 this->ToggleCargoFilter(widget
, this->show_pax
);
297 case WID_DB_SHOW_FREIGHT
:
298 this->ToggleCargoFilter(widget
, this->show_freight
);
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);
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
);
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
);
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()));
370 virtual void DrawWidget(const Rect
&r
, int widget
) const
374 this->DrawDeparturesListItems(r
);
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
;
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
);
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
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;
435 result
+= cached_status_width
;
437 /* Find the maximum company name width. */
440 /* Find the maximum company name width. */
443 /* Find the maximum vehicle name width. */
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! */
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
;
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
);
488 /* Make sure a double free doesn't happen. */
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
;
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);
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;
533 for (uint i
= 0; i
< 2; ++i
)
535 if (this->departure_types
[i
]) {
536 none_selected
[1] = false;
541 if (none_selected
[0] || none_selected
[1]) {
542 DrawString(text_left
, text_right
, y
+ 1, STR_DEPARTURES_NONE_SELECTED
);
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
);
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. */
571 /* Find the maximum group name width. */
574 /* Find the maximum vehicle name width. */
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! */
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
;
608 /* Draw each departure. */
609 for (uint i
= 0; i
< max_departures
; ++i
) {
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
++];
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
) {
635 if (i
< this->vscroll
->GetPosition()) {
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) {
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
;
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
) {
669 type
= STR_DEPARTURES_TYPE_TRAIN
;
672 type
= IsCargoInClass(departure_vehicle
->cargo_type
, CC_PASSENGERS
) ? STR_DEPARTURES_TYPE_BUS
: STR_DEPARTURES_TYPE_LORRY
;
675 type
= STR_DEPARTURES_TYPE_SHIP
;
678 type
= STR_DEPARTURES_TYPE_PLANE
;
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
;
732 if (d
->via
== INVALID_STATION
) {
733 /* Only show the terminus. */
734 SetDParam(0, d
->terminus
.station
);
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
);
739 /* Show the terminus and the via station. */
740 SetDParam(0, d
->terminus
.station
);
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
);
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
);
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
);
762 SetDParam(0, d
->terminus
.station
);
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
);
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
);
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
);
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
);
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
);
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
);
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);
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. */
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
;
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
);
868 if (continuesTo
== INVALID_STATION
) {
869 string
= _settings_client
.gui
.departure_larger_font
? STR_DEPARTURES_CALLING_AT_LIST_LARGE
: STR_DEPARTURES_CALLING_AT_LIST
;
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
));
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
);
887 DrawPixelInfo tmp_dpi
;
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
;
894 DrawPixelInfo
*old_dpi
= _cur_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. */
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
);
908 y
+= this->entry_height
;