GUIs no longer show a vehicle as early if the earliness is due to a late counter...
[openttd-joker.git] / src / timetable_gui.cpp
blobfe07953d16cb8384d9c7c56d1bf592f8a58262ff
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"
29 #include "widgets/timetable_widget.h"
31 #include "table/sprites.h"
32 #include "table/strings.h"
33 #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 #include "safeguards.h"
48 /** Container for the arrival/departure dates of a vehicle */
49 struct TimetableArrivalDeparture {
50 Ticks arrival; ///< The arrival time
51 Ticks departure; ///< The departure time
54 /**
55 * Set the timetable parameters in the format as described by the setting.
56 * @param param1 the first DParam to fill
57 * @param param2 the second DParam to fill
58 * @param ticks the number of ticks to 'draw'
60 void SetTimetableParams(int param1, int param2, Ticks ticks)
62 SetDParam(param1, STR_TIMETABLE_TICKS);
63 SetDParam(param2, ticks);
66 /**
67 * Check whether it is possible to determine how long the order takes.
68 * @param order the order to check.
69 * @param travelling whether we are interested in the travel or the wait part.
70 * @return true if the travel/wait time can be used.
72 static bool CanDetermineTimeTaken(const Order *order, bool travelling)
74 /* Current order is conditional */
75 if (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT)) return false;
76 /* No travel time and we have not already finished travelling */
77 if (travelling && !order->IsTravelTimetabled()) return false;
78 /* No wait time but we are loading at this timetabled station */
79 if (!travelling && !order->IsWaitTimetabled() && order->IsType(OT_GOTO_STATION) &&
80 !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) {
81 return false;
84 return true;
88 /**
89 * Fill the table with arrivals and departures
90 * @param v Vehicle which must have at least 2 orders.
91 * @param start order index to start at
92 * @param travelling Are we still in the travelling part of the start order
93 * @param table Fill in arrival and departures including intermediate orders
94 * @param offset Add this value to result and all arrivals and departures
96 static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID start, bool travelling, TimetableArrivalDeparture *table, Ticks offset)
98 assert(table != NULL);
99 assert(v->GetNumOrders() >= 2);
100 assert(start < v->GetNumOrders());
102 Ticks sum = offset;
103 VehicleOrderID i = start;
104 const Order *order = v->GetOrder(i);
106 /* Pre-initialize with unknown time */
107 for (int i = 0; i < v->GetNumOrders(); ++i) {
108 table[i].arrival = table[i].departure = INVALID_TICKS;
111 /* Cyclically loop over all orders until we reach the current one again.
112 * As we may start at the current order, do a post-checking loop */
113 do {
114 /* Automatic orders don't influence the overall timetable;
115 * they just add some untimetabled entries, but the time till
116 * the next non-implicit order can still be known. */
117 if (!order->IsType(OT_IMPLICIT)) {
118 if (travelling || i != start) {
119 if (!CanDetermineTimeTaken(order, true)) return;
120 sum += order->GetTimetabledTravel();
121 table[i].arrival = sum;
124 if (!CanDetermineTimeTaken(order, false)) return;
125 sum += order->GetTimetabledWait();
126 table[i].departure = sum;
129 ++i;
130 order = order->next;
131 if (i >= v->GetNumOrders()) {
132 i = 0;
133 assert(order == NULL);
134 order = v->GetFirstOrder();
136 } while (i != start);
138 /* When loading at a scheduled station we still have to treat the
139 * travelling part of the first order. */
140 if (!travelling) {
141 if (!CanDetermineTimeTaken(order, true)) return;
142 sum += order->GetTimetabledTravel();
143 table[i].arrival = sum;
148 struct TimetableWindow : Window {
149 int sel_index;
150 const Vehicle *vehicle; ///< Vehicle monitored by the window.
151 bool show_expected; ///< Whether we show expected arrival or scheduled
152 uint deparr_time_width; ///< The width of the departure/arrival time
153 uint deparr_abbr_width; ///< The width of the departure/arrival abbreviation
154 Scrollbar *vscroll;
155 bool query_is_speed_query; ///< The currently open query window is a speed query and not a time query
156 bool query_is_bulk_query; ///< The currently open query window applies to all relevant orders.
157 TTSepSettings new_sep_settings; ///< Contains new separation settings.
158 VehicleTimetableWidgets query_widget; ///< Required to determinate source of input query
160 TimetableWindow(WindowDesc *desc, WindowNumber window_number) :
161 Window(desc),
162 sel_index(-1),
163 vehicle(Vehicle::Get(window_number)),
164 show_expected(true)
166 this->new_sep_settings = vehicle->GetTimetableSeparationSettings();
167 this->CreateNestedTree();
168 this->vscroll = this->GetScrollbar(WID_VT_SCROLLBAR);
169 this->UpdateSelectionStates();
170 this->FinishInitNested(window_number);
172 this->owner = this->vehicle->owner;
175 ~TimetableWindow()
177 if (!FocusWindowById(WC_VEHICLE_VIEW, this->window_number)) {
178 MarkAllRouteStepsDirty(this->vehicle);
183 * Build the arrival-departure list for a given vehicle
184 * @param v the vehicle to make the list for
185 * @param table the table to fill
186 * @return if next arrival will be early
188 static bool BuildArrivalDepartureList(const Vehicle *v, TimetableArrivalDeparture *table)
190 assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED));
192 bool travelling = (!(v->current_order.IsType(OT_LOADING) || v->current_order.IsType(OT_WAITING)) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
193 Ticks start_time = GetCurrentTickCount() - v->current_order_time;
195 FillTimetableArrivalDepartureTable(v, v->cur_real_order_index % v->GetNumOrders(), travelling, table, start_time);
197 return (travelling && v->lateness_counter < 0);
200 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
202 switch (widget) {
203 case WID_VT_ARRIVAL_DEPARTURE_PANEL:
204 SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR, 0, FS_SMALL);
205 this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_TINY).width;
206 this->deparr_abbr_width = max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION).width);
207 size->width = WD_FRAMERECT_LEFT + this->deparr_abbr_width + 10 + this->deparr_time_width + WD_FRAMERECT_RIGHT;
208 FALLTHROUGH;
210 case WID_VT_ARRIVAL_DEPARTURE_SELECTION:
211 case WID_VT_TIMETABLE_PANEL:
212 resize->height = FONT_HEIGHT_NORMAL;
213 size->height = WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM;
214 break;
216 case WID_VT_SUMMARY_PANEL:
217 size->height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
218 break;
222 int GetOrderFromTimetableWndPt(int y, const Vehicle *v)
224 int sel = (y - this->GetWidget<NWidgetBase>(WID_VT_TIMETABLE_PANEL)->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
226 if ((uint)sel >= this->vscroll->GetCapacity()) return INVALID_ORDER;
228 sel += this->vscroll->GetPosition();
230 return (sel < v->GetNumOrders() * 2 && sel >= 0) ? sel : INVALID_ORDER;
234 * Some data on this window has become invalid.
235 * @param data Information about the changed data.
236 * @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.
238 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
240 this->new_sep_settings = vehicle->GetTimetableSeparationSettings();
242 switch (data) {
243 case VIWD_AUTOREPLACE:
244 /* Autoreplace replaced the vehicle */
245 this->vehicle = Vehicle::Get(this->window_number);
246 break;
248 case VIWD_REMOVE_ALL_ORDERS:
249 /* Removed / replaced all orders (after deleting / sharing) */
250 if (this->sel_index == -1) break;
252 this->DeleteChildWindows();
253 this->sel_index = -1;
254 break;
256 case VIWD_MODIFY_ORDERS:
257 if (!gui_scope) break;
258 this->UpdateSelectionStates();
259 this->ReInit();
260 break;
262 default: {
263 if (gui_scope) break; // only do this once; from command scope
265 /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
266 * the order is being created / removed */
267 if (this->sel_index == -1) break;
269 VehicleOrderID from = GB(data, 0, 8);
270 VehicleOrderID to = GB(data, 8, 8);
272 if (from == to) break; // no need to change anything
274 /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
275 uint old_num_orders = this->vehicle->GetNumOrders() - (uint)(from == INVALID_VEH_ORDER_ID) + (uint)(to == INVALID_VEH_ORDER_ID);
277 VehicleOrderID selected_order = (this->sel_index + 1) / 2;
278 if (selected_order == old_num_orders) selected_order = 0; // when last travel time is selected, it belongs to order 0
280 bool travel = HasBit(this->sel_index, 0);
282 if (from != selected_order) {
283 /* Moving from preceding order? */
284 selected_order -= (int)(from <= selected_order);
285 /* Moving to preceding order? */
286 selected_order += (int)(to <= selected_order);
287 } else {
288 /* Now we are modifying the selected order */
289 if (to == INVALID_VEH_ORDER_ID) {
290 /* Deleting selected order */
291 this->DeleteChildWindows();
292 this->sel_index = -1;
293 break;
294 } else {
295 /* Moving selected order */
296 selected_order = to;
300 /* recompute new sel_index */
301 this->sel_index = 2 * selected_order - (int)travel;
302 /* travel time of first order needs special handling */
303 if (this->sel_index == -1) this->sel_index = this->vehicle->GetNumOrders() * 2 - 1;
304 break;
310 virtual void OnPaint()
312 const Vehicle *v = this->vehicle;
313 int selected = this->sel_index;
315 this->vscroll->SetCount(v->GetNumOrders() * 2);
317 if (v->owner == _local_company) {
318 bool disable = IsActionDisabled(v, selected);
319 bool disable_speed = disable || selected % 2 != 1 || v->type == VEH_AIRCRAFT;
321 this->SetWidgetDisabledState(WID_VT_CHANGE_TIME, disable);
322 this->SetWidgetDisabledState(WID_VT_CLEAR_TIME, disable);
323 this->SetWidgetDisabledState(WID_VT_CHANGE_SPEED, disable_speed);
324 this->SetWidgetDisabledState(WID_VT_CLEAR_SPEED, disable_speed);
325 this->SetWidgetDisabledState(WID_VT_SHARED_ORDER_LIST, !v->HasSharedOrdersList());
327 this->SetWidgetDisabledState(WID_VT_CONFIRM_ALL, !v->HasOrdersList());
328 this->SetWidgetDisabledState(WID_VT_RESET_LATENESS, !v->HasOrdersList());
329 this->SetWidgetDisabledState(WID_VT_AUTOMATE, !v->HasOrdersList());
330 } else {
331 this->DisableWidget(WID_VT_CONFIRM_ALL);
332 this->DisableWidget(WID_VT_CHANGE_TIME);
333 this->DisableWidget(WID_VT_CLEAR_TIME);
334 this->DisableWidget(WID_VT_CHANGE_SPEED);
335 this->DisableWidget(WID_VT_CLEAR_SPEED);
336 this->DisableWidget(WID_VT_RESET_LATENESS);
337 this->DisableWidget(WID_VT_AUTOMATE);
338 this->DisableWidget(WID_VT_SHARED_ORDER_LIST);
341 /* We can only set parameters if we're in one of the manual modes. */
342 bool enabled_state = (this->new_sep_settings.mode == TTS_MODE_MAN_N) || (this->new_sep_settings.mode == TTS_MODE_MAN_T);
344 this->SetWidgetDisabledState(WID_VT_TTSEP_SET_PARAMETER, !enabled_state);
346 this->SetWidgetLoweredState(WID_VT_AUTOMATE, HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE));
348 this->DrawWidgets();
351 virtual void SetStringParameters(int widget) const
353 switch (widget) {
354 case WID_VT_CAPTION: SetDParam(0, this->vehicle->index); break;
355 case WID_VT_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break;
356 case WID_VT_TTSEP_MODE_DROPDOWN: SetDParam(0, TimetableSeparationDropdownOptions[this->new_sep_settings.mode]); break;
357 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;
361 virtual void DrawWidget(const Rect &r, int widget) const
363 const Vehicle *v = this->vehicle;
364 int selected = this->sel_index;
366 switch (widget) {
367 case WID_VT_TIMETABLE_PANEL: {
368 int y = r.top + WD_FRAMERECT_TOP;
369 int i = this->vscroll->GetPosition();
370 VehicleOrderID order_id = (i + 1) / 2;
371 bool final_order = false;
373 bool rtl = _current_text_dir == TD_RTL;
374 SetDParamMaxValue(0, v->GetNumOrders(), 2);
375 int index_column_width = GetStringBoundingBox(STR_ORDER_INDEX).width + 2 * GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + 3;
376 int middle = rtl ? r.right - WD_FRAMERECT_RIGHT - index_column_width : r.left + WD_FRAMERECT_LEFT + index_column_width;
378 const Order *order = v->GetOrder(order_id);
379 while (order != NULL) {
380 /* Don't draw anything if it extends past the end of the window. */
381 if (!this->vscroll->IsVisible(i)) break;
383 if (i % 2 == 0) {
384 DrawOrderString(v, order, order_id, y, i == selected, true, r.left + WD_FRAMERECT_LEFT, middle, r.right - WD_FRAMERECT_RIGHT);
386 order_id++;
388 if (order_id >= v->GetNumOrders()) {
389 order = v->GetOrder(0);
390 final_order = true;
391 } else {
392 order = order->next;
394 } else {
395 StringID string;
396 TextColour colour = (i == selected) ? TC_WHITE : TC_BLACK;
397 if (order->IsType(OT_CONDITIONAL)) {
398 string = STR_TIMETABLE_NO_TRAVEL;
399 } else if (order->IsType(OT_IMPLICIT)) {
400 string = STR_TIMETABLE_NOT_TIMETABLEABLE;
401 colour = ((i == selected) ? TC_SILVER : TC_GREY) | TC_NO_SHADE;
402 } else if (!order->IsTravelTimetabled()) {
403 if (order->GetTravelTime() > 0) {
404 SetTimetableParams(0, 1, order->GetTravelTime());
405 string = order->GetMaxSpeed() != UINT16_MAX ?
406 STR_TIMETABLE_TRAVEL_FOR_SPEED_ESTIMATED :
407 STR_TIMETABLE_TRAVEL_FOR_ESTIMATED;
408 } else {
409 string = order->GetMaxSpeed() != UINT16_MAX ?
410 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED :
411 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED;
413 } else {
414 SetTimetableParams(0, 1, order->GetTimetabledTravel());
415 string = order->GetMaxSpeed() != UINT16_MAX ?
416 STR_TIMETABLE_TRAVEL_FOR_SPEED : STR_TIMETABLE_TRAVEL_FOR;
418 SetDParam(2, order->GetMaxSpeed());
420 DrawString(rtl ? r.left + WD_FRAMERECT_LEFT : middle, rtl ? middle : r.right - WD_FRAMERECT_LEFT, y, string, colour);
422 if (final_order) break;
425 i++;
426 y += FONT_HEIGHT_NORMAL;
428 break;
431 case WID_VT_ARRIVAL_DEPARTURE_PANEL: {
432 /* Arrival and departure times are handled in an all-or-nothing approach,
433 * i.e. are only shown if we can calculate all times.
434 * Excluding order lists with only one order makes some things easier.
436 Ticks total_time = v->GetTimetableDurationIncomplete();
437 if (total_time <= 0 || v->GetNumOrders() <= 1 || !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) break;
439 TimetableArrivalDeparture *arr_dep = AllocaM(TimetableArrivalDeparture, v->GetNumOrders());
440 const VehicleOrderID cur_order = v->cur_real_order_index % v->GetNumOrders();
442 VehicleOrderID earlyID = BuildArrivalDepartureList(v, arr_dep) ? cur_order : (VehicleOrderID)INVALID_VEH_ORDER_ID;
444 int y = r.top + WD_FRAMERECT_TOP;
446 bool show_late = this->show_expected && v->lateness_counter > TICKS_PER_MINUTE;
447 Ticks offset = show_late ? 0 : -v->lateness_counter;
449 if (HasBit(v->vehicle_flags, VF_SEPARATION_IN_PROGRESS)) {
450 show_late = false;
453 bool rtl = _current_text_dir == TD_RTL;
454 int abbr_left = rtl ? r.right - WD_FRAMERECT_RIGHT - this->deparr_abbr_width : r.left + WD_FRAMERECT_LEFT;
455 int abbr_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->deparr_abbr_width;
456 int time_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - WD_FRAMERECT_RIGHT - this->deparr_time_width;
457 int time_right = rtl ? r.left + WD_FRAMERECT_LEFT + this->deparr_time_width : r.right - WD_FRAMERECT_RIGHT;
459 for (int i = this->vscroll->GetPosition(); i / 2 < v->GetNumOrders(); ++i) { // note: i is also incremented in the loop
460 /* Don't draw anything if it extends past the end of the window. */
461 if (!this->vscroll->IsVisible(i)) break;
463 if (i % 2 == 0) {
464 if (arr_dep[i / 2].arrival != INVALID_TICKS) {
465 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_ARRIVAL_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
466 if (this->show_expected && i / 2 == earlyID) {
467 SetDParam(0, arr_dep[i / 2].arrival);
468 DrawString(time_left, time_right, y, STR_JUST_TIME_TINY, TC_GREEN);
469 } else {
470 SetDParam(0, arr_dep[i / 2].arrival + offset);
471 DrawString(time_left, time_right, y, STR_JUST_TIME_TINY, show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
475 else {
476 if (arr_dep[i / 2].departure != INVALID_TICKS) {
477 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_DEPARTURE_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
478 SetDParam(0, arr_dep[i / 2].departure + offset);
479 DrawString(time_left, time_right, y, STR_JUST_TIME_TINY,
480 show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
483 y += FONT_HEIGHT_NORMAL;
485 break;
488 case WID_VT_SUMMARY_PANEL: {
489 int y = r.top + WD_FRAMERECT_TOP;
491 Ticks total_time = v->GetTimetableDurationIncomplete();
492 if (total_time != 0) {
493 SetTimetableParams(0, 1, total_time);
494 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, v->HasCompleteTimetable() ? STR_TIMETABLE_TOTAL_TIME : STR_TIMETABLE_TOTAL_TIME_INCOMPLETE);
496 y += FONT_HEIGHT_NORMAL;
498 auto lateness_counter = HasBit(v->vehicle_flags, VF_SEPARATION_IN_PROGRESS) ? 0 : v->lateness_counter;
500 if (v->timetable_start != 0) {
501 /* We are running towards the first station so we can start the
502 * timetable at the given time. */
503 SetDParam(0, STR_JUST_DATE_TINY);
504 SetDParam(1, v->timetable_start);
505 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_START_AT);
507 else if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) {
508 /* We aren't running on a timetable yet, so how can we be "on time"
509 * when we aren't even "on service"/"on duty"? */
510 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_NOT_STARTED);
512 else if (lateness_counter == 0) {
513 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_ON_TIME);
515 else {
516 SetTimetableParams(0, 1, abs(lateness_counter));
517 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE);
519 break;
522 case WID_VT_TTSEP_PANEL_TEXT: {
523 int y = r.top + WD_FRAMERECT_TOP; // Represents the current vertical position
524 const int left_border = r.left + WD_FRAMERECT_LEFT; // Represents the left border of the separation display frame
525 const int right_border = r.right - WD_FRAMERECT_RIGHT; // Represents the right border of the separation display frame.
527 /* If separation is inactive, we can stop here. */
528 if (!_settings_game.order.automatic_timetable_separation || !this->vehicle->HasOrdersList())
529 break;
531 /* If the new mode is OFF... */
532 if (this->new_sep_settings.mode == TTS_MODE_OFF) {
533 /* ... skip description lines. */
534 int offset = GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height;
536 y = y + GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height + offset;
539 else {
540 uint64 par;
542 // If separation hasn't just been switched off, we need to draw various description lines.
543 // The first line is the amount of separation which is either saved in the struct or must
544 // be calculated on the fly.
545 if (this->new_sep_settings.mode == TTS_MODE_MAN_T ||
546 this->new_sep_settings.mode == TTS_MODE_AUTO ||
547 this->new_sep_settings.mode == TTS_MODE_BUFFERED_AUTO) {
548 par = this->new_sep_settings.sep_ticks;
550 else {
551 par = this->vehicle->GetTimetableTotalDuration() / max(1u, this->new_sep_settings.num_veh);
554 if (this->new_sep_settings.mode == TTS_MODE_MAN_T ||
555 (this->vehicle->HasCompleteTimetable() && this->vehicle->IsTimetableSeparationValid())) {
557 SetDParam(0, par);
558 DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_TIME_DESC_TICKS, TC_BLACK);
560 y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height;
562 else {
563 y += GetStringBoundingBox(STR_TTSEPARATION_REQ_TIME_DESC_TICKS).height;
566 // Print either the chosen amount of vehicles (when in MAN_N mode) or the calculated result...
567 // Do not print anything at all if we do not have a valid timetable yet.
568 if (this->new_sep_settings.mode == TTS_MODE_MAN_N ||
569 this->new_sep_settings.mode == TTS_MODE_AUTO ||
570 this->new_sep_settings.mode == TTS_MODE_BUFFERED_AUTO) {
571 par = this->new_sep_settings.num_veh;
573 else {
574 par = this->vehicle->GetTimetableTotalDuration() / max(1u, this->new_sep_settings.sep_ticks);
577 if (this->new_sep_settings.mode == TTS_MODE_MAN_N ||
578 (this->vehicle->HasCompleteTimetable() && this->vehicle->IsTimetableSeparationValid())) {
579 SetDParam(0, par);
580 DrawString(left_border, right_border, y, STR_TTSEPARATION_REQ_NUM_DESC, TC_BLACK);
583 y += GetStringBoundingBox(STR_TTSEPARATION_REQ_NUM_DESC).height;
586 /* If separation is switched on at all... */
587 if (this->vehicle->IsTimetableSeparationOn()) {
588 if (!this->vehicle->HasCompleteTimetable()) {
589 SetDParam(0, STR_TTSEPARATION_STATUS_WAITING_FOR_TIMETABLE);
591 else {
592 /* ... set displayed status to either "Running" or "Initializing" */
593 SetDParam(0, (this->vehicle->IsTimetableSeparationValid()) ? STR_TTSEPARATION_STATUS_RUNNING : STR_TTSEPARATION_STATUS_INIT);
596 else {
597 /* If separation is switched off, show this instead. */
598 SetDParam(0, STR_TTSEPARATION_STATUS_OFF);
601 /* Print status description. */
602 DrawStringMultiLine(left_border, right_border, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_TTSEPARATION_STATUS_DESC);
607 static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected, bool speed)
609 uint order_number = (selected + 1) / 2;
610 ModifyTimetableFlags mtf = (selected % 2 == 1) ? (speed ? MTF_TRAVEL_SPEED : MTF_TRAVEL_TIME) : MTF_WAIT_TIME;
612 if (order_number >= v->GetNumOrders()) order_number = 0;
614 return v->index | (order_number << 20) | (mtf << 28);
617 static inline bool IsActionDisabled(const Vehicle *v, int selected)
619 bool disabled = true;
620 if (selected != -1) {
621 const Order *order = v->GetOrder(((selected + 1) / 2) % v->GetNumOrders());
622 if (selected % 2 == 1) {
623 disabled = order != NULL && (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT));
625 else {
626 disabled = order == NULL || ((!(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));
629 return disabled;
632 virtual void OnClick(Point pt, int widget, int click_count)
634 const Vehicle *v = this->vehicle;
636 this->DeleteChildWindows(WC_QUERY_STRING);
638 switch (widget) {
639 case WID_VT_ORDER_VIEW: // Order view button
640 ShowOrdersWindow(v);
641 break;
643 case WID_VT_TIMETABLE_PANEL: { // Main panel.
644 int selected = GetOrderFromTimetableWndPt(pt.y, v);
646 /* Allow change time by double-clicking order */
647 if (click_count == 2) {
648 this->sel_index = selected == INVALID_ORDER ? -1 : selected;
649 this->OnClick(pt, WID_VT_CHANGE_TIME, click_count);
650 return;
651 } else {
652 this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
655 this->DeleteChildWindows();
656 break;
659 case WID_VT_CONFIRM_ALL: { // Confirm all estimated times as timetabled.
660 DoCommandP(0, v->index, 0, CMD_CONFIRM_ALL | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
661 break;
664 case WID_VT_CHANGE_TIME: { // "Wait For" button.
665 int selected = this->sel_index;
666 VehicleOrderID real = (selected + 1) / 2;
668 if (real >= v->GetNumOrders()) real = 0;
670 const Order *order = v->GetOrder(real);
671 StringID current = STR_EMPTY;
673 if (order != NULL) {
674 uint time = (selected % 2 == 1) ? order->GetTravelTime() : order->GetWaitTime();
676 if (time != 0) {
677 SetDParam(0, time);
678 current = STR_JUST_INT;
682 this->query_widget = WID_VT_CHANGE_TIME;
683 this->query_is_speed_query = false;
684 this->query_is_bulk_query = _ctrl_pressed;
685 ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
686 break;
689 case WID_VT_CHANGE_SPEED: { // Change max speed button.
690 int selected = this->sel_index;
691 VehicleOrderID real = (selected + 1) / 2;
693 if (real >= v->GetNumOrders()) real = 0;
695 StringID current = STR_EMPTY;
696 const Order *order = v->GetOrder(real);
697 if (order != NULL) {
698 if (order->GetMaxSpeed() != UINT16_MAX) {
699 SetDParam(0, ConvertKmhishSpeedToDisplaySpeed(order->GetMaxSpeed()));
700 current = STR_JUST_INT;
704 this->query_widget = WID_VT_CHANGE_SPEED;
705 this->query_is_speed_query = true;
706 this->query_is_bulk_query = _ctrl_pressed;
707 ShowQueryString(current, STR_TIMETABLE_CHANGE_SPEED, 31, this, CS_NUMERAL, QSF_NONE);
708 break;
711 case WID_VT_CLEAR_TIME: { // Clear waiting time.
712 uint32 p1 = PackTimetableArgs(v, this->sel_index, false);
713 DoCommandP(0, p1, 0, (_ctrl_pressed ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
714 break;
717 case WID_VT_CLEAR_SPEED: { // Clear max speed button.
718 uint32 p1 = PackTimetableArgs(v, this->sel_index, true);
719 DoCommandP(0, p1, UINT16_MAX, (_ctrl_pressed ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
720 break;
723 case WID_VT_RESET_LATENESS: // Reset the vehicle's late counter.
724 DoCommandP(0, v->index, 0, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
725 break;
727 case WID_VT_AUTOMATE: { // Automate the timetable.
728 uint32 p2 = 0;
729 if (!HasBit(v->vehicle_flags, VF_AUTOMATE_TIMETABLE)) SetBit(p2, 0);
730 if (_ctrl_pressed) SetBit(p2, 1);
731 DoCommandP(0, v->index, p2, CMD_AUTOMATE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
732 break;
735 case WID_VT_EXPECTED:
736 this->show_expected = !this->show_expected;
737 break;
739 case WID_VT_SHARED_ORDER_LIST:
740 ShowVehicleListWindow(v);
741 break;
743 case WID_VT_TTSEP_MODE_DROPDOWN: {
744 ShowDropDownMenu(this, TimetableSeparationDropdownOptions, this->new_sep_settings.mode, WID_VT_TTSEP_MODE_DROPDOWN, 0, 0);
745 break;
748 case WID_VT_TTSEP_SET_PARAMETER: {
749 this->query_widget = WID_VT_TTSEP_SET_PARAMETER;
750 SetDParam(0, (this->new_sep_settings.mode == TTS_MODE_MAN_N) ? this->new_sep_settings.num_veh : this->new_sep_settings.sep_ticks);
751 ShowQueryString(STR_JUST_INT, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_NONE);
752 break;
756 this->SetDirty();
759 virtual void OnDropdownSelect(int widget, int index)
761 assert(widget == WID_VT_TTSEP_MODE_DROPDOWN);
763 this->new_sep_settings = this->vehicle->GetTimetableSeparationSettings();
764 this->new_sep_settings.mode = (TTSepMode)index;
765 this->vehicle->SetTimetableSeparationSettings(this->new_sep_settings);
766 this->InvalidateData();
770 virtual void OnQueryTextFinished(char *str)
772 if (str == NULL || StrEmpty(str))
773 return;
775 switch (this->query_widget) {
776 case WID_VT_CHANGE_TIME:
777 case WID_VT_CHANGE_SPEED: {
778 const Vehicle *v = this->vehicle;
780 uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
782 uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
783 if (this->query_is_speed_query) {
784 val = ConvertDisplaySpeedToKmhishSpeed(val);
787 uint32 p2 = minu(val, UINT16_MAX);
789 DoCommandP(0, p1, p2, (this->query_is_bulk_query ? CMD_BULK_CHANGE_TIMETABLE : CMD_CHANGE_TIMETABLE) | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
790 break;
792 case WID_VT_TTSEP_SET_PARAMETER: {
793 int value = atoi(str);
795 switch (this->new_sep_settings.mode)
797 case TTS_MODE_AUTO:
798 case TTS_MODE_BUFFERED_AUTO:
799 case TTS_MODE_OFF:
800 break;
802 case TTS_MODE_MAN_N:
803 this->new_sep_settings.num_veh = Clamp(value, 1, 65535);
804 break;
806 case TTS_MODE_MAN_T:
807 this->new_sep_settings.sep_ticks = Clamp(value, 1, 65535);
808 break;
810 default:
811 NOT_REACHED();
812 break;
815 this->vehicle->SetTimetableSeparationSettings(this->new_sep_settings);
816 this->InvalidateData();
817 break;
819 default:
820 NOT_REACHED();
821 break;
825 virtual void OnResize()
827 /* Update the scroll bar */
828 this->vscroll->SetCapacityFromWidget(this, WID_VT_TIMETABLE_PANEL, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
832 * Update the selection state of the arrival/departure data
834 void UpdateSelectionStates()
836 this->GetWidget<NWidgetStacked>(WID_VT_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : SZSP_NONE);
837 this->GetWidget<NWidgetStacked>(WID_VT_EXPECTED_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : 1);
840 virtual void OnFocus(Window *previously_focused_window)
842 if (HasFocusedVehicleChanged(this->window_number, previously_focused_window)) {
843 MarkAllRoutePathsDirty(this->vehicle);
844 MarkAllRouteStepsDirty(this->vehicle);
848 virtual void OnFocusLost(Window *newly_focused_window)
850 if (HasFocusedVehicleChanged(this->window_number, newly_focused_window)) {
851 MarkAllRoutePathsDirty(this->vehicle);
852 MarkAllRouteStepsDirty(this->vehicle);
856 const Vehicle *GetVehicle()
858 return this->vehicle;
862 static const NWidgetPart _nested_timetable_widgets[] = {
863 NWidget(NWID_HORIZONTAL),
864 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
865 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VT_CAPTION), SetDataTip(STR_TIMETABLE_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
866 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_ORDER_VIEW), SetMinimalSize(61, 14), SetDataTip( STR_TIMETABLE_ORDER_VIEW, STR_TIMETABLE_ORDER_VIEW_TOOLTIP),
867 NWidget(WWT_SHADEBOX, COLOUR_GREY),
868 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
869 NWidget(WWT_STICKYBOX, COLOUR_GREY),
870 EndContainer(),
871 NWidget(NWID_HORIZONTAL),
872 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(),
873 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_ARRIVAL_DEPARTURE_SELECTION),
874 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(80, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), SetScrollbar(WID_VT_SCROLLBAR), EndContainer(),
875 EndContainer(),
876 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VT_SCROLLBAR),
877 NWidget(WWT_PANEL, COLOUR_GREY),
878 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_TTSEPARATION_SETTINGS_DESC, STR_NULL), SetPadding(3),
879 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_VT_TTSEP_MODE_DROPDOWN), SetDataTip(STR_JUST_STRING, STR_TIMETABLE_TOOLTIP),
880 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_TTSEP_SET_PARAMETER), SetFill(1, 0), SetDataTip(STR_TTSEPARATION_SET_XX, STR_TIMETABLE_TOOLTIP),
881 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_TTSEP_PANEL_TEXT), SetFill(1, 1), SetResize(0, 1), SetMinimalSize(180, 44), EndContainer(),
882 EndContainer(),
883 EndContainer(),
884 EndContainer(),
885 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
886 NWidget(NWID_HORIZONTAL),
887 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
888 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
889 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),
890 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),
891 EndContainer(),
892 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
893 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),
894 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),
895 EndContainer(),
896 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
897 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),
898 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),
899 EndContainer(),
900 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
901 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_AUTOMATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOMATE, STR_TIMETABLE_AUTOMATE_TOOLTIP),
902 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_EXPECTED_SELECTION),
903 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_EXPECTED), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
904 NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
905 EndContainer(),
906 EndContainer(),
907 EndContainer(),
908 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
909 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),
910 NWidget(WWT_RESIZEBOX, COLOUR_GREY), SetFill(0, 1),
911 EndContainer(),
912 EndContainer(),
915 static WindowDesc _timetable_desc(
916 WDP_AUTO, "view_vehicle_timetable", 400, 130,
917 WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW,
918 WDF_CONSTRUCTION,
919 _nested_timetable_widgets, lengthof(_nested_timetable_widgets)
923 * Show the timetable for a given vehicle.
924 * @param v The vehicle to show the timetable for.
926 void ShowTimetableWindow(const Vehicle *v)
928 DeleteWindowById(WC_VEHICLE_DETAILS, v->index, false);
929 DeleteWindowById(WC_VEHICLE_ORDERS, v->index, false);
930 AllocateWindowDescFront<TimetableWindow>(&_timetable_desc, v->index);