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 timetable_gui.cpp GUI for time tabling. */
13 #include "command_func.h"
15 #include "window_gui.h"
16 #include "window_func.h"
17 #include "textbuf_gui.h"
18 #include "strings_func.h"
19 #include "vehicle_base.h"
20 #include "string_func.h"
22 #include "company_func.h"
23 #include "date_func.h"
25 #include "vehicle_gui.h"
26 #include "settings_type.h"
27 #include "viewport_func.h"
28 #include "timetable.h"
30 #include "widgets/timetable_widget.h"
32 #include "table/sprites.h"
33 #include "table/strings.h"
34 #include "widgets/dropdown_func.h"
37 /** Entries for mode selection dropdown list. Order must be identical to the one in #TTSepMode */
38 static const StringID TimetableSeparationDropdownOptions
[6] = {
39 STR_TTSEPARATION_AUTO
,
41 STR_TTSEPARATION_MAN_TIME
,
42 STR_TTSEPARATION_MAN_NUM
,
43 STR_TTSEPARATION_BUFFERED_AUTO
,
47 #include "safeguards.h"
49 /** Container for the arrival/departure dates of a vehicle */
50 struct TimetableArrivalDeparture
{
51 Ticks arrival
; ///< The arrival time
52 Ticks departure
; ///< The departure time
56 * Set the timetable parameters in the ticks (minutes) format.
57 * @param param1 the first of three successive DParam to fill
58 * @param ticks the number of ticks to 'draw'
60 void SetTimetableParams(Ticks ticks
, int param1
)
62 SetDParam(param1
, STR_TIMETABLE_TICKS_MINUTES
);
63 SetDParam(param1
+ 1, ticks
);
64 SetDParam(param1
+ 2, ticks
);
68 * Check whether it is possible to determine how long the order takes.
69 * @param order the order to check.
70 * @param travelling whether we are interested in the travel or the wait part.
71 * @return true if the travel/wait time can be used.
73 static bool CanDetermineTimeTaken(const Order
*order
, bool travelling
)
75 /* Current order is conditional */
76 if (order
->IsType(OT_CONDITIONAL
) || order
->IsType(OT_IMPLICIT
)) return false;
77 /* No travel time and we have not already finished travelling */
78 if (travelling
&& !order
->IsTravelTimetabled()) return false;
79 /* No wait time but we are loading at this timetabled station */
80 if (!travelling
&& !order
->IsWaitTimetabled() && order
->IsType(OT_GOTO_STATION
) &&
81 !(order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
)) {
90 * Fill the table with arrivals and departures
91 * @param v Vehicle which must have at least 2 orders.
92 * @param start order index to start at
93 * @param travelling Are we still in the travelling part of the start order
94 * @param table Fill in arrival and departures including intermediate orders
95 * @param offset Add this value to result and all arrivals and departures
97 static void FillTimetableArrivalDepartureTable(const Vehicle
*v
, VehicleOrderID start
, bool travelling
, TimetableArrivalDeparture
*table
, Ticks offset
)
99 assert(table
!= nullptr);
100 assert(v
->GetNumOrders() >= 2);
101 assert(start
< v
->GetNumOrders());
104 VehicleOrderID i
= start
;
105 const Order
*order
= v
->GetOrder(i
);
107 /* Pre-initialize with unknown time */
108 for (int i
= 0; i
< v
->GetNumOrders(); ++i
) {
109 table
[i
].arrival
= table
[i
].departure
= INVALID_TICKS
;
112 /* Cyclically loop over all orders until we reach the current one again.
113 * As we may start at the current order, do a post-checking loop */
115 /* Automatic orders don't influence the overall timetable;
116 * they just add some untimetabled entries, but the time till
117 * the next non-implicit order can still be known. */
118 if (!order
->IsType(OT_IMPLICIT
)) {
119 if (travelling
|| i
!= start
) {
120 if (!CanDetermineTimeTaken(order
, true)) return;
121 sum
+= order
->GetTimetabledTravel();
122 table
[i
].arrival
= sum
;
125 if (!CanDetermineTimeTaken(order
, false)) return;
126 sum
+= order
->GetTimetabledWait();
127 table
[i
].departure
= sum
;
132 if (i
>= v
->GetNumOrders()) {
134 assert(order
== nullptr);
135 order
= v
->GetFirstOrder();
137 } while (i
!= start
);
139 /* When loading at a scheduled station we still have to treat the
140 * travelling part of the first order. */
142 if (!CanDetermineTimeTaken(order
, true)) return;
143 sum
+= order
->GetTimetabledTravel();
144 table
[i
].arrival
= sum
;
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
157 bool query_is_bulk_query
; ///< The currently open query window applies to all relevant orders.
158 TTSepSettings new_sep_settings
; ///< Contains new separation settings.
159 VehicleTimetableWidgets query_widget
; ///< Required to determinate source of input query
160 int summary_warnings
= 0; ///< Number of summary warnings shown
162 TimetableWindow(WindowDesc
*desc
, WindowNumber window_number
) :
165 vehicle(Vehicle::Get(window_number
)),
168 this->new_sep_settings
= vehicle
->GetTimetableSeparationSettings();
169 this->CreateNestedTree();
170 this->vscroll
= this->GetScrollbar(WID_VT_SCROLLBAR
);
171 this->UpdateSelectionStates();
172 this->FinishInitNested(window_number
);
174 this->owner
= this->vehicle
->owner
;
179 if (!FocusWindowById(WC_VEHICLE_VIEW
, this->window_number
)) {
180 MarkAllRouteStepsDirty(this->vehicle
);
185 * Build the arrival-departure list for a given vehicle
186 * @param v the vehicle to make the list for
187 * @param table the table to fill
188 * @return if next arrival will be early
190 static bool BuildArrivalDepartureList(const Vehicle
*v
, TimetableArrivalDeparture
*table
)
192 assert(HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
));
194 bool travelling
= (!(v
->current_order
.IsType(OT_LOADING
) || v
->current_order
.IsType(OT_WAITING
)) || v
->current_order
.GetNonStopType() == ONSF_STOP_EVERYWHERE
);
195 Ticks start_time
= GetCurrentTickCount() - v
->current_order_time
;
196 if (v
->cur_timetable_order_index
!= INVALID_VEH_ORDER_ID
&& v
->cur_timetable_order_index
!= v
->cur_real_order_index
) {
197 // Vehicle is taking a conditional order branch, adjust start time to compensate.
198 const Order
* real_current_order
= v
->GetOrder(v
->cur_real_order_index
);
199 const Order
* real_timetable_order
= v
->GetOrder(v
->cur_timetable_order_index
);
200 assert(real_timetable_order
->IsType(OT_CONDITIONAL
));
201 start_time
+= (real_timetable_order
->GetWaitTime() - real_current_order
->GetTravelTime());
204 FillTimetableArrivalDepartureTable(v
, v
->cur_real_order_index
% v
->GetNumOrders(), travelling
, table
, start_time
);
206 return (travelling
&& v
->lateness_counter
< 0);
209 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
212 case WID_VT_ARRIVAL_DEPARTURE_PANEL
:
213 SetDParamMaxValue(0, MAX_YEAR
* DAYS_IN_YEAR
, 0, FS_SMALL
);
214 this->deparr_time_width
= GetStringBoundingBox(STR_JUST_TIME
).width
;
215 this->deparr_abbr_width
= max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION
).width
, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION
).width
);
216 size
->width
= WD_FRAMERECT_LEFT
+ this->deparr_abbr_width
+ 10 + this->deparr_time_width
+ 10 + WD_FRAMERECT_RIGHT
;
219 case WID_VT_ARRIVAL_DEPARTURE_SELECTION
:
220 case WID_VT_TIMETABLE_PANEL
:
221 resize
->height
= FONT_HEIGHT_NORMAL
;
222 size
->height
= WD_FRAMERECT_TOP
+ 8 * resize
->height
+ WD_FRAMERECT_BOTTOM
;
225 case WID_VT_SUMMARY_PANEL
: {
226 Dimension d
= GetSpriteSize(SPR_WARNING_SIGN
);
227 size
->height
= WD_FRAMERECT_TOP
+ 2 * FONT_HEIGHT_NORMAL
+ this->summary_warnings
* max
<int>(d
.height
, FONT_HEIGHT_NORMAL
) + WD_FRAMERECT_BOTTOM
;
233 int GetOrderFromTimetableWndPt(int y
, const Vehicle
*v
)
235 int sel
= (y
- this->GetWidget
<NWidgetBase
>(WID_VT_TIMETABLE_PANEL
)->pos_y
- WD_FRAMERECT_TOP
) / FONT_HEIGHT_NORMAL
;
237 if ((uint
)sel
>= this->vscroll
->GetCapacity()) return INVALID_ORDER
;
239 sel
+= this->vscroll
->GetPosition();
241 return (sel
< v
->GetNumOrders() * 2 && sel
>= 0) ? sel
: INVALID_ORDER
;
245 * Some data on this window has become invalid.
246 * @param data Information about the changed data.
247 * @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.
249 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
251 this->new_sep_settings
= vehicle
->GetTimetableSeparationSettings();
254 case VIWD_AUTOREPLACE
:
255 /* Autoreplace replaced the vehicle */
256 this->vehicle
= Vehicle::Get(this->window_number
);
259 case VIWD_REMOVE_ALL_ORDERS
:
260 /* Removed / replaced all orders (after deleting / sharing) */
261 if (this->sel_index
== -1) break;
263 this->DeleteChildWindows();
264 this->sel_index
= -1;
267 case VIWD_MODIFY_ORDERS
:
268 if (!gui_scope
) break;
269 this->UpdateSelectionStates();
274 if (gui_scope
) break; // only do this once; from command scope
276 /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
277 * the order is being created / removed */
278 if (this->sel_index
== -1) break;
280 VehicleOrderID from
= GB(data
, 0, 8);
281 VehicleOrderID to
= GB(data
, 8, 8);
283 if (from
== to
) break; // no need to change anything
285 /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
286 uint old_num_orders
= this->vehicle
->GetNumOrders() - (uint
)(from
== INVALID_VEH_ORDER_ID
) + (uint
)(to
== INVALID_VEH_ORDER_ID
);
288 VehicleOrderID selected_order
= (this->sel_index
+ 1) / 2;
289 if (selected_order
== old_num_orders
) selected_order
= 0; // when last travel time is selected, it belongs to order 0
291 bool travel
= HasBit(this->sel_index
, 0);
293 if (from
!= selected_order
) {
294 /* Moving from preceding order? */
295 selected_order
-= (int)(from
<= selected_order
);
296 /* Moving to preceding order? */
297 selected_order
+= (int)(to
<= selected_order
);
299 /* Now we are modifying the selected order */
300 if (to
== INVALID_VEH_ORDER_ID
) {
301 /* Deleting selected order */
302 this->DeleteChildWindows();
303 this->sel_index
= -1;
306 /* Moving selected order */
311 /* recompute new sel_index */
312 this->sel_index
= 2 * selected_order
- (int)travel
;
313 /* travel time of first order needs special handling */
314 if (this->sel_index
== -1) this->sel_index
= this->vehicle
->GetNumOrders() * 2 - 1;
321 virtual void OnPaint()
323 const Vehicle
*v
= this->vehicle
;
324 int selected
= this->sel_index
;
326 this->vscroll
->SetCount(v
->GetNumOrders() * 2);
328 if (v
->owner
== _local_company
) {
329 bool disable
= IsActionDisabled(v
, selected
);
330 bool disable_speed
= disable
|| selected
% 2 != 1 || v
->type
== VEH_AIRCRAFT
;
332 this->SetWidgetDisabledState(WID_VT_CHANGE_TIME
, disable
);
333 this->SetWidgetDisabledState(WID_VT_CLEAR_TIME
, disable
);
334 this->SetWidgetDisabledState(WID_VT_CHANGE_SPEED
, disable_speed
);
335 this->SetWidgetDisabledState(WID_VT_CLEAR_SPEED
, disable_speed
);
336 this->SetWidgetDisabledState(WID_VT_SHARED_ORDER_LIST
, !v
->HasSharedOrdersList());
338 this->SetWidgetDisabledState(WID_VT_CONFIRM_ALL
, !v
->HasOrdersList());
339 this->SetWidgetDisabledState(WID_VT_RESET_LATENESS
, !v
->HasOrdersList());
340 this->SetWidgetDisabledState(WID_VT_AUTOMATE
, !v
->HasOrdersList());
342 this->DisableWidget(WID_VT_CONFIRM_ALL
);
343 this->DisableWidget(WID_VT_CHANGE_TIME
);
344 this->DisableWidget(WID_VT_CLEAR_TIME
);
345 this->DisableWidget(WID_VT_CHANGE_SPEED
);
346 this->DisableWidget(WID_VT_CLEAR_SPEED
);
347 this->DisableWidget(WID_VT_RESET_LATENESS
);
348 this->DisableWidget(WID_VT_AUTOMATE
);
349 this->DisableWidget(WID_VT_SHARED_ORDER_LIST
);
352 /* We can only set parameters if we're in one of the manual modes. */
353 bool enabled_state
= (this->new_sep_settings
.mode
== TTS_MODE_MAN_N
) || (this->new_sep_settings
.mode
== TTS_MODE_MAN_T
);
355 this->SetWidgetDisabledState(WID_VT_TTSEP_SET_PARAMETER
, !enabled_state
);
357 this->SetWidgetLoweredState(WID_VT_AUTOMATE
, HasBit(v
->vehicle_flags
, VF_AUTOMATE_TIMETABLE
));
362 virtual void SetStringParameters(int widget
) const
365 case WID_VT_CAPTION
: SetDParam(0, this->vehicle
->index
); break;
366 case WID_VT_EXPECTED
: SetDParam(0, this->show_expected
? STR_TIMETABLE_EXPECTED
: STR_TIMETABLE_SCHEDULED
); break;
367 case WID_VT_TTSEP_MODE_DROPDOWN
: SetDParam(0, TimetableSeparationDropdownOptions
[this->new_sep_settings
.mode
]); break;
368 case WID_VT_TTSEP_SET_PARAMETER
: SetDParam(0, (this->new_sep_settings
.mode
== TTS_MODE_MAN_N
) ? STR_TTSEPARATION_SET_NUM
: STR_TTSEPARATION_SET_TIME
); break;
372 void DrawWarnings(const Rect
& r
, int& y
, const Vehicle
* v
) const
374 bool have_missing_times
= !v
->HasCompleteTimetable();
375 bool have_conditional
= false;
376 bool have_bad_full_load
= false;
378 const bool is_automated_timetable
= HasBit(v
->vehicle_flags
, VF_AUTOMATE_TIMETABLE
);
380 for (int n
= 0; n
< v
->GetNumOrders(); ++n
) {
381 const Order
*order
= v
->GetOrder(n
);
383 if (order
->IsType(OT_CONDITIONAL
)) {
384 have_conditional
= true;
387 if (!have_bad_full_load
&& (is_automated_timetable
|| order
->IsWaitTimetabled())) {
388 if (order
->GetLoadType() & OLFB_FULL_LOAD
) {
389 have_bad_full_load
= true;
391 else if (order
->GetLoadType() == OLFB_CARGO_TYPE_LOAD
) {
392 for (CargoID c
= 0; c
< NUM_CARGO
; c
++) {
393 if (order
->GetCargoLoadTypeRaw(c
) & OLFB_FULL_LOAD
) {
394 have_bad_full_load
= true;
402 const Dimension warning_dimensions
= GetSpriteSize(SPR_WARNING_SIGN
);
403 const int step_height
= max
<int>(warning_dimensions
.height
, FONT_HEIGHT_NORMAL
);
404 const int text_offset_y
= (step_height
- FONT_HEIGHT_NORMAL
) / 2;
405 const int warning_offset_y
= (step_height
- warning_dimensions
.height
) / 2;
406 const bool rtl
= _current_text_dir
== TD_RTL
;
408 int warning_count
= 0;
410 auto draw_info
= [&](StringID text
, bool warning
) {
411 int left
= r
.left
+ WD_FRAMERECT_LEFT
;
412 int right
= r
.right
- WD_FRAMERECT_RIGHT
;
415 DrawSprite(SPR_WARNING_SIGN
, 0, rtl
? right
- warning_dimensions
.width
- 5 : left
+ 5, y
+ warning_offset_y
);
417 right
-= (warning_dimensions
.width
+ 10);
420 left
+= (warning_dimensions
.width
+ 10);
424 DrawString(left
, right
, y
+ text_offset_y
, text
);
430 if (this->new_sep_settings
.mode
!= TTS_MODE_OFF
) {
431 if (v
->GetNumOrders() == 0) {
432 draw_info(STR_TIMETABLE_AUTOSEP_TIMETABLE_INCOMPLETE
, false);
434 else if (have_missing_times
) {
435 if (is_automated_timetable
) {
436 draw_info(STR_TIMETABLE_AUTOSEP_TIMETABLE_INCOMPLETE
, false);
439 draw_info(STR_TIMETABLE_WARNING_AUTOSEP_MISSING_TIMINGS
, true);
443 draw_info(v
->HasSharedOrdersList() ? STR_TIMETABLE_AUTOSEP_OK
: STR_TIMETABLE_AUTOSEP_SINGLE_VEH
, !v
->HasSharedOrdersList());
446 if (have_conditional
) draw_info(STR_TIMETABLE_WARNING_AUTOSEP_CONDITIONAL
, true);
447 if (have_bad_full_load
) draw_info(STR_TIMETABLE_WARNING_FULL_LOAD
, true);
450 if (warning_count
!= this->summary_warnings
) {
451 TimetableWindow
* mutable_this
= const_cast<TimetableWindow
*>(this);
452 mutable_this
->summary_warnings
= warning_count
;
453 mutable_this
->ReInit();
457 virtual void DrawWidget(const Rect
&r
, int widget
) const
459 const Vehicle
*v
= this->vehicle
;
460 int selected
= this->sel_index
;
463 case WID_VT_TIMETABLE_PANEL
: {
464 int y
= r
.top
+ WD_FRAMERECT_TOP
;
465 int i
= this->vscroll
->GetPosition();
466 VehicleOrderID order_id
= (i
+ 1) / 2;
467 bool final_order
= false;
469 bool rtl
= _current_text_dir
== TD_RTL
;
470 SetDParamMaxValue(0, v
->GetNumOrders(), 2);
471 int index_column_width
= GetStringBoundingBox(STR_ORDER_INDEX
).width
+ 2 * GetSpriteSize(rtl
? SPR_ARROW_RIGHT
: SPR_ARROW_LEFT
).width
+ 3;
472 int middle
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- index_column_width
: r
.left
+ WD_FRAMERECT_LEFT
+ index_column_width
;
474 const Order
*order
= v
->GetOrder(order_id
);
475 while (order
!= nullptr) {
476 /* Don't draw anything if it extends past the end of the window. */
477 if (!this->vscroll
->IsVisible(i
)) break;
480 DrawOrderString(v
, order
, order_id
, y
, i
== selected
, true, r
.left
+ WD_FRAMERECT_LEFT
, middle
, r
.right
- WD_FRAMERECT_RIGHT
);
484 if (order_id
>= v
->GetNumOrders()) {
485 order
= v
->GetOrder(0);
492 TextColour colour
= (i
== selected
) ? TC_WHITE
: TC_BLACK
;
493 if (order
->IsType(OT_CONDITIONAL
)) {
494 string
= STR_TIMETABLE_NO_TRAVEL
;
495 } else if (order
->IsType(OT_IMPLICIT
)) {
496 string
= STR_TIMETABLE_NOT_TIMETABLEABLE
;
497 colour
= ((i
== selected
) ? TC_SILVER
: TC_GREY
) | TC_NO_SHADE
;
498 } else if (!order
->IsTravelTimetabled()) {
499 if (order
->GetTravelTime() > 0) {
500 SetTimetableParams(order
->GetTravelTime());
501 string
= order
->GetMaxSpeed() != UINT16_MAX
?
502 STR_TIMETABLE_TRAVEL_FOR_MINUTES_SPEED_ESTIMATED
:
503 STR_TIMETABLE_TRAVEL_FOR_MINUTES_ESTIMATED
;
505 string
= order
->GetMaxSpeed() != UINT16_MAX
?
506 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED
:
507 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED
;
510 SetTimetableParams(order
->GetTimetabledTravel());
511 string
= order
->GetMaxSpeed() != UINT16_MAX
?
512 STR_TIMETABLE_TRAVEL_FOR_MINUTES_SPEED
: STR_TIMETABLE_TRAVEL_FOR_MINUTES
;
514 SetDParam(3, order
->GetMaxSpeed());
516 DrawString(rtl
? r
.left
+ WD_FRAMERECT_LEFT
: middle
, rtl
? middle
: r
.right
- WD_FRAMERECT_LEFT
, y
, string
, colour
);
518 if (final_order
) break;
522 y
+= FONT_HEIGHT_NORMAL
;
527 case WID_VT_ARRIVAL_DEPARTURE_PANEL
: {
528 /* Arrival and departure times are handled in an all-or-nothing approach,
529 * i.e. are only shown if we can calculate all times.
530 * Excluding order lists with only one order makes some things easier.
532 Ticks total_time
= v
->GetTimetableDurationIncomplete();
533 if (total_time
<= 0 || v
->GetNumOrders() <= 1 || !HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
)) break;
535 TimetableArrivalDeparture
*arr_dep
= AllocaM(TimetableArrivalDeparture
, v
->GetNumOrders());
536 const VehicleOrderID cur_order
= v
->cur_real_order_index
% v
->GetNumOrders();
538 VehicleOrderID earlyID
= BuildArrivalDepartureList(v
, arr_dep
) ? cur_order
: (VehicleOrderID
)INVALID_VEH_ORDER_ID
;
540 int y
= r
.top
+ WD_FRAMERECT_TOP
;
542 bool show_late
= this->show_expected
&& v
->lateness_counter
> _settings_client
.gui
.ticks_per_minute
;
543 Ticks offset
= show_late
? 0 : -v
->lateness_counter
;
545 if (HasBit(v
->vehicle_flags
, VF_SEPARATION_IN_PROGRESS
)) {
549 bool rtl
= _current_text_dir
== TD_RTL
;
550 int abbr_left
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- this->deparr_abbr_width
: r
.left
+ WD_FRAMERECT_LEFT
;
551 int abbr_right
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
: r
.left
+ WD_FRAMERECT_LEFT
+ this->deparr_abbr_width
;
552 int time_left
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- r
.left
- WD_FRAMERECT_RIGHT
- this->deparr_abbr_width
- 10 - this->deparr_time_width
: r
.left
+ WD_FRAMERECT_RIGHT
+ this->deparr_abbr_width
+ 10;
553 int time_right
= rtl
? r
.right
- WD_FRAMERECT_RIGHT
- this->deparr_abbr_width
- 10 : r
.left
+ WD_FRAMERECT_RIGHT
+ r
.left
+ WD_FRAMERECT_RIGHT
+ this->deparr_abbr_width
+ 10 + this->deparr_time_width
;
555 for (int i
= this->vscroll
->GetPosition(); i
/ 2 < v
->GetNumOrders(); ++i
) { // note: i is also incremented in the loop
556 /* Don't draw anything if it extends past the end of the window. */
557 if (!this->vscroll
->IsVisible(i
)) break;
560 if (arr_dep
[i
/ 2].arrival
!= INVALID_TICKS
) {
561 DrawString(abbr_left
, abbr_right
, y
, STR_TIMETABLE_ARRIVAL_ABBREVIATION
, i
== selected
? TC_WHITE
: TC_BLACK
);
562 if (this->show_expected
&& i
/ 2 == earlyID
) {
563 SetDParam(0, arr_dep
[i
/ 2].arrival
);
564 DrawString(time_left
, time_right
, y
, STR_JUST_TIME
, TC_GREEN
);
566 SetDParam(0, arr_dep
[i
/ 2].arrival
+ offset
);
567 DrawString(time_left
, time_right
, y
, STR_JUST_TIME
, show_late
? TC_RED
: i
== selected
? TC_WHITE
: TC_BLACK
);
572 if (arr_dep
[i
/ 2].departure
!= INVALID_TICKS
) {
573 DrawString(abbr_left
, abbr_right
, y
, STR_TIMETABLE_DEPARTURE_ABBREVIATION
, i
== selected
? TC_WHITE
: TC_BLACK
);
574 SetDParam(0, arr_dep
[i
/ 2].departure
+ offset
);
575 DrawString(time_left
, time_right
, y
, STR_JUST_TIME
,
576 show_late
? TC_RED
: i
== selected
? TC_WHITE
: TC_BLACK
);
579 y
+= FONT_HEIGHT_NORMAL
;
584 case WID_VT_SUMMARY_PANEL
: {
585 int y
= r
.top
+ WD_FRAMERECT_TOP
;
587 Ticks total_time
= v
->GetTimetableDurationIncomplete();
588 if (total_time
!= 0) {
589 SetTimetableParams(total_time
);
590 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, v
->HasCompleteTimetable() ? STR_TIMETABLE_TOTAL_TIME_MINUTES
: STR_TIMETABLE_TOTAL_TIME_MINUTES_INCOMPLETE
);
592 y
+= FONT_HEIGHT_NORMAL
;
594 auto lateness_counter
= HasBit(v
->vehicle_flags
, VF_SEPARATION_IN_PROGRESS
) ? 0 : v
->lateness_counter
;
596 if (v
->timetable_start
!= 0) {
597 /* We are running towards the first station so we can start the
598 * timetable at the given time. */
599 SetDParam(0, STR_JUST_DATE
);
600 SetDParam(1, v
->timetable_start
);
601 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_START_AT
);
603 else if (!HasBit(v
->vehicle_flags
, VF_TIMETABLE_STARTED
)) {
604 /* We aren't running on a timetable yet, so how can we be "on time"
605 * when we aren't even "on service"/"on duty"? */
606 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_NOT_STARTED
);
608 else if (lateness_counter
== 0) {
609 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_TIMETABLE_STATUS_ON_TIME
);
612 SetTimetableParams(abs(lateness_counter
));
613 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, lateness_counter
< 0 ? STR_TIMETABLE_STATUS_EARLY_MINUTES
: STR_TIMETABLE_STATUS_LATE_MINUTES
);
615 y
+= FONT_HEIGHT_NORMAL
;
617 DrawWarnings(r
, y
, v
);
621 case WID_VT_TTSEP_PANEL_TEXT
: {
622 int y
= r
.top
+ WD_FRAMERECT_TOP
; // Represents the current vertical position
623 const int left_border
= r
.left
+ WD_FRAMERECT_LEFT
; // Represents the left border of the separation display frame
624 const int right_border
= r
.right
- WD_FRAMERECT_RIGHT
; // Represents the right border of the separation display frame.
626 /* If separation is inactive, we can stop here. */
627 if (!_settings_game
.order
.automatic_timetable_separation
|| !this->vehicle
->HasOrdersList())
630 if (this->new_sep_settings
.mode
!= TTS_MODE_OFF
&&
631 this->vehicle
->HasCompleteTimetable() &&
632 this->vehicle
->IsTimetableSeparationValid()) {
635 // If separation hasn't just been switched off, we need to draw various description lines.
636 // The first line is the amount of separation which is either saved in the struct or must
637 // be calculated on the fly.
638 if (this->new_sep_settings
.mode
== TTS_MODE_MAN_T
||
639 this->new_sep_settings
.mode
== TTS_MODE_AUTO
||
640 this->new_sep_settings
.mode
== TTS_MODE_BUFFERED_AUTO
) {
641 par
= this->new_sep_settings
.sep_ticks
;
644 par
= this->vehicle
->GetTimetableTotalDuration() / max(1u, this->new_sep_settings
.num_veh
);
647 if (this->new_sep_settings
.mode
== TTS_MODE_MAN_T
||
648 (this->vehicle
->HasCompleteTimetable() && this->vehicle
->IsTimetableSeparationValid())) {
652 DrawString(left_border
, right_border
, y
, STR_TTSEPARATION_REQ_TIME_DESC
, TC_BLACK
);
653 y
+= GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC
).height
;
655 DrawString(left_border
, right_border
, y
, STR_TTSEPARATION_BETWEEN
, TC_BLACK
);
656 y
+= GetStringBoundingBox(STR_TTSEPARATION_BETWEEN
).height
;
659 y
+= GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC
).height
;
662 // Print either the chosen amount of vehicles (when in MAN_N mode) or the calculated result...
663 // Do not print anything at all if we do not have a valid timetable yet.
664 if (this->new_sep_settings
.mode
== TTS_MODE_MAN_N
||
665 this->new_sep_settings
.mode
== TTS_MODE_AUTO
||
666 this->new_sep_settings
.mode
== TTS_MODE_BUFFERED_AUTO
) {
667 par
= this->new_sep_settings
.num_veh
;
670 par
= this->vehicle
->GetTimetableTotalDuration() / max(1u, this->new_sep_settings
.sep_ticks
);
673 if (this->new_sep_settings
.mode
== TTS_MODE_MAN_N
||
674 (this->vehicle
->HasCompleteTimetable() && this->vehicle
->IsTimetableSeparationValid())) {
676 DrawString(left_border
, right_border
, y
, STR_TTSEPARATION_REQ_NUM_DESC
, TC_BLACK
);
679 y
+= GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC
).height
;
682 /* If separation is switched on at all... */
683 if (this->vehicle
->IsTimetableSeparationOn()) {
684 if (!this->vehicle
->HasCompleteTimetable()) {
685 SetDParam(0, STR_TTSEPARATION_STATUS_WAITING_FOR_TIMETABLE
);
687 else if (!this->vehicle
->HasSharedOrdersList()) {
688 SetDParam(0, STR_TTSEPARATION_STATUS_WAITING_FOR_VEHICLES
);
691 /* ... set displayed status to either "Running" or "Initializing" */
692 SetDParam(0, (this->vehicle
->IsTimetableSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING
: STR_TTSEPARATION_STATUS_INIT
);
696 /* If separation is switched off, show this instead. */
697 SetDParam(0, STR_TTSEPARATION_STATUS_OFF
);
700 y
+= FONT_HEIGHT_NORMAL
;
702 /* Print status description. */
703 DrawStringMultiLine(left_border
, right_border
, y
, r
.bottom
- WD_FRAMERECT_BOTTOM
, STR_TTSEPARATION_STATUS_DESC
);
708 static inline uint32
PackTimetableArgs(const Vehicle
*v
, uint selected
, bool speed
)
710 uint order_number
= (selected
+ 1) / 2;
711 ModifyTimetableFlags mtf
= (selected
% 2 == 1) ? (speed
? MTF_TRAVEL_SPEED
: MTF_TRAVEL_TIME
) : MTF_WAIT_TIME
;
713 if (order_number
>= v
->GetNumOrders()) order_number
= 0;
715 return v
->index
| (order_number
<< 20) | (mtf
<< 28);
718 static inline bool IsActionDisabled(const Vehicle
*v
, int selected
)
720 bool disabled
= true;
721 if (selected
!= -1) {
722 const Order
*order
= v
->GetOrder(((selected
+ 1) / 2) % v
->GetNumOrders());
723 if (selected
% 2 == 1) {
724 disabled
= order
!= nullptr && (order
->IsType(OT_CONDITIONAL
) || order
->IsType(OT_IMPLICIT
));
727 disabled
= order
== nullptr || ((!(order
->IsType(OT_GOTO_STATION
) || (order
->IsType(OT_GOTO_DEPOT
) && !(order
->GetDepotActionType() & ODATFB_HALT
))) || (order
->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION
)) && !order
->IsType(OT_CONDITIONAL
));
733 virtual void OnClick(Point pt
, int widget
, int click_count
)
735 const Vehicle
*v
= this->vehicle
;
737 this->DeleteChildWindows(WC_QUERY_STRING
);
740 case WID_VT_ORDER_VIEW
: // Order view button
744 case WID_VT_TIMETABLE_PANEL
: { // Main panel.
745 int selected
= GetOrderFromTimetableWndPt(pt
.y
, v
);
747 /* Allow change time by double-clicking order */
748 if (click_count
== 2) {
749 this->sel_index
= selected
== INVALID_ORDER
? -1 : selected
;
750 this->OnClick(pt
, WID_VT_CHANGE_TIME
, click_count
);
753 this->sel_index
= (selected
== INVALID_ORDER
|| selected
== this->sel_index
) ? -1 : selected
;
756 this->DeleteChildWindows();
760 case WID_VT_CONFIRM_ALL
: { // Confirm all estimated times as timetabled.
761 DoCommandP(0, v
->index
, 0, CMD_CONFIRM_ALL
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
765 case WID_VT_CHANGE_TIME
: { // "Wait For" button.
766 int selected
= this->sel_index
;
767 VehicleOrderID real
= (selected
+ 1) / 2;
769 if (real
>= v
->GetNumOrders()) real
= 0;
771 const Order
*order
= v
->GetOrder(real
);
772 StringID current
= STR_EMPTY
;
774 if (order
!= nullptr) {
775 uint time
= (selected
% 2 == 1) ? order
->GetTravelTime() : order
->GetWaitTime();
779 current
= STR_JUST_INT
;
783 this->query_widget
= WID_VT_CHANGE_TIME
;
784 this->query_is_speed_query
= false;
785 this->query_is_bulk_query
= _ctrl_pressed
;
786 ShowQueryString(current
, STR_TIMETABLE_CHANGE_TIME
, 31, this, CS_NUMERAL
, QSF_ACCEPT_UNCHANGED
);
790 case WID_VT_CHANGE_SPEED
: { // Change max speed button.
791 int selected
= this->sel_index
;
792 VehicleOrderID real
= (selected
+ 1) / 2;
794 if (real
>= v
->GetNumOrders()) real
= 0;
796 StringID current
= STR_EMPTY
;
797 const Order
*order
= v
->GetOrder(real
);
798 if (order
!= nullptr) {
799 if (order
->GetMaxSpeed() != UINT16_MAX
) {
800 SetDParam(0, ConvertKmhishSpeedToDisplaySpeed(order
->GetMaxSpeed()));
801 current
= STR_JUST_INT
;
805 this->query_widget
= WID_VT_CHANGE_SPEED
;
806 this->query_is_speed_query
= true;
807 this->query_is_bulk_query
= _ctrl_pressed
;
808 ShowQueryString(current
, STR_TIMETABLE_CHANGE_SPEED
, 31, this, CS_NUMERAL
, QSF_NONE
);
812 case WID_VT_CLEAR_TIME
: { // Clear waiting time.
813 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, false);
814 DoCommandP(0, p1
, 0, (_ctrl_pressed
? CMD_BULK_CHANGE_TIMETABLE
: CMD_CHANGE_TIMETABLE
) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
818 case WID_VT_CLEAR_SPEED
: { // Clear max speed button.
819 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, true);
820 DoCommandP(0, p1
, UINT16_MAX
, (_ctrl_pressed
? CMD_BULK_CHANGE_TIMETABLE
: CMD_CHANGE_TIMETABLE
) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
824 case WID_VT_RESET_LATENESS
: // Reset the vehicle's late counter.
825 DoCommandP(0, v
->index
, 0, CMD_SET_VEHICLE_ON_TIME
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
828 case WID_VT_AUTOMATE
: { // Automate the timetable.
830 if (!HasBit(v
->vehicle_flags
, VF_AUTOMATE_TIMETABLE
)) SetBit(p2
, 0);
831 if (_ctrl_pressed
) SetBit(p2
, 1);
832 DoCommandP(0, v
->index
, p2
, CMD_AUTOMATE_TIMETABLE
| CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
836 case WID_VT_EXPECTED
:
837 this->show_expected
= !this->show_expected
;
840 case WID_VT_SHARED_ORDER_LIST
:
841 ShowVehicleListWindow(v
);
844 case WID_VT_TTSEP_MODE_DROPDOWN
: {
845 ShowDropDownMenu(this, TimetableSeparationDropdownOptions
, this->new_sep_settings
.mode
, WID_VT_TTSEP_MODE_DROPDOWN
, 0, 0);
849 case WID_VT_TTSEP_SET_PARAMETER
: {
850 this->query_widget
= WID_VT_TTSEP_SET_PARAMETER
;
851 SetDParam(0, (this->new_sep_settings
.mode
== TTS_MODE_MAN_N
) ? this->new_sep_settings
.num_veh
: this->new_sep_settings
.sep_ticks
);
852 ShowQueryString(STR_JUST_INT
, STR_TIMETABLE_CHANGE_TIME
, 31, this, CS_NUMERAL
, QSF_NONE
);
860 virtual void OnDropdownSelect(int widget
, int index
)
862 assert(widget
== WID_VT_TTSEP_MODE_DROPDOWN
);
864 this->new_sep_settings
= this->vehicle
->GetTimetableSeparationSettings();
865 this->new_sep_settings
.mode
= (TTSepMode
)index
;
866 this->vehicle
->SetTimetableSeparationSettings(this->new_sep_settings
);
867 this->InvalidateData();
871 virtual void OnQueryTextFinished(char *str
)
873 if (str
== nullptr || StrEmpty(str
))
876 switch (this->query_widget
) {
877 case WID_VT_CHANGE_TIME
:
878 case WID_VT_CHANGE_SPEED
: {
879 const Vehicle
*v
= this->vehicle
;
881 uint32 p1
= PackTimetableArgs(v
, this->sel_index
, this->query_is_speed_query
);
883 uint64 val
= StrEmpty(str
) ? 0 : strtoul(str
, nullptr, 10);
884 if (this->query_is_speed_query
) {
885 val
= ConvertDisplaySpeedToKmhishSpeed(val
);
888 uint32 p2
= minu(val
, UINT16_MAX
);
890 DoCommandP(0, p1
, p2
, (this->query_is_bulk_query
? CMD_BULK_CHANGE_TIMETABLE
: CMD_CHANGE_TIMETABLE
) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE
));
893 case WID_VT_TTSEP_SET_PARAMETER
: {
894 int value
= atoi(str
);
896 switch (this->new_sep_settings
.mode
)
899 case TTS_MODE_BUFFERED_AUTO
:
904 this->new_sep_settings
.num_veh
= Clamp(value
, 1, 65535);
908 this->new_sep_settings
.sep_ticks
= Clamp(value
, 1, 65535);
916 this->vehicle
->SetTimetableSeparationSettings(this->new_sep_settings
);
917 this->InvalidateData();
926 virtual void OnResize()
928 /* Update the scroll bar */
929 this->vscroll
->SetCapacityFromWidget(this, WID_VT_TIMETABLE_PANEL
, WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
);
933 * Update the selection state of the arrival/departure data
935 void UpdateSelectionStates()
937 this->GetWidget
<NWidgetStacked
>(WID_VT_ARRIVAL_DEPARTURE_SELECTION
)->SetDisplayedPlane(_settings_client
.gui
.timetable_arrival_departure
? 0 : SZSP_NONE
);
938 this->GetWidget
<NWidgetStacked
>(WID_VT_EXPECTED_SELECTION
)->SetDisplayedPlane(_settings_client
.gui
.timetable_arrival_departure
? 0 : 1);
941 virtual void OnFocus(Window
*previously_focused_window
)
943 if (HasFocusedVehicleChanged(this->window_number
, previously_focused_window
)) {
944 MarkAllRoutePathsDirty(this->vehicle
);
945 MarkAllRouteStepsDirty(this->vehicle
);
949 virtual void OnFocusLost(Window
*newly_focused_window
)
951 if (HasFocusedVehicleChanged(this->window_number
, newly_focused_window
)) {
952 MarkAllRoutePathsDirty(this->vehicle
);
953 MarkAllRouteStepsDirty(this->vehicle
);
957 const Vehicle
*GetVehicle()
959 return this->vehicle
;
963 static const NWidgetPart _nested_timetable_widgets
[] = {
964 NWidget(NWID_HORIZONTAL
),
965 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
966 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_VT_CAPTION
), SetDataTip(STR_TIMETABLE_TITLE
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
967 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_ORDER_VIEW
), SetMinimalSize(61, 14), SetDataTip( STR_TIMETABLE_ORDER_VIEW
, STR_TIMETABLE_ORDER_VIEW_TOOLTIP
),
968 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
969 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
970 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
972 NWidget(NWID_HORIZONTAL
),
973 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(),
974 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_VT_ARRIVAL_DEPARTURE_SELECTION
),
975 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_ARRIVAL_DEPARTURE_PANEL
), SetMinimalSize(85, 0), SetFill(0, 1), SetDataTip(STR_NULL
, STR_TIMETABLE_TOOLTIP
), SetScrollbar(WID_VT_SCROLLBAR
), EndContainer(),
977 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_VT_SCROLLBAR
),
978 NWidget(WWT_PANEL
, COLOUR_GREY
),
979 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_TTSEPARATION_SETTINGS_DESC
, STR_NULL
), SetPadding(3),
980 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_VT_TTSEP_MODE_DROPDOWN
), SetDataTip(STR_JUST_STRING
, STR_TIMETABLE_TOOLTIP
),
981 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_TTSEP_SET_PARAMETER
), SetFill(1, 0), SetDataTip(STR_TTSEPARATION_SET_XX
, STR_TIMETABLE_TOOLTIP
),
982 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_TTSEP_PANEL_TEXT
), SetFill(1, 1), SetResize(0, 1), SetMinimalSize(225, 44), EndContainer(),
986 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_VT_SUMMARY_PANEL
), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
987 NWidget(NWID_HORIZONTAL
),
988 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
989 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
990 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
),
991 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
),
993 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
994 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
),
995 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
),
997 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
998 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_CONFIRM_ALL
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CONFIRM_ALL
, STR_TIMETABLE_CONFIRM_ALL_TOOLTIP
),
999 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
),
1001 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
1002 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_AUTOMATE
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOMATE
, STR_TIMETABLE_AUTOMATE_TOOLTIP
),
1003 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_VT_EXPECTED_SELECTION
),
1004 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_VT_EXPECTED
), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING
, STR_TIMETABLE_EXPECTED_TOOLTIP
),
1005 NWidget(WWT_PANEL
, COLOUR_GREY
), SetResize(1, 0), SetFill(1, 1), EndContainer(),
1009 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
),
1010 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
),
1011 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
), SetFill(0, 1),
1016 static WindowDesc
_timetable_desc(
1017 WDP_AUTO
, "view_vehicle_timetable", 400, 130,
1018 WC_VEHICLE_TIMETABLE
, WC_VEHICLE_VIEW
,
1020 _nested_timetable_widgets
, lengthof(_nested_timetable_widgets
)
1024 * Show the timetable for a given vehicle.
1025 * @param v The vehicle to show the timetable for.
1027 void ShowTimetableWindow(const Vehicle
*v
)
1029 DeleteWindowById(WC_VEHICLE_DETAILS
, v
->index
, false);
1030 DeleteWindowById(WC_VEHICLE_ORDERS
, v
->index
, false);
1031 AllocateWindowDescFront
<TimetableWindow
>(&_timetable_desc
, v
->index
);