Show snow on depot tiles. (Looks weird my ass)
[openttd-joker.git] / src / timetable_gui.cpp
blobe613ae9c45d183824b9fdfc726d394c6534a3f09
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"
13 #include "command_func.h"
14 #include "gui.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"
21 #include "gfx_func.h"
22 #include "company_func.h"
23 #include "date_func.h"
24 #include "date_gui.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,
40 STR_TTSEPARATION_OFF,
41 STR_TTSEPARATION_MAN_TIME,
42 STR_TTSEPARATION_MAN_NUM,
43 STR_TTSEPARATION_BUFFERED_AUTO,
44 INVALID_STRING_ID,
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
55 /**
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);
67 /**
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)) {
82 return false;
85 return true;
89 /**
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());
103 Ticks sum = offset;
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 */
114 do {
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;
130 ++i;
131 order = order->next;
132 if (i >= v->GetNumOrders()) {
133 i = 0;
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. */
141 if (!travelling) {
142 if (!CanDetermineTimeTaken(order, true)) return;
143 sum += order->GetTimetabledTravel();
144 table[i].arrival = sum;
149 struct TimetableWindow : Window {
150 int sel_index;
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
155 Scrollbar *vscroll;
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) :
163 Window(desc),
164 sel_index(-1),
165 vehicle(Vehicle::Get(window_number)),
166 show_expected(true)
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;
177 ~TimetableWindow()
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)
211 switch (widget) {
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;
217 FALLTHROUGH;
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;
223 break;
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;
228 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;
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();
253 switch (data) {
254 case VIWD_AUTOREPLACE:
255 /* Autoreplace replaced the vehicle */
256 this->vehicle = Vehicle::Get(this->window_number);
257 break;
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;
265 break;
267 case VIWD_MODIFY_ORDERS:
268 if (!gui_scope) break;
269 this->UpdateSelectionStates();
270 this->ReInit();
271 break;
273 default: {
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);
298 } else {
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;
304 break;
305 } else {
306 /* Moving selected order */
307 selected_order = to;
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;
315 break;
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());
341 } else {
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));
359 this->DrawWidgets();
362 virtual void SetStringParameters(int widget) const
364 switch (widget) {
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;
395 break;
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;
414 if (warning) {
415 DrawSprite(SPR_WARNING_SIGN, 0, rtl ? right - warning_dimensions.width - 5 : left + 5, y + warning_offset_y);
416 if (rtl) {
417 right -= (warning_dimensions.width + 10);
419 else {
420 left += (warning_dimensions.width + 10);
424 DrawString(left, right, y + text_offset_y, text);
426 y += step_height;
427 warning_count++;
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);
438 else {
439 draw_info(STR_TIMETABLE_WARNING_AUTOSEP_MISSING_TIMINGS, true);
442 else {
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;
462 switch (widget) {
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;
479 if (i % 2 == 0) {
480 DrawOrderString(v, order, order_id, y, i == selected, true, r.left + WD_FRAMERECT_LEFT, middle, r.right - WD_FRAMERECT_RIGHT);
482 order_id++;
484 if (order_id >= v->GetNumOrders()) {
485 order = v->GetOrder(0);
486 final_order = true;
487 } else {
488 order = order->next;
490 } else {
491 StringID string;
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;
504 } else {
505 string = order->GetMaxSpeed() != UINT16_MAX ?
506 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED :
507 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED;
509 } else {
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;
521 i++;
522 y += FONT_HEIGHT_NORMAL;
524 break;
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)) {
546 show_late = false;
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;
559 if (i % 2 == 0) {
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);
565 } else {
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);
571 else {
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;
581 break;
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);
611 else {
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);
618 break;
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())
628 break;
630 if (this->new_sep_settings.mode != TTS_MODE_OFF &&
631 this->vehicle->HasCompleteTimetable() &&
632 this->vehicle->IsTimetableSeparationValid()) {
633 uint64 par;
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;
643 else {
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())) {
650 SetDParam(0, par);
651 SetDParam(1, par);
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;
658 else {
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;
669 else {
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())) {
675 SetDParam(0, par);
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);
690 else {
691 /* ... set displayed status to either "Running" or "Initializing" */
692 SetDParam(0, (this->vehicle->IsTimetableSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING : STR_TTSEPARATION_STATUS_INIT);
695 else {
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));
726 else {
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));
730 return disabled;
733 virtual void OnClick(Point pt, int widget, int click_count)
735 const Vehicle *v = this->vehicle;
737 this->DeleteChildWindows(WC_QUERY_STRING);
739 switch (widget) {
740 case WID_VT_ORDER_VIEW: // Order view button
741 ShowOrdersWindow(v);
742 break;
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);
751 return;
752 } else {
753 this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
756 this->DeleteChildWindows();
757 break;
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));
762 break;
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();
777 if (time != 0) {
778 SetDParam(0, time);
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);
787 break;
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);
809 break;
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));
815 break;
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));
821 break;
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));
826 break;
828 case WID_VT_AUTOMATE: { // Automate the timetable.
829 uint32 p2 = 0;
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));
833 break;
836 case WID_VT_EXPECTED:
837 this->show_expected = !this->show_expected;
838 break;
840 case WID_VT_SHARED_ORDER_LIST:
841 ShowVehicleListWindow(v);
842 break;
844 case WID_VT_TTSEP_MODE_DROPDOWN: {
845 ShowDropDownMenu(this, TimetableSeparationDropdownOptions, this->new_sep_settings.mode, WID_VT_TTSEP_MODE_DROPDOWN, 0, 0);
846 break;
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);
853 break;
857 this->SetDirty();
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))
874 return;
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));
891 break;
893 case WID_VT_TTSEP_SET_PARAMETER: {
894 int value = atoi(str);
896 switch (this->new_sep_settings.mode)
898 case TTS_MODE_AUTO:
899 case TTS_MODE_BUFFERED_AUTO:
900 case TTS_MODE_OFF:
901 break;
903 case TTS_MODE_MAN_N:
904 this->new_sep_settings.num_veh = Clamp(value, 1, 65535);
905 break;
907 case TTS_MODE_MAN_T:
908 this->new_sep_settings.sep_ticks = Clamp(value, 1, 65535);
909 break;
911 default:
912 NOT_REACHED();
913 break;
916 this->vehicle->SetTimetableSeparationSettings(this->new_sep_settings);
917 this->InvalidateData();
918 break;
920 default:
921 NOT_REACHED();
922 break;
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),
971 EndContainer(),
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(),
976 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(),
983 EndContainer(),
984 EndContainer(),
985 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),
992 EndContainer(),
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),
996 EndContainer(),
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),
1000 EndContainer(),
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(),
1006 EndContainer(),
1007 EndContainer(),
1008 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),
1012 EndContainer(),
1013 EndContainer(),
1016 static WindowDesc _timetable_desc(
1017 WDP_AUTO, "view_vehicle_timetable", 400, 130,
1018 WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW,
1019 WDF_CONSTRUCTION,
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);