2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 /** @file timetable_gui.cpp GUI for time tabling. */
11 #include "command_func.h"
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "textbuf_gui.h"
16 #include "strings_func.h"
17 #include "vehicle_base.h"
18 #include "string_func.h"
20 #include "company_func.h"
21 #include "date_func.h"
23 #include "vehicle_gui.h"
24 #include "settings_type.h"
26 #include "widgets/timetable_widget.h"
28 #include "table/sprites.h"
29 #include "table/strings.h"
31 #include "safeguards.h"
33 /** Container for the arrival/departure dates of a vehicle */
34 struct TimetableArrivalDeparture
{
35 Ticks arrival
; ///< The arrival time
36 Ticks departure
; ///< The departure time
40 * Set the timetable parameters in the format as described by the setting.
41 * @param param1 the first DParam to fill
42 * @param param2 the second DParam to fill
43 * @param ticks the number of ticks to 'draw'
45 void SetTimetableParams(int param1
, int param2
, Ticks ticks
)
47 if (_settings_client
.gui
.timetable_in_ticks
) {
48 SetDParam(param1
, STR_TIMETABLE_TICKS
);
49 SetDParam(param2
, ticks
);
51 SetDParam(param1
, STR_TIMETABLE_DAYS
);
52 SetDParam(param2
, ticks
/ DAY_TICKS
);
57 * Check whether it is possible to determine how long the order takes.
58 * @param order the order to check.
59 * @param travelling whether we are interested in the travel or the wait part.
60 * @return true if the travel/wait time can be used.
62 static bool CanDetermineTimeTaken(const Order
*order
, bool travelling
)
64 /* Current order is conditional */
65 if (order
->IsType(OT_CONDITIONAL
) || order
->IsType(OT_IMPLICIT
)) return false;
66 /* No travel time and we have not already finished travelling */
67 if (travelling
&& !order
->IsTravelTimetabled()) return false;
68 /* No wait time but we are loading at this timetabled station */
69 if (!travelling
&& !order
->IsWaitTimetabled() && order
->IsType(OT_GOTO_STATION
) &&
70 !(order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
)) {
79 * Fill the table with arrivals and departures
80 * @param v Vehicle which must have at least 2 orders.
81 * @param start order index to start at
82 * @param travelling Are we still in the travelling part of the start order
83 * @param table Fill in arrival and departures including intermediate orders
84 * @param offset Add this value to result and all arrivals and departures
86 static void FillTimetableArrivalDepartureTable(const Vehicle
*v
, VehicleOrderID start
, bool travelling
, TimetableArrivalDeparture
*table
, Ticks offset
)
88 assert(table
!= nullptr);
89 assert(v
->GetNumOrders() >= 2);
90 assert(start
< v
->GetNumOrders());
93 VehicleOrderID i
= start
;
94 const Order
*order
= v
->GetOrder(i
);
96 /* Pre-initialize with unknown time */
97 for (int i
= 0; i
< v
->GetNumOrders(); ++i
) {
98 table
[i
].arrival
= table
[i
].departure
= INVALID_TICKS
;
101 /* Cyclically loop over all orders until we reach the current one again.
102 * As we may start at the current order, do a post-checking loop */
104 /* Automatic orders don't influence the overall timetable;
105 * they just add some untimetabled entries, but the time till
106 * the next non-implicit order can still be known. */
107 if (!order
->IsType(OT_IMPLICIT
)) {
108 if (travelling
|| i
!= start
) {
109 if (!CanDetermineTimeTaken(order
, true)) return;
110 sum
+= order
->GetTimetabledTravel();
111 table
[i
].arrival
= sum
;
114 if (!CanDetermineTimeTaken(order
, false)) return;
115 sum
+= order
->GetTimetabledWait();
116 table
[i
].departure
= sum
;
121 if (i
>= v
->GetNumOrders()) {
123 assert(order
== nullptr);
124 order
= v
->orders
.list
->GetFirstOrder();
126 } while (i
!= start
);
128 /* When loading at a scheduled station we still have to treat the
129 * travelling part of the first order. */
131 if (!CanDetermineTimeTaken(order
, true)) return;
132 sum
+= order
->GetTimetabledTravel();
133 table
[i
].arrival
= sum
;
139 * Callback for when a time has been chosen to start the time table
140 * @param w the window related to the setting of the date
141 * @param date the actually chosen date
143 static void ChangeTimetableStartCallback(const Window
*w
, Date date
)
145 DoCommandP(0, w
->window_number
, date
, CMD_SET_TIMETABLE_START
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
149 struct TimetableWindow
: Window
{
151 const Vehicle
*vehicle
; ///< Vehicle monitored by the window.
152 bool show_expected
; ///< Whether we show expected arrival or scheduled
153 uint deparr_time_width
; ///< The width of the departure/arrival time
154 uint deparr_abbr_width
; ///< The width of the departure/arrival abbreviation
156 bool query_is_speed_query
; ///< The currently open query window is a speed query and not a time query.
158 TimetableWindow(WindowDesc
*desc
, WindowNumber window_number
) :
161 vehicle(Vehicle::Get(window_number
)),
164 this->CreateNestedTree();
165 this->vscroll
= this->GetScrollbar(WID_VT_SCROLLBAR
);
166 this->UpdateSelectionStates();
167 this->FinishInitNested(window_number
);
169 this->owner
= this->vehicle
->owner
;
173 * Build the arrival-departure list for a given vehicle
174 * @param v the vehicle to make the list for
175 * @param table the table to fill
176 * @return if next arrival will be early
178 static bool BuildArrivalDepartureList(const Vehicle
*v
, TimetableArrivalDeparture
*table
)
180 assert(HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
));
182 bool travelling
= (!v
->current_order
.IsType(OT_LOADING
) || v
->current_order
.GetNonStopType() == ONSF_STOP_EVERYWHERE
);
183 Ticks start_time
= _date_fract
- v
->current_order_time
;
185 FillTimetableArrivalDepartureTable(v
, v
->cur_real_order_index
% v
->GetNumOrders(), travelling
, table
, start_time
);
187 return (travelling
&& v
->lateness_counter
< 0);
190 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
193 case WID_VT_ARRIVAL_DEPARTURE_PANEL
:
194 SetDParamMaxValue(0, MAX_YEAR
* DAYS_IN_YEAR
, 0, FS_SMALL
);
195 this->deparr_time_width
= GetStringBoundingBox(STR_JUST_DATE_TINY
).width
;
196 this->deparr_abbr_width
= max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION
).width
, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION
).width
);
197 size
->width
= WD_FRAMERECT_LEFT
+ this->deparr_abbr_width
+ 10 + this->deparr_time_width
+ WD_FRAMERECT_RIGHT
;
200 case WID_VT_ARRIVAL_DEPARTURE_SELECTION
:
201 case WID_VT_TIMETABLE_PANEL
:
202 resize
->height
= FONT_HEIGHT_NORMAL
;
203 size
->height
= WD_FRAMERECT_TOP
+ 8 * resize
->height
+ WD_FRAMERECT_BOTTOM
;
206 case WID_VT_SUMMARY_PANEL
:
207 size
->height
= WD_FRAMERECT_TOP
+ 2 * FONT_HEIGHT_NORMAL
+ WD_FRAMERECT_BOTTOM
;
212 int GetOrderFromTimetableWndPt(int y
, const Vehicle
*v
)
214 int sel
= (y
- this->GetWidget
<NWidgetBase
>(WID_VT_TIMETABLE_PANEL
)->pos_y
- WD_FRAMERECT_TOP
) / FONT_HEIGHT_NORMAL
;
216 if ((uint
)sel
>= this->vscroll
->GetCapacity()) return INVALID_ORDER
;
218 sel
+= this->vscroll
->GetPosition();
220 return (sel
< v
->GetNumOrders() * 2 && sel
>= 0) ? sel
: INVALID_ORDER
;
224 * Some data on this window has become invalid.
225 * @param data Information about the changed data.
226 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
228 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
231 case VIWD_AUTOREPLACE
:
232 /* Autoreplace replaced the vehicle */
233 this->vehicle
= Vehicle::Get(this->window_number
);
236 case VIWD_REMOVE_ALL_ORDERS
:
237 /* Removed / replaced all orders (after deleting / sharing) */
238 if (this->sel_index
== -1) break;
240 this->DeleteChildWindows();
241 this->sel_index
= -1;
244 case VIWD_MODIFY_ORDERS
:
245 if (!gui_scope
) break;
246 this->UpdateSelectionStates();
251 if (gui_scope
) break; // only do this once; from command scope
253 /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
254 * the order is being created / removed */
255 if (this->sel_index
== -1) break;
257 VehicleOrderID from
= GB(data
, 0, 8);
258 VehicleOrderID to
= GB(data
, 8, 8);
260 if (from
== to
) break; // no need to change anything
262 /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
263 uint old_num_orders
= this->vehicle
->GetNumOrders() - (uint
)(from
== INVALID_VEH_ORDER_ID
) + (uint
)(to
== INVALID_VEH_ORDER_ID
);
265 VehicleOrderID selected_order
= (this->sel_index
+ 1) / 2;
266 if (selected_order
== old_num_orders
) selected_order
= 0; // when last travel time is selected, it belongs to order 0
268 bool travel
= HasBit(this->sel_index
, 0);
270 if (from
!= selected_order
) {
271 /* Moving from preceding order? */
272 selected_order
-= (int)(from
<= selected_order
);
273 /* Moving to preceding order? */
274 selected_order
+= (int)(to
<= selected_order
);
276 /* Now we are modifying the selected order */
277 if (to
== INVALID_VEH_ORDER_ID
) {
278 /* Deleting selected order */
279 this->DeleteChildWindows();
280 this->sel_index
= -1;
283 /* Moving selected order */
288 /* recompute new sel_index */
289 this->sel_index
= 2 * selected_order
- (int)travel
;
290 /* travel time of first order needs special handling */
291 if (this->sel_index
== -1) this->sel_index
= this->vehicle
->GetNumOrders() * 2 - 1;
298 void OnPaint() override
300 const Vehicle
*v
= this->vehicle
;
301 int selected
= this->sel_index
;
303 this->vscroll
->SetCount(v
->GetNumOrders() * 2);
305 if (v
->owner
== _local_company
) {
307 if (selected
!= -1) {
308 const Order
*order
= v
->GetOrder(((selected
+ 1) / 2) % v
->GetNumOrders());
309 if (selected
% 2 == 1) {
310 disable
= order
!= nullptr && (order
->IsType(OT_CONDITIONAL
) || order
->IsType(OT_IMPLICIT
));
312 disable
= order
== nullptr || ((!order
->IsType(OT_GOTO_STATION
) || (order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
)) && !order
->IsType(OT_CONDITIONAL
));
315 bool disable_speed
= disable
|| selected
% 2 != 1 || v
->type
== VEH_AIRCRAFT
;
317 this->SetWidgetDisabledState(WID_VT_CHANGE_TIME
, disable
);
318 this->SetWidgetDisabledState(WID_VT_CLEAR_TIME
, disable
);
319 this->SetWidgetDisabledState(WID_VT_CHANGE_SPEED
, disable_speed
);
320 this->SetWidgetDisabledState(WID_VT_CLEAR_SPEED
, disable_speed
);
321 this->SetWidgetDisabledState(WID_VT_SHARED_ORDER_LIST
, !v
->IsOrderListShared());
323 this->SetWidgetDisabledState(WID_VT_START_DATE
, v
->orders
.list
== nullptr);
324 this->SetWidgetDisabledState(WID_VT_RESET_LATENESS
, v
->orders
.list
== nullptr);
325 this->SetWidgetDisabledState(WID_VT_AUTOFILL
, v
->orders
.list
== nullptr);
327 this->DisableWidget(WID_VT_START_DATE
);
328 this->DisableWidget(WID_VT_CHANGE_TIME
);
329 this->DisableWidget(WID_VT_CLEAR_TIME
);
330 this->DisableWidget(WID_VT_CHANGE_SPEED
);
331 this->DisableWidget(WID_VT_CLEAR_SPEED
);
332 this->DisableWidget(WID_VT_RESET_LATENESS
);
333 this->DisableWidget(WID_VT_AUTOFILL
);
334 this->DisableWidget(WID_VT_SHARED_ORDER_LIST
);
337 this->SetWidgetLoweredState(WID_VT_AUTOFILL
, HasBit(v
->vehicle_flags
, VF_AUTOFILL_TIMETABLE
));
342 void SetStringParameters(int widget
) const override
345 case WID_VT_CAPTION
: SetDParam(0, this->vehicle
->index
); break;
346 case WID_VT_EXPECTED
: SetDParam(0, this->show_expected
? STR_TIMETABLE_EXPECTED
: STR_TIMETABLE_SCHEDULED
); break;
350 void DrawWidget(const Rect
&r
, int widget
) const override
352 const Vehicle
*v
= this->vehicle
;
353 int selected
= this->sel_index
;
356 case WID_VT_TIMETABLE_PANEL
: {
357 int y
= r
.top
+ WD_FRAMERECT_TOP
;
358 int i
= this->vscroll
->GetPosition();
359 VehicleOrderID order_id
= (i
+ 1) / 2;
360 bool final_order
= false;
362 bool rtl
= _current_text_dir
== TD_RTL
;
363 SetDParamMaxValue(0, v
->GetNumOrders(), 2);
364 int index_column_width
= GetStringBoundingBox(STR_ORDER_INDEX
).width
+ 2 * GetSpriteSize(rtl
? SPR_ARROW_RIGHT
: SPR_ARROW_LEFT
).width
+ 3;
365 int middle
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- index_column_width
: r
.left
+ WD_FRAMERECT_LEFT
+ index_column_width
;
367 const Order
*order
= v
->GetOrder(order_id
);
368 while (order
!= nullptr) {
369 /* Don't draw anything if it extends past the end of the window. */
370 if (!this->vscroll
->IsVisible(i
)) break;
373 DrawOrderString(v
, order
, order_id
, y
, i
== selected
, true, r
.left
+ WD_FRAMERECT_LEFT
, middle
, r
.right
- WD_FRAMERECT_RIGHT
);
377 if (order_id
>= v
->GetNumOrders()) {
378 order
= v
->GetOrder(0);
385 TextColour colour
= (i
== selected
) ? TC_WHITE
: TC_BLACK
;
386 if (order
->IsType(OT_CONDITIONAL
)) {
387 string
= STR_TIMETABLE_NO_TRAVEL
;
388 } else if (order
->IsType(OT_IMPLICIT
)) {
389 string
= STR_TIMETABLE_NOT_TIMETABLEABLE
;
390 colour
= ((i
== selected
) ? TC_SILVER
: TC_GREY
) | TC_NO_SHADE
;
391 } else if (!order
->IsTravelTimetabled()) {
392 if (order
->GetTravelTime() > 0) {
393 SetTimetableParams(0, 1, order
->GetTravelTime());
394 string
= order
->GetMaxSpeed() != UINT16_MAX
?
395 STR_TIMETABLE_TRAVEL_FOR_SPEED_ESTIMATED
:
396 STR_TIMETABLE_TRAVEL_FOR_ESTIMATED
;
398 string
= order
->GetMaxSpeed() != UINT16_MAX
?
399 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED
:
400 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED
;
403 SetTimetableParams(0, 1, order
->GetTimetabledTravel());
404 string
= order
->GetMaxSpeed() != UINT16_MAX
?
405 STR_TIMETABLE_TRAVEL_FOR_SPEED
: STR_TIMETABLE_TRAVEL_FOR
;
407 SetDParam(2, order
->GetMaxSpeed());
409 DrawString(rtl
? r
.left
+ WD_FRAMERECT_LEFT
: middle
, rtl
? middle
: r
.right
- WD_FRAMERECT_LEFT
, y
, string
, colour
);
411 if (final_order
) break;
415 y
+= FONT_HEIGHT_NORMAL
;
420 case WID_VT_ARRIVAL_DEPARTURE_PANEL
: {
421 /* Arrival and departure times are handled in an all-or-nothing approach,
422 * i.e. are only shown if we can calculate all times.
423 * Excluding order lists with only one order makes some things easier.
425 Ticks total_time
= v
->orders
.list
!= nullptr ? v
->orders
.list
->GetTimetableDurationIncomplete() : 0;
426 if (total_time
<= 0 || v
->GetNumOrders() <= 1 || !HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
)) break;
428 TimetableArrivalDeparture
*arr_dep
= AllocaM(TimetableArrivalDeparture
, v
->GetNumOrders());
429 const VehicleOrderID cur_order
= v
->cur_real_order_index
% v
->GetNumOrders();
431 VehicleOrderID earlyID
= BuildArrivalDepartureList(v
, arr_dep
) ? cur_order
: (VehicleOrderID
)INVALID_VEH_ORDER_ID
;
433 int y
= r
.top
+ WD_FRAMERECT_TOP
;
435 bool show_late
= this->show_expected
&& v
->lateness_counter
> DAY_TICKS
;
436 Ticks offset
= show_late
? 0 : -v
->lateness_counter
;
438 bool rtl
= _current_text_dir
== TD_RTL
;
439 int abbr_left
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- this->deparr_abbr_width
: r
.left
+ WD_FRAMERECT_LEFT
;
440 int abbr_right
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
: r
.left
+ WD_FRAMERECT_LEFT
+ this->deparr_abbr_width
;
441 int time_left
= rtl
? r
.left
+ WD_FRAMERECT_LEFT
: r
.right
- WD_FRAMERECT_RIGHT
- this->deparr_time_width
;
442 int time_right
= rtl
? r
.left
+ WD_FRAMERECT_LEFT
+ this->deparr_time_width
: r
.right
- WD_FRAMERECT_RIGHT
;
444 for (int i
= this->vscroll
->GetPosition(); i
/ 2 < v
->GetNumOrders(); ++i
) { // note: i is also incremented in the loop
445 /* Don't draw anything if it extends past the end of the window. */
446 if (!this->vscroll
->IsVisible(i
)) break;
449 if (arr_dep
[i
/ 2].arrival
!= INVALID_TICKS
) {
450 DrawString(abbr_left
, abbr_right
, y
, STR_TIMETABLE_ARRIVAL_ABBREVIATION
, i
== selected
? TC_WHITE
: TC_BLACK
);
451 if (this->show_expected
&& i
/ 2 == earlyID
) {
452 SetDParam(0, _date
+ arr_dep
[i
/ 2].arrival
/ DAY_TICKS
);
453 DrawString(time_left
, time_right
, y
, STR_JUST_DATE_TINY
, TC_GREEN
);
455 SetDParam(0, _date
+ (arr_dep
[i
/ 2].arrival
+ offset
) / DAY_TICKS
);
456 DrawString(time_left
, time_right
, y
, STR_JUST_DATE_TINY
,
457 show_late
? TC_RED
: i
== selected
? TC_WHITE
: TC_BLACK
);
461 if (arr_dep
[i
/ 2].departure
!= INVALID_TICKS
) {
462 DrawString(abbr_left
, abbr_right
, y
, STR_TIMETABLE_DEPARTURE_ABBREVIATION
, i
== selected
? TC_WHITE
: TC_BLACK
);
463 SetDParam(0, _date
+ (arr_dep
[i
/2].departure
+ offset
) / DAY_TICKS
);
464 DrawString(time_left
, time_right
, y
, STR_JUST_DATE_TINY
,
465 show_late
? TC_RED
: i
== selected
? TC_WHITE
: TC_BLACK
);
468 y
+= FONT_HEIGHT_NORMAL
;
473 case WID_VT_SUMMARY_PANEL
: {
474 int y
= r
.top
+ WD_FRAMERECT_TOP
;
476 Ticks total_time
= v
->orders
.list
!= nullptr ? v
->orders
.list
->GetTimetableDurationIncomplete() : 0;
477 if (total_time
!= 0) {
478 SetTimetableParams(0, 1, total_time
);
479 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, v
->orders
.list
->IsCompleteTimetable() ? STR_TIMETABLE_TOTAL_TIME
: STR_TIMETABLE_TOTAL_TIME_INCOMPLETE
);
481 y
+= FONT_HEIGHT_NORMAL
;
483 if (v
->timetable_start
!= 0) {
484 /* We are running towards the first station so we can start the
485 * timetable at the given time. */
486 SetDParam(0, STR_JUST_DATE_TINY
);
487 SetDParam(1, v
->timetable_start
);
488 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_START_AT
);
489 } else if (!HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
)) {
490 /* We aren't running on a timetable yet, so how can we be "on time"
491 * when we aren't even "on service"/"on duty"? */
492 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_NOT_STARTED
);
493 } else if (v
->lateness_counter
== 0 || (!_settings_client
.gui
.timetable_in_ticks
&& v
->lateness_counter
/ DAY_TICKS
== 0)) {
494 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_ON_TIME
);
496 SetTimetableParams(0, 1, abs(v
->lateness_counter
));
497 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, v
->lateness_counter
< 0 ? STR_TIMETABLE_STATUS_EARLY
: STR_TIMETABLE_STATUS_LATE
);
504 static inline uint32
PackTimetableArgs(const Vehicle
*v
, uint selected
, bool speed
)
506 uint order_number
= (selected
+ 1) / 2;
507 ModifyTimetableFlags mtf
= (selected
% 2 == 1) ? (speed
? MTF_TRAVEL_SPEED
: MTF_TRAVEL_TIME
) : MTF_WAIT_TIME
;
509 if (order_number
>= v
->GetNumOrders()) order_number
= 0;
511 return v
->index
| (order_number
<< 20) | (mtf
<< 28);
514 void OnClick(Point pt
, int widget
, int click_count
) override
516 const Vehicle
*v
= this->vehicle
;
519 case WID_VT_ORDER_VIEW
: // Order view button
523 case WID_VT_TIMETABLE_PANEL
: { // Main panel.
524 int selected
= GetOrderFromTimetableWndPt(pt
.y
, v
);
526 this->DeleteChildWindows();
527 this->sel_index
= (selected
== INVALID_ORDER
|| selected
== this->sel_index
) ? -1 : selected
;
531 case WID_VT_START_DATE
: // Change the date that the timetable starts.
532 ShowSetDateWindow(this, v
->index
| (v
->orders
.list
->IsCompleteTimetable() && _ctrl_pressed
? 1U << 20 : 0), _date
, _cur_year
, _cur_year
+ 15, ChangeTimetableStartCallback
);
535 case WID_VT_CHANGE_TIME
: { // "Wait For" button.
536 int selected
= this->sel_index
;
537 VehicleOrderID real
= (selected
+ 1) / 2;
539 if (real
>= v
->GetNumOrders()) real
= 0;
541 const Order
*order
= v
->GetOrder(real
);
542 StringID current
= STR_EMPTY
;
544 if (order
!= nullptr) {
545 uint time
= (selected
% 2 == 1) ? order
->GetTravelTime() : order
->GetWaitTime();
546 if (!_settings_client
.gui
.timetable_in_ticks
) time
/= DAY_TICKS
;
550 current
= STR_JUST_INT
;
554 this->query_is_speed_query
= false;
555 ShowQueryString(current
, STR_TIMETABLE_CHANGE_TIME
, 31, this, CS_NUMERAL
, QSF_ACCEPT_UNCHANGED
);
559 case WID_VT_CHANGE_SPEED
: { // Change max speed button.
560 int selected
= this->sel_index
;
561 VehicleOrderID real
= (selected
+ 1) / 2;
563 if (real
>= v
->GetNumOrders()) real
= 0;
565 StringID current
= STR_EMPTY
;
566 const Order
*order
= v
->GetOrder(real
);
567 if (order
!= nullptr) {
568 if (order
->GetMaxSpeed() != UINT16_MAX
) {
569 SetDParam(0, ConvertKmhishSpeedToDisplaySpeed(order
->GetMaxSpeed()));
570 current
= STR_JUST_INT
;
574 this->query_is_speed_query
= true;
575 ShowQueryString(current
, STR_TIMETABLE_CHANGE_SPEED
, 31, this, CS_NUMERAL
, QSF_NONE
);
579 case WID_VT_CLEAR_TIME
: { // Clear waiting time.
580 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, false);
581 DoCommandP(0, p1
, 0, CMD_CHANGE_TIMETABLE
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
585 case WID_VT_CLEAR_SPEED
: { // Clear max speed button.
586 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, true);
587 DoCommandP(0, p1
, UINT16_MAX
, CMD_CHANGE_TIMETABLE
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
591 case WID_VT_RESET_LATENESS
: // Reset the vehicle's late counter.
592 DoCommandP(0, v
->index
, 0, CMD_SET_VEHICLE_ON_TIME
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
595 case WID_VT_AUTOFILL
: { // Autofill the timetable.
597 if (!HasBit(v
->vehicle_flags
, VF_AUTOFILL_TIMETABLE
)) SetBit(p2
, 0);
598 if (_ctrl_pressed
) SetBit(p2
, 1);
599 DoCommandP(0, v
->index
, p2
, CMD_AUTOFILL_TIMETABLE
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
603 case WID_VT_EXPECTED
:
604 this->show_expected
= !this->show_expected
;
607 case WID_VT_SHARED_ORDER_LIST
:
608 ShowVehicleListWindow(v
);
615 void OnQueryTextFinished(char *str
) override
617 if (str
== nullptr) return;
619 const Vehicle
*v
= this->vehicle
;
621 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, this->query_is_speed_query
);
623 uint64 val
= StrEmpty(str
) ? 0 : strtoul(str
, nullptr, 10);
624 if (this->query_is_speed_query
) {
625 val
= ConvertDisplaySpeedToKmhishSpeed(val
);
627 if (!_settings_client
.gui
.timetable_in_ticks
) val
*= DAY_TICKS
;
630 uint32 p2
= minu(val
, UINT16_MAX
);
632 DoCommandP(0, p1
, p2
, CMD_CHANGE_TIMETABLE
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
635 void OnResize() override
637 /* Update the scroll bar */
638 this->vscroll
->SetCapacityFromWidget(this, WID_VT_TIMETABLE_PANEL
, WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
);
642 * Update the selection state of the arrival/departure data
644 void UpdateSelectionStates()
646 this->GetWidget
<NWidgetStacked
>(WID_VT_ARRIVAL_DEPARTURE_SELECTION
)->SetDisplayedPlane(_settings_client
.gui
.timetable_arrival_departure
? 0 : SZSP_NONE
);
647 this->GetWidget
<NWidgetStacked
>(WID_VT_EXPECTED_SELECTION
)->SetDisplayedPlane(_settings_client
.gui
.timetable_arrival_departure
? 0 : 1);
651 static const NWidgetPart _nested_timetable_widgets
[] = {
652 NWidget(NWID_HORIZONTAL
),
653 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
654 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_VT_CAPTION
), SetDataTip(STR_TIMETABLE_TITLE
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
655 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_ORDER_VIEW
), SetMinimalSize(61, 14), SetDataTip( STR_TIMETABLE_ORDER_VIEW
, STR_TIMETABLE_ORDER_VIEW_TOOLTIP
),
656 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
657 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
658 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
660 NWidget(NWID_HORIZONTAL
),
661 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_TIMETABLE_PANEL
), SetMinimalSize(388, 82), SetResize(1, 10), SetDataTip(STR_NULL
, STR_TIMETABLE_TOOLTIP
), SetScrollbar(WID_VT_SCROLLBAR
), EndContainer(),
662 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_VT_ARRIVAL_DEPARTURE_SELECTION
),
663 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_ARRIVAL_DEPARTURE_PANEL
), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL
, STR_TIMETABLE_TOOLTIP
), SetScrollbar(WID_VT_SCROLLBAR
), EndContainer(),
665 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_VT_SCROLLBAR
),
667 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_SUMMARY_PANEL
), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
668 NWidget(NWID_HORIZONTAL
),
669 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
670 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
671 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_CHANGE_TIME
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CHANGE_TIME
, STR_TIMETABLE_WAIT_TIME_TOOLTIP
),
672 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_CLEAR_TIME
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CLEAR_TIME
, STR_TIMETABLE_CLEAR_TIME_TOOLTIP
),
674 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
675 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_CHANGE_SPEED
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CHANGE_SPEED
, STR_TIMETABLE_CHANGE_SPEED_TOOLTIP
),
676 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_CLEAR_SPEED
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CLEAR_SPEED
, STR_TIMETABLE_CLEAR_SPEED_TOOLTIP
),
678 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
679 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_START_DATE
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_STARTING_DATE
, STR_TIMETABLE_STARTING_DATE_TOOLTIP
),
680 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_RESET_LATENESS
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_RESET_LATENESS
, STR_TIMETABLE_RESET_LATENESS_TOOLTIP
),
682 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
683 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_AUTOFILL
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOFILL
, STR_TIMETABLE_AUTOFILL_TOOLTIP
),
684 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_VT_EXPECTED_SELECTION
),
685 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_EXPECTED
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING
, STR_TIMETABLE_EXPECTED_TOOLTIP
),
686 NWidget(WWT_PANEL
, COLOUR_GREY
), SetResize(1, 0), SetFill(1, 1), EndContainer(),
690 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
691 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_VT_SHARED_ORDER_LIST
), SetFill(0, 1), SetDataTip(SPR_SHARED_ORDERS_ICON
, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP
),
692 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
), SetFill(0, 1),
697 static WindowDesc
_timetable_desc(
698 WDP_AUTO
, "view_vehicle_timetable", 400, 130,
699 WC_VEHICLE_TIMETABLE
, WC_VEHICLE_VIEW
,
701 _nested_timetable_widgets
, lengthof(_nested_timetable_widgets
)
705 * Show the timetable for a given vehicle.
706 * @param v The vehicle to show the timetable for.
708 void ShowTimetableWindow(const Vehicle
*v
)
710 DeleteWindowById(WC_VEHICLE_DETAILS
, v
->index
, false);
711 DeleteWindowById(WC_VEHICLE_ORDERS
, v
->index
, false);
712 AllocateWindowDescFront
<TimetableWindow
>(&_timetable_desc
, v
->index
);