Update readme and changelog for v1.27.0
[openttd-joker.git] / src / timetable_gui.cpp
bloba0bbdefd0dcfb696415985a29b3ed40a7fffe0b3
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file timetable_gui.cpp GUI for time tabling. */
12 #include "stdafx.h"
14 #include "command_func.h"
15 #include "company_func.h"
16 #include "date_func.h"
17 #include "date_gui.h"
18 #include "gfx_func.h"
19 #include "gui.h"
20 #include "settings_type.h"
21 #include "string_func.h"
22 #include "strings_func.h"
23 #include "textbuf_gui.h"
24 #include "timetable.h"
25 #include "vehicle_base.h"
26 #include "vehicle_gui.h"
27 #include "viewport_func.h"
28 #include "window_func.h"
29 #include "window_gui.h"
31 #include "table/sprites.h"
32 #include "table/strings.h"
33 #include "widgets/timetable_widget.h"
34 #include "widgets/dropdown_func.h"
36 //! Entries for mode selection dropdown list. Order must be identical to the one in #TTSepMode.
37 static const StringID TimetableSeparationDropdownOptions[6] = {
38 STR_TTSEPARATION_AUTO,
39 STR_TTSEPARATION_OFF,
40 STR_TTSEPARATION_MAN_TIME,
41 STR_TTSEPARATION_MAN_NUM,
42 STR_TTSEPARATION_BUFFERED_AUTO,
43 INVALID_STRING_ID,
46 //! Container for the arrival/departure dates of a vehicle.
47 struct TimetableArrivalDeparture
49 Ticks arrival; //!< The arrival time
50 Ticks departure; //!< The departure time
53 //! Set the timetable parameters in the ticks (minutes) format.
54 //! @param param1 the first of three successive DParam to fill
55 //! @param ticks the number of ticks to 'draw'
56 void SetTimetableParams(Ticks ticks, int param1)
58 SetDParam(param1, STR_TIMETABLE_TICKS_MINUTES);
59 SetDParam(param1 + 1, ticks);
60 SetDParam(param1 + 2, ticks);
63 //! Check whether it is possible to determine how long the order takes.
64 //! @param order the order to check.
65 //! @param travelling whether we are interested in the travel or the wait part.
66 //! @return true if the travel/wait time can be used.
67 static bool CanDetermineTimeTaken(const Order* order, bool travelling)
69 // Current order is conditional.
70 if (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT)) return false;
72 // No travel time and we have not already finished travelling.
73 if (travelling && !order->IsTravelTimetabled()) return false;
75 // No wait time but we are loading at this timetabled station.
76 if (!travelling && !order->IsWaitTimetabled() && order->IsType(OT_GOTO_STATION) &&
77 !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) {
78 return false;
81 return true;
84 //! Fill the table with arrivals and departures
85 //! @param vehicle Vehicle which must have at least 2 orders.
86 //! @param start order index to start at
87 //! @param travelling Are we still in the travelling part of the start order
88 //! @param table Fill in arrival and departures including intermediate orders
89 //! @param offset Add this value to result and all arrivals and departures
90 static void FillTimetableArrivalDepartureTable(const Vehicle* vehicle, VehicleOrderID start, bool travelling, TimetableArrivalDeparture* table, Ticks offset)
92 assert(table != nullptr);
93 assert(vehicle->GetNumOrders() >= 2);
94 assert(start < vehicle->GetNumOrders());
96 Ticks sum = offset;
97 VehicleOrderID i = start;
98 const Order* order = vehicle->GetOrder(i);
100 // Pre-initialize with unknown time.
101 for (int j = 0; j < vehicle->GetNumOrders(); ++j) {
102 table[j].arrival = table[j].departure = INVALID_TICKS;
105 // Cyclically loop over all orders until we reach the current one again.
106 // As we may start at the current order, do a post-checking loop.
107 do {
108 // Automatic orders don't influence the overall timetable;
109 // they just add some untimetabled entries, but the time till
110 // the next non-implicit order can still be known.
111 if (!order->IsType(OT_IMPLICIT)) {
112 if (travelling || i != start) {
113 if (!CanDetermineTimeTaken(order, true)) return;
114 sum += order->GetTimetabledTravel();
115 table[i].arrival = sum;
118 if (!CanDetermineTimeTaken(order, false)) return;
119 sum += order->GetTimetabledWait();
120 table[i].departure = sum;
123 ++i;
124 order = order->next;
125 if (i >= vehicle->GetNumOrders()) {
126 i = 0;
127 assert(order == nullptr);
128 order = vehicle->GetFirstOrder();
130 } while (i != start);
132 // When loading at a scheduled station we still have to treat the
133 // travelling part of the first order.
134 if (!travelling) {
135 if (!CanDetermineTimeTaken(order, true)) return;
136 sum += order->GetTimetabledTravel();
137 table[i].arrival = sum;
141 struct TimetableWindow : Window
143 int sel_index;
144 const Vehicle* vehicle; //!< Vehicle monitored by the window.
145 bool show_expected; //!< Whether we show expected arrival or scheduled
146 uint deparr_time_width {}; //!< The width of the departure/arrival time
147 uint deparr_abbr_width {}; //!< The width of the departure/arrival abbreviation
148 Scrollbar* vscroll;
149 bool query_is_speed_query {}; //!< The currently open query window is a speed query and not a time query
150 bool query_is_bulk_query {}; //!< The currently open query window applies to all relevant orders.
151 TTSepSettings new_sep_settings; //!< Contains new separation settings.
152 VehicleTimetableWidgets query_widget {}; //!< Required to determinate source of input query
153 int summary_warnings = 0; //!< Number of summary warnings shown
155 TimetableWindow(WindowDesc* desc, WindowNumber window_number) :
156 Window(desc),
157 sel_index(-1),
158 vehicle(Vehicle::Get(window_number)),
159 show_expected(true)
161 this->new_sep_settings = vehicle->GetTimetableSeparationSettings();
162 this->CreateNestedTree();
163 this->vscroll = this->GetScrollbar(WID_VT_SCROLLBAR);
164 this->UpdateSelectionStates();
165 this->FinishInitNested(window_number);
166 this->owner = this->vehicle->owner;
169 ~TimetableWindow()
171 if (!FocusWindowById(WC_VEHICLE_VIEW, this->window_number)) {
172 MarkAllRouteStepsDirty(this->vehicle);
176 TimetableWindow(const TimetableWindow&) = delete;
177 TimetableWindow(TimetableWindow&&) = delete;
178 TimetableWindow& operator=(const TimetableWindow&) = delete;
179 TimetableWindow& operator=(TimetableWindow&&) = delete;
181 //! Build the arrival-departure list for a given vehicle
182 //! @param vehicle the vehicle to make the list for
183 //! @param table the table to fill
184 //! @return if next arrival will be early
185 static bool BuildArrivalDepartureList(const Vehicle* vehicle, TimetableArrivalDeparture* table)
187 assert(HasBit(vehicle->vehicle_flags, VF_TIMETABLE_STARTED));
189 const bool travelling = (!(vehicle->current_order.IsType(OT_LOADING) || vehicle->current_order.IsType(OT_WAITING)) ||
190 vehicle->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
191 Ticks start_time = GetCurrentTickCount() - vehicle->current_order_time;
193 if (vehicle->cur_timetable_order_index != INVALID_VEH_ORDER_ID && vehicle->cur_timetable_order_index != vehicle->cur_real_order_index) {
194 // Vehicle is taking a conditional order branch, adjust start time to compensate.
195 const Order* real_current_order = vehicle->GetOrder(vehicle->cur_real_order_index);
196 const Order* real_timetable_order = vehicle->GetOrder(vehicle->cur_timetable_order_index);
197 assert(real_timetable_order->IsType(OT_CONDITIONAL));
198 start_time += (real_timetable_order->GetWaitTime() - real_current_order->GetTravelTime());
201 FillTimetableArrivalDepartureTable(vehicle, vehicle->cur_real_order_index % vehicle->GetNumOrders(), travelling, table, start_time);
203 return (travelling && vehicle->lateness_counter < 0);
206 void UpdateWidgetSize(int widget, Dimension* size, const Dimension& padding, Dimension* fill, Dimension* resize) override
208 switch (widget) {
209 case WID_VT_ARRIVAL_DEPARTURE_PANEL:
210 SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR, 0, FS_SMALL);
211 this->deparr_time_width = GetStringBoundingBox(STR_JUST_TIME).width;
212 this->deparr_abbr_width = max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION).width);
213 size->width = WD_FRAMERECT_LEFT + this->deparr_abbr_width + 10 + this->deparr_time_width + 10 + WD_FRAMERECT_RIGHT;
214 FALLTHROUGH;
216 case WID_VT_ARRIVAL_DEPARTURE_SELECTION:
217 case WID_VT_TIMETABLE_PANEL:
218 resize->height = FONT_HEIGHT_NORMAL;
219 size->height = WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM;
220 break;
222 case WID_VT_SUMMARY_PANEL: {
223 const Dimension warning_sign_size = GetSpriteSize(SPR_WARNING_SIGN);
224 size->height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + this->summary_warnings * max<int>(warning_sign_size.height, FONT_HEIGHT_NORMAL) + WD_FRAMERECT_BOTTOM;
225 break;
228 default:
229 break;
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;
244 //! Some data on this window has become invalid.
245 //! @param data Information about the changed data.
246 //! @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.
247 void OnInvalidateData(int data, bool gui_scope) override
249 switch (data) {
250 case VIWD_AUTOREPLACE:
251 // Autoreplace replaced the vehicle.
252 this->vehicle = Vehicle::Get(this->window_number);
253 break;
255 case VIWD_REMOVE_ALL_ORDERS:
256 // Removed / replaced all orders (after deleting / sharing).
257 if (this->sel_index == -1) break;
259 this->DeleteChildWindows();
260 this->sel_index = -1;
261 break;
263 case VIWD_MODIFY_ORDERS:
264 if (!gui_scope) break;
265 this->UpdateSelectionStates();
266 this->ReInit();
267 break;
269 default: {
270 if (gui_scope) break; // Only do this once; from command scope
272 this->new_sep_settings = this->vehicle->GetTimetableSeparationSettings();
274 // Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
275 // the order is being created / removed.
276 if (this->sel_index == -1) break;
278 const VehicleOrderID from = GB(data, 0, 8);
279 const VehicleOrderID to = GB(data, 8, 8);
281 if (from == to) break; // No need to change anything
283 // if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
284 const uint old_num_orders = this->vehicle->GetNumOrders() - uint(from == INVALID_VEH_ORDER_ID) + uint(to == INVALID_VEH_ORDER_ID);
285 VehicleOrderID selected_order = (this->sel_index + 1) / 2;
287 if (selected_order == old_num_orders) {
288 // When last travel time is selected, it belongs to order 0
289 selected_order = 0;
292 const bool travel = HasBit(this->sel_index, 0);
294 if (from != selected_order) {
295 // Moving from preceding order?
296 selected_order -= int(from <= selected_order);
297 // Moving to preceding order?
298 selected_order += int(to <= selected_order);
299 } else {
300 // Now we are modifying the selected order.
301 if (to == INVALID_VEH_ORDER_ID) {
302 // Deleting selected order.
303 this->DeleteChildWindows();
304 this->sel_index = -1;
305 break;
308 // Moving selected order.
309 selected_order = to;
312 // Recompute new sel_index
313 this->sel_index = 2 * selected_order - int(travel);
315 // Travel time of first order needs special handling.
316 if (this->sel_index == -1) this->sel_index = this->vehicle->GetNumOrders() * 2 - 1;
317 break;
322 void OnPaint() override
324 const Vehicle* vehicle = this->vehicle;
325 const int selected = this->sel_index;
327 this->vscroll->SetCount(vehicle->GetNumOrders() * 2);
329 if (vehicle->owner == _local_company) {
330 const bool disable = IsActionDisabled(vehicle, selected);
331 const bool disable_speed = disable || selected % 2 != 1 || vehicle->type == VEH_AIRCRAFT;
333 this->SetWidgetDisabledState(WID_VT_CHANGE_TIME, disable);
334 this->SetWidgetDisabledState(WID_VT_CLEAR_TIME, disable);
335 this->SetWidgetDisabledState(WID_VT_CHANGE_SPEED, disable_speed);
336 this->SetWidgetDisabledState(WID_VT_CLEAR_SPEED, disable_speed);
337 this->SetWidgetDisabledState(WID_VT_SHARED_ORDER_LIST, !vehicle->HasSharedOrdersList());
339 this->SetWidgetDisabledState(WID_VT_TTSEP_MODE_DROPDOWN, !vehicle->HasOrdersList());
340 this->SetWidgetDisabledState(WID_VT_CONFIRM_ALL, !vehicle->HasOrdersList());
341 this->SetWidgetDisabledState(WID_VT_RESET_LATENESS, !vehicle->HasOrdersList());
342 this->SetWidgetDisabledState(WID_VT_AUTOMATE, !vehicle->HasOrdersList());
343 } else {
344 this->DisableWidget(WID_VT_CONFIRM_ALL);
345 this->DisableWidget(WID_VT_CHANGE_TIME);
346 this->DisableWidget(WID_VT_CLEAR_TIME);
347 this->DisableWidget(WID_VT_CHANGE_SPEED);
348 this->DisableWidget(WID_VT_CLEAR_SPEED);
349 this->DisableWidget(WID_VT_RESET_LATENESS);
350 this->DisableWidget(WID_VT_AUTOMATE);
351 this->DisableWidget(WID_VT_SHARED_ORDER_LIST);
354 // We can only set parameters if we're in one of the manual modes.
355 const bool enabled_state = (this->new_sep_settings.mode == TTS_MODE_MAN_N) || (this->new_sep_settings.mode == TTS_MODE_MAN_T);
357 this->SetWidgetDisabledState(WID_VT_TTSEP_SET_PARAMETER, !enabled_state);
358 this->SetWidgetLoweredState(WID_VT_AUTOMATE, HasBit(vehicle->vehicle_flags, VF_AUTOMATE_TIMETABLE));
360 this->DrawWidgets();
363 void SetStringParameters(int widget) const override
365 switch (widget) {
366 case WID_VT_CAPTION:
367 SetDParam(0, this->vehicle->index);
368 break;
370 case WID_VT_EXPECTED:
371 SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED);
372 break;
374 case WID_VT_TTSEP_MODE_DROPDOWN:
375 SetDParam(0, TimetableSeparationDropdownOptions[this->new_sep_settings.mode]);
376 break;
378 case WID_VT_TTSEP_SET_PARAMETER:
379 SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? STR_TTSEPARATION_SET_NUM : STR_TTSEPARATION_SET_TIME);
380 break;
382 default:
383 break;
387 void DrawWarnings(const Rect& rect, int& y, const Vehicle* vehicle) const
389 const bool have_missing_times = !vehicle->HasCompleteTimetable();
390 bool have_conditional = false;
391 bool have_bad_full_load = false;
393 const bool is_automated_timetable = HasBit(vehicle->vehicle_flags, VF_AUTOMATE_TIMETABLE);
395 for (int n = 0; n < vehicle->GetNumOrders(); ++n) {
396 const Order* order = vehicle->GetOrder(n);
398 if (order->IsType(OT_CONDITIONAL)) {
399 have_conditional = true;
402 if (!have_bad_full_load && (is_automated_timetable || order->IsWaitTimetabled())) {
403 if (order->GetLoadType() & OLFB_FULL_LOAD) {
404 have_bad_full_load = true;
405 } else if (order->GetLoadType() == OLFB_CARGO_TYPE_LOAD) {
406 for (CargoID c = 0; c < NUM_CARGO; c++) {
407 if (order->GetCargoLoadTypeRaw(c) & OLFB_FULL_LOAD) {
408 have_bad_full_load = true;
409 break;
416 const Dimension warning_dimensions = GetSpriteSize(SPR_WARNING_SIGN);
417 const int step_height = max<int>(warning_dimensions.height, FONT_HEIGHT_NORMAL);
418 const int text_offset_y = (step_height - FONT_HEIGHT_NORMAL) / 2;
419 const int warning_offset_y = (step_height - warning_dimensions.height) / 2;
420 const bool rtl = _current_text_dir == TD_RTL;
422 int warning_count = 0;
424 const auto draw_info = [&](StringID text, bool warning) {
425 int left = rect.left + WD_FRAMERECT_LEFT;
426 int right = rect.right - WD_FRAMERECT_RIGHT;
428 if (warning) {
429 DrawSprite(SPR_WARNING_SIGN, 0, rtl ? right - warning_dimensions.width - 5 : left + 5, y + warning_offset_y);
430 if (rtl) {
431 right -= (warning_dimensions.width + 10);
432 } else {
433 left += (warning_dimensions.width + 10);
437 DrawString(left, right, y + text_offset_y, text);
439 y += step_height;
440 warning_count++;
443 if (this->new_sep_settings.mode != TTS_MODE_OFF) {
444 if (vehicle->GetNumOrders() == 0) {
445 draw_info(STR_TIMETABLE_AUTOSEP_TIMETABLE_INCOMPLETE, false);
446 } else if (have_missing_times) {
447 if (is_automated_timetable) {
448 draw_info(STR_TIMETABLE_AUTOSEP_TIMETABLE_INCOMPLETE, false);
449 } else {
450 draw_info(STR_TIMETABLE_WARNING_AUTOSEP_MISSING_TIMINGS, true);
452 } else {
453 draw_info(vehicle->HasSharedOrdersList() ? STR_TIMETABLE_AUTOSEP_OK : STR_TIMETABLE_AUTOSEP_SINGLE_VEH, !vehicle->HasSharedOrdersList());
456 if (have_conditional) draw_info(STR_TIMETABLE_WARNING_AUTOSEP_CONDITIONAL, true);
457 if (have_bad_full_load) draw_info(STR_TIMETABLE_WARNING_FULL_LOAD, true);
460 if (warning_count != this->summary_warnings) {
461 auto mutable_this = const_cast<TimetableWindow*>(this);
462 mutable_this->summary_warnings = warning_count;
463 mutable_this->ReInit();
467 void DrawWidget(const Rect& rect, int widget) const override
469 const Vehicle* vehicle = this->vehicle;
470 const int selected = this->sel_index;
472 switch (widget) {
473 case WID_VT_TIMETABLE_PANEL: {
474 int y = rect.top + WD_FRAMERECT_TOP;
475 int scroll_position = this->vscroll->GetPosition();
476 VehicleOrderID order_id = (scroll_position + 1) / 2;
477 bool final_order = false;
479 const bool rtl = _current_text_dir == TD_RTL;
480 SetDParamMaxValue(0, vehicle->GetNumOrders(), 2);
481 const int index_column_width = GetStringBoundingBox(STR_ORDER_INDEX).width + 2 *
482 GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + 3;
484 const int middle = rtl ?
485 rect.right - WD_FRAMERECT_RIGHT - index_column_width :
486 rect.left + WD_FRAMERECT_LEFT + index_column_width;
488 const Order* order = vehicle->GetOrder(order_id);
490 while (order != nullptr) {
491 // Don't draw anything if it extends past the end of the window.
492 if (!this->vscroll->IsVisible(scroll_position)) break;
494 if (scroll_position % 2 == 0) {
495 DrawOrderString(vehicle, order, order_id, y, scroll_position == selected, true, rect.left + WD_FRAMERECT_LEFT, middle, rect.right - WD_FRAMERECT_RIGHT);
497 order_id++;
499 if (order_id >= vehicle->GetNumOrders()) {
500 order = vehicle->GetOrder(0);
501 final_order = true;
502 } else {
503 order = order->next;
505 } else {
506 StringID string;
507 TextColour colour = (scroll_position == selected) ? TC_WHITE : TC_BLACK;
508 if (order->IsType(OT_CONDITIONAL)) {
509 string = STR_TIMETABLE_NO_TRAVEL;
510 } else if (order->IsType(OT_IMPLICIT)) {
511 string = STR_TIMETABLE_NOT_TIMETABLEABLE;
512 colour = ((scroll_position == selected) ? TC_SILVER : TC_GREY) | TC_NO_SHADE;
513 } else if (!order->IsTravelTimetabled()) {
514 if (order->GetTravelTime() > 0) {
515 SetTimetableParams(order->GetTravelTime());
516 string = order->GetMaxSpeed() != UINT16_MAX ?
517 STR_TIMETABLE_TRAVEL_FOR_MINUTES_SPEED_ESTIMATED :
518 STR_TIMETABLE_TRAVEL_FOR_MINUTES_ESTIMATED;
519 } else {
520 string = order->GetMaxSpeed() != UINT16_MAX ?
521 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED :
522 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED;
524 } else {
525 SetTimetableParams(order->GetTimetabledTravel());
526 string = order->GetMaxSpeed() != UINT16_MAX ?
527 STR_TIMETABLE_TRAVEL_FOR_MINUTES_SPEED :
528 STR_TIMETABLE_TRAVEL_FOR_MINUTES;
531 SetDParam(3, order->GetMaxSpeed());
532 DrawString(rtl ? rect.left + WD_FRAMERECT_LEFT : middle, rtl ? middle : rect.right - WD_FRAMERECT_LEFT, y, string, colour);
534 if (final_order) break;
537 scroll_position++;
538 y += FONT_HEIGHT_NORMAL;
540 break;
543 case WID_VT_ARRIVAL_DEPARTURE_PANEL: {
544 // Arrival and departure times are handled in an all-or-nothing approach,
545 // i.e. are only shown if we can calculate all times.
546 // Excluding order lists with only one order makes some things easier.
547 const Ticks total_time = vehicle->GetTimetableDurationIncomplete();
549 if (total_time <= 0 || vehicle->GetNumOrders() <= 1 || !HasBit(vehicle->vehicle_flags, VF_TIMETABLE_STARTED)) break;
551 TimetableArrivalDeparture* arr_dep = AllocaM(TimetableArrivalDeparture, vehicle->GetNumOrders());
552 const VehicleOrderID cur_order = vehicle->cur_real_order_index % vehicle->GetNumOrders();
554 const VehicleOrderID earlyID = BuildArrivalDepartureList(vehicle, arr_dep) ?
555 cur_order :
556 static_cast<VehicleOrderID>(INVALID_VEH_ORDER_ID);
558 int y = rect.top + WD_FRAMERECT_TOP;
560 bool show_late = this->show_expected && vehicle->lateness_counter > _settings_client.gui.ticks_per_minute;
561 const Ticks offset = show_late ? 0 : -vehicle->lateness_counter;
563 if (HasBit(vehicle->vehicle_flags, VF_SEPARATION_IN_PROGRESS)) {
564 show_late = false;
567 const bool rtl = _current_text_dir == TD_RTL;
568 const int abbr_left = rtl ? rect.right - WD_FRAMERECT_RIGHT - this->deparr_abbr_width : rect.left + WD_FRAMERECT_LEFT;
569 const int abbr_right = rtl ? rect.right - WD_FRAMERECT_RIGHT : rect.left + WD_FRAMERECT_LEFT + this->deparr_abbr_width;
570 const int time_left = rtl ? rect.right - WD_FRAMERECT_RIGHT - rect.left - WD_FRAMERECT_RIGHT - this->deparr_abbr_width - 10 - this->deparr_time_width : rect.left + WD_FRAMERECT_RIGHT + this->deparr_abbr_width + 10;
571 const int time_right = rtl ? rect.right - WD_FRAMERECT_RIGHT - this->deparr_abbr_width - 10 : rect.left + WD_FRAMERECT_RIGHT + rect.left + WD_FRAMERECT_RIGHT + this->deparr_abbr_width + 10 + this->deparr_time_width;
573 for (int i = this->vscroll->GetPosition(); i / 2 < vehicle->GetNumOrders(); ++i) {
574 // note: i is also incremented in the loop
575 // Don't draw anything if it extends past the end of the window.
576 if (!this->vscroll->IsVisible(i)) break;
578 if (i % 2 == 0) {
579 if (arr_dep[i / 2].arrival != INVALID_TICKS) {
580 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_ARRIVAL_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
581 if (this->show_expected && i / 2 == earlyID) {
582 SetDParam(0, arr_dep[i / 2].arrival);
583 DrawString(time_left, time_right, y, STR_JUST_TIME, TC_GREEN);
584 } else {
585 SetDParam(0, arr_dep[i / 2].arrival + offset);
586 DrawString(time_left, time_right, y, STR_JUST_TIME, show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
589 } else {
590 if (arr_dep[i / 2].departure != INVALID_TICKS) {
591 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_DEPARTURE_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
592 SetDParam(0, arr_dep[i / 2].departure + offset);
593 DrawString(time_left, time_right, y, STR_JUST_TIME,
594 show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
598 y += FONT_HEIGHT_NORMAL;
600 break;
603 case WID_VT_SUMMARY_PANEL: {
604 int y = rect.top + WD_FRAMERECT_TOP;
605 const Ticks total_time = vehicle->GetTimetableDurationIncomplete();
607 if (total_time != 0) {
608 SetTimetableParams(total_time);
609 DrawString(rect.left + WD_FRAMERECT_LEFT, rect.right - WD_FRAMERECT_RIGHT, y, vehicle->HasCompleteTimetable() ? STR_TIMETABLE_TOTAL_TIME_MINUTES : STR_TIMETABLE_TOTAL_TIME_MINUTES_INCOMPLETE);
612 y += FONT_HEIGHT_NORMAL;
614 const auto lateness_counter = HasBit(vehicle->vehicle_flags, VF_SEPARATION_IN_PROGRESS) ?
616 vehicle->lateness_counter;
618 if (vehicle->timetable_start != 0) {
619 // We are running towards the first station so we can start the
620 // timetable at the given time.
621 SetDParam(0, STR_JUST_DATE);
622 SetDParam(1, vehicle->timetable_start);
623 DrawString(rect.left + WD_FRAMERECT_LEFT, rect.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_START_AT);
624 } else if (!HasBit(vehicle->vehicle_flags, VF_TIMETABLE_STARTED)) {
625 // We aren't running on a timetable yet, so how can we be "on time"
626 // when we aren't even "on service"/"on duty"?
627 DrawString(rect.left + WD_FRAMERECT_LEFT, rect.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_NOT_STARTED);
628 } else if (lateness_counter == 0) {
629 DrawString(rect.left + WD_FRAMERECT_LEFT, rect.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_ON_TIME);
630 } else {
631 SetTimetableParams(abs(lateness_counter));
632 DrawString(rect.left + WD_FRAMERECT_LEFT, rect.right - WD_FRAMERECT_RIGHT, y, lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY_MINUTES : STR_TIMETABLE_STATUS_LATE_MINUTES);
635 y += FONT_HEIGHT_NORMAL;
637 DrawWarnings(rect, y, vehicle);
638 break;
641 case WID_VT_TTSEP_PANEL_TEXT: {
642 // Represents the current vertical position.
643 int y = rect.top + WD_FRAMERECT_TOP;
645 // Represents the left border of the separation display frame.
646 const int left_border = rect.left + WD_FRAMERECT_LEFT;
648 // Represents the right border of the separation display frame.
649 const int right_border = rect.right - WD_FRAMERECT_RIGHT;
651 // If separation is inactive, we can stop here.
652 if (!_settings_game.order.automatic_timetable_separation || !this->vehicle->HasOrdersList()) break;
654 const bool is_off = this->new_sep_settings.mode == TTS_MODE_OFF;
655 const bool is_auto = this->new_sep_settings.mode == TTS_MODE_AUTO ||
656 this->new_sep_settings.mode == TTS_MODE_BUFFERED_AUTO;
657 const bool is_manual_number = this->new_sep_settings.mode == TTS_MODE_MAN_N;
658 const bool is_manual_time = this->new_sep_settings.mode == TTS_MODE_MAN_T;
660 const bool is_complete = this->vehicle->HasCompleteTimetable();
661 const bool is_valid = this->vehicle->IsTimetableSeparationValid();
663 if (!is_off && (!is_auto || (is_complete && is_valid))) {
664 uint64 par;
666 // If separation hasn't just been switched off, we need to draw various description lines.
667 // The first line is the amount of separation which is either saved in the struct or must
668 // be calculated on the fly.
669 if (is_manual_time || is_auto) {
670 par = this->new_sep_settings.sep_ticks;
671 } else {
672 par = this->vehicle->GetTimetableTotalDuration() / max(1u, this->new_sep_settings.num_veh);
675 if (!is_manual_number && (is_manual_time || (is_auto && (is_complete && is_valid)))) {
676 SetDParam(0, par);
677 SetDParam(1, par);
678 DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC, TC_BLACK);
679 y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC).height;
682 if (!is_off && is_auto && (is_complete && is_valid)) {
683 DrawString(left_border, right_border, y, STR_TTSEPARATION_BETWEEN, TC_BLACK);
684 y += GetStringBoundingBox(STR_TTSEPARATION_BETWEEN).height;
687 par = this->new_sep_settings.num_veh;
689 if (is_manual_number || (is_auto && (is_complete && is_valid))) {
690 SetDParam(0, par);
691 DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_NUM_DESC, TC_BLACK);
692 y += GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height;
696 // If separation is switched on at all...
697 if (this->vehicle->IsTimetableSeparationOn()) {
698 if (!is_complete) {
699 SetDParam(0, STR_TTSEPARATION_STATUS_WAITING_FOR_TIMETABLE);
700 } else if (!this->vehicle->HasSharedOrdersList()) {
701 SetDParam(0, STR_TTSEPARATION_STATUS_WAITING_FOR_VEHICLES);
702 } else {
703 // ... set displayed status to either "Running" or "Initializing"
704 SetDParam(0, (this->vehicle->IsTimetableSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING : STR_TTSEPARATION_STATUS_INIT);
706 } else {
707 // If separation is switched off, show this instead.
708 SetDParam(0, STR_TTSEPARATION_STATUS_OFF);
711 y += FONT_HEIGHT_NORMAL;
713 // Print status description.
714 DrawStringMultiLine(left_border, right_border, y, rect.bottom - WD_FRAMERECT_BOTTOM, STR_TTSEPARATION_STATUS_DESC);
717 default:
718 break;
722 static uint32 PackTimetableArgs(const Vehicle* v, uint selected, bool speed)
724 uint order_number = (selected + 1) / 2;
725 ModifyTimetableFlags mtf = (selected % 2 == 1) ? (speed ? MTF_TRAVEL_SPEED : MTF_TRAVEL_TIME) : MTF_WAIT_TIME;
727 if (order_number >= v->GetNumOrders()) order_number = 0;
729 return v->index | (order_number << 20) | (mtf << 28);
732 static bool IsActionDisabled(const Vehicle* vehicle, int selected)
734 bool disabled = true;
735 if (selected != -1) {
736 const Order* order = vehicle->GetOrder(((selected + 1) / 2) % vehicle->GetNumOrders());
737 if (selected % 2 == 1) {
738 disabled = order != nullptr && (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT));
739 } else {
740 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));
743 return disabled;
746 void OnClick(Point point, int widget, int click_count) override
748 const Vehicle* v = this->vehicle;
750 this->DeleteChildWindows(WC_QUERY_STRING);
752 switch (widget) {
753 case WID_VT_ORDER_VIEW:
754 // Order view button
755 ShowOrdersWindow(v);
756 break;
758 case WID_VT_TIMETABLE_PANEL: {
759 // Main panel.
760 const int selected = GetOrderFromTimetableWndPt(point.y, v);
762 // Allow change time by double-clicking order.
763 if (click_count == 2) {
764 this->sel_index = selected == INVALID_ORDER ? -1 : selected;
765 this->OnClick(point, WID_VT_CHANGE_TIME, click_count);
766 return;
767 } else {
768 this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
771 this->DeleteChildWindows();
772 break;
775 case WID_VT_CONFIRM_ALL: {
776 // Confirm all estimated times as timetabled.
777 DoCommandP(0, v->index, 0, CMD_CONFIRM_ALL | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
778 break;
781 case WID_VT_CHANGE_TIME: {
782 // "Wait For" button.
783 const int selected = this->sel_index;
784 VehicleOrderID real = (selected + 1) / 2;
786 if (real >= v->GetNumOrders()) real = 0;
788 const Order* order = v->GetOrder(real);
789 StringID current = STR_EMPTY;
791 if (order != nullptr) {
792 const uint time = (selected % 2 == 1) ? order->GetTravelTime() : order->GetWaitTime();
794 if (time != 0) {
795 SetDParam(0, time);
796 current = STR_JUST_INT;
800 this->query_widget = WID_VT_CHANGE_TIME;
801 this->query_is_speed_query = false;
802 this->query_is_bulk_query = _ctrl_pressed;
803 ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
804 break;
807 case WID_VT_CHANGE_SPEED: {
808 // Change max speed button.
809 const int selected = this->sel_index;
810 VehicleOrderID real = (selected + 1) / 2;
812 if (real >= v->GetNumOrders()) real = 0;
814 StringID current = STR_EMPTY;
815 const Order* order = v->GetOrder(real);
816 if (order != nullptr) {
817 if (order->GetMaxSpeed() != UINT16_MAX) {
818 SetDParam(0, ConvertKmhishSpeedToDisplaySpeed(order->GetMaxSpeed()));
819 current = STR_JUST_INT;
823 this->query_widget = WID_VT_CHANGE_SPEED;
824 this->query_is_speed_query = true;
825 this->query_is_bulk_query = _ctrl_pressed;
826 ShowQueryString(current, STR_TIMETABLE_CHANGE_SPEED, 31, this, CS_NUMERAL, QSF_NONE);
827 break;
830 case WID_VT_CLEAR_TIME: {
831 // Clear waiting time.
832 const uint32 p1 = PackTimetableArgs(v, this->sel_index, false);
833 DoCommandP(0, p1, 0, (_ctrl_pressed ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
834 break;
837 case WID_VT_CLEAR_SPEED: {
838 // Clear max speed button.
839 const uint32 p1 = PackTimetableArgs(v, this->sel_index, true);
840 DoCommandP(0, p1, UINT16_MAX, (_ctrl_pressed ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
841 break;
844 case WID_VT_RESET_LATENESS:
845 // Reset the vehicle's late counter.
846 DoCommandP(0, v->index, 0, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
847 break;
849 case WID_VT_AUTOMATE: {
850 // Automate the timetable.
851 uint32 p2 = 0;
852 if (!HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) SetBit(p2, 0);
853 if (_ctrl_pressed) SetBit(p2, 1);
854 DoCommandP(0, v->index, p2, CMD_AUTOMATE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
855 break;
858 case WID_VT_EXPECTED:
859 this->show_expected = !this->show_expected;
860 break;
862 case WID_VT_SHARED_ORDER_LIST:
863 ShowVehicleListWindow(v);
864 break;
866 case WID_VT_TTSEP_MODE_DROPDOWN: {
867 ShowDropDownMenu(this, TimetableSeparationDropdownOptions, this->new_sep_settings.mode, WID_VT_TTSEP_MODE_DROPDOWN, 0, 0);
868 break;
871 case WID_VT_TTSEP_SET_PARAMETER: {
872 this->query_widget = WID_VT_TTSEP_SET_PARAMETER;
873 SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? this->new_sep_settings.num_veh : this->new_sep_settings.sep_ticks);
874 ShowQueryString(STR_JUST_INT, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE);
875 break;
878 default:
879 break;
882 this->SetDirty();
885 void UpdateVehicleSeparationSettings()
887 uint32 p2 = GB<uint32>(this->new_sep_settings.mode, 0, 3);
888 AB<uint32, uint>(p2, 3, 29, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ?
889 this->new_sep_settings.num_veh :
890 this->new_sep_settings.sep_ticks);
892 DoCommandP(0, this->vehicle->FirstShared()->index, p2, CMD_REINIT_SEPARATION);
895 void OnDropdownSelect(int widget, int index) override
897 assert(widget == WID_VT_TTSEP_MODE_DROPDOWN);
899 this->new_sep_settings = this->vehicle->GetTimetableSeparationSettings();
900 this->new_sep_settings.mode = static_cast<TTSepMode>(index);
902 UpdateVehicleSeparationSettings();
904 this->InvalidateData();
907 void OnQueryTextFinished(char* str) override
909 if (str == nullptr || StrEmpty(str)) return;
911 switch (this->query_widget) {
912 case WID_VT_CHANGE_TIME:
913 case WID_VT_CHANGE_SPEED: {
914 const Vehicle* vehicle = this->vehicle;
916 const uint32 p1 = PackTimetableArgs(vehicle, this->sel_index, this->query_is_speed_query);
918 uint64 speed = StrEmpty(str) ? 0 : strtoul(str, nullptr, 10);
919 if (this->query_is_speed_query) {
920 speed = ConvertDisplaySpeedToKmhishSpeed(speed);
923 const uint32 p2 = minu(speed, UINT16_MAX);
925 DoCommandP(0, p1, p2, (this->query_is_bulk_query ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
926 break;
929 case WID_VT_TTSEP_SET_PARAMETER: {
930 const int value = strtol(str, nullptr, 10);
932 switch (this->new_sep_settings.mode) {
933 case TTS_MODE_AUTO:
934 case TTS_MODE_BUFFERED_AUTO:
935 case TTS_MODE_OFF:
936 break;
938 case TTS_MODE_MAN_N:
939 this->new_sep_settings.num_veh = Clamp(value, 1, 65535);
940 break;
942 case TTS_MODE_MAN_T:
943 this->new_sep_settings.sep_ticks = Clamp(value, 1, 65535);
944 break;
946 default:
947 NOT_REACHED();
950 UpdateVehicleSeparationSettings();
951 this->InvalidateData();
952 break;
955 default:
956 NOT_REACHED();
960 void OnResize() override
962 // Update the scroll bar.
963 this->vscroll->SetCapacityFromWidget(this, WID_VT_TIMETABLE_PANEL, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
966 //! Update the selection state of the arrival/departure data
967 void UpdateSelectionStates()
969 this->GetWidget<NWidgetStacked>(WID_VT_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : SZSP_NONE);
970 this->GetWidget<NWidgetStacked>(WID_VT_EXPECTED_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : 1);
973 void OnFocus(Window* previously_focused_window) override
975 if (HasFocusedVehicleChanged(this->window_number, previously_focused_window)) {
976 MarkAllRoutePathsDirty(this->vehicle);
977 MarkAllRouteStepsDirty(this->vehicle);
981 void OnFocusLost(Window* newly_focused_window) override
983 if (HasFocusedVehicleChanged(this->window_number, newly_focused_window)) {
984 MarkAllRoutePathsDirty(this->vehicle);
985 MarkAllRouteStepsDirty(this->vehicle);
989 const Vehicle* GetVehicle() const
991 return this->vehicle;
995 static const NWidgetPart _nested_timetable_widgets[] = {
996 NWidget(NWID_HORIZONTAL),
997 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
998 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VT_CAPTION), SetDataTip(STR_TIMETABLE_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
999 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_ORDER_VIEW), SetMinimalSize(61, 14), SetDataTip( STR_TIMETABLE_ORDER_VIEW, STR_TIMETABLE_ORDER_VIEW_TOOLTIP),
1000 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1001 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1002 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1003 EndContainer(),
1004 NWidget(NWID_HORIZONTAL),
1005 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(),
1006 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_ARRIVAL_DEPARTURE_SELECTION),
1007 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(),
1008 EndContainer(),
1009 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VT_SCROLLBAR),
1010 NWidget(WWT_PANEL, COLOUR_GREY),
1011 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_TTSEPARATION_SETTINGS_DESC, STR_NULL), SetPadding(3),
1012 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VT_TTSEP_MODE_DROPDOWN), SetDataTip(STR_JUST_STRING, STR_TIMETABLE_TOOLTIP),
1013 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_TTSEP_SET_PARAMETER), SetFill(1, 0), SetDataTip(STR_TTSEPARATION_SET_XX, STR_TIMETABLE_TOOLTIP),
1014 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_TTSEP_PANEL_TEXT), SetFill(1, 1), SetResize(0, 1), SetMinimalSize(225, 44), EndContainer(),
1015 EndContainer(),
1016 EndContainer(),
1017 EndContainer(),
1018 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
1019 NWidget(NWID_HORIZONTAL),
1020 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1021 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1022 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),
1023 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),
1024 EndContainer(),
1025 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1026 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),
1027 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),
1028 EndContainer(),
1029 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1030 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),
1031 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),
1032 EndContainer(),
1033 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1034 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_AUTOMATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOMATE, STR_TIMETABLE_AUTOMATE_TOOLTIP),
1035 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_EXPECTED_SELECTION),
1036 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_EXPECTED), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
1037 NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
1038 EndContainer(),
1039 EndContainer(),
1040 EndContainer(),
1041 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1042 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),
1043 NWidget(WWT_RESIZEBOX, COLOUR_GREY), SetFill(0, 1),
1044 EndContainer(),
1045 EndContainer(),
1048 static WindowDesc _timetable_desc(
1049 WDP_AUTO, "view_vehicle_timetable", 400, 130,
1050 WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW,
1051 WDF_CONSTRUCTION,
1052 _nested_timetable_widgets, lengthof(_nested_timetable_widgets)
1055 //! Show the timetable for a given vehicle.
1056 //! @param vehicle The vehicle to show the timetable for.
1057 void ShowTimetableWindow(const Vehicle* vehicle)
1059 DeleteWindowById(WC_VEHICLE_DETAILS, vehicle->index, false);
1060 DeleteWindowById(WC_VEHICLE_ORDERS, vehicle->index, false);
1061 AllocateWindowDescFront<TimetableWindow>(&_timetable_desc, vehicle->index);