OpenTTD: src/timetable_gui.cpp Source File

OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

timetable_gui.cpp
Go to the documentation of this file.
1 /* $Id: timetable_gui.cpp 27893 2017-08-13 18:38:42Z frosch $ */
2
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 */
9
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
28 #include "widgets/timetable_widget.h"
29
30 #include "table/sprites.h"
31 #include "table/strings.h"
32
33 #include "safeguards.h"
34
36 struct TimetableArrivalDeparture {
37 Ticks arrival;
38 Ticks departure;
39 };
40
47 void SetTimetableParams(int param1, int param2, Ticks ticks)
48 {
49 if (_settings_client.gui.timetable_in_ticks) {
50 SetDParam(param1, STR_TIMETABLE_TICKS);
51 SetDParam(param2, ticks);
52 } else {
53 SetDParam(param1, STR_TIMETABLE_DAYS);
54 SetDParam(param2, ticks / DAY_TICKS);
55 }
56 }
57
64 static bool CanDetermineTimeTaken(const Order *order, bool travelling)
65 {
66 /* Current order is conditional */
67 if (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT)) return false;
68 /* No travel time and we have not already finished travelling */
69 if (travelling && !order->IsTravelTimetabled()) return false;
70 /* No wait time but we are loading at this timetabled station */
71 if (!travelling && !order->IsWaitTimetabled() && order->IsType(OT_GOTO_STATION) &&
72 !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) {
73 return false;
74 }
75
76 return true;
77 }
78
79
88 static void FillTimetableArrivalDepartureTable(const Vehicle *v, VehicleOrderID start, bool travelling, TimetableArrivalDeparture *table, Ticks offset)
89 {
90 assert(table != NULL);
91 assert(v->GetNumOrders() >= 2);
92 assert(start < v->GetNumOrders());
93
94 Ticks sum = offset;
95 VehicleOrderID i = start;
96 const Order *order = v->GetOrder(i);
97
98 /* Pre-initialize with unknown time */
99 for (int i = 0; i < v->GetNumOrders(); ++i) {
100 table[i].arrival = table[i].departure = INVALID_TICKS;
101 }
102
103 /* Cyclically loop over all orders until we reach the current one again.
104 * As we may start at the current order, do a post-checking loop */
105 do {
106 /* Automatic orders don't influence the overall timetable;
107 * they just add some untimetabled entries, but the time till
108 * the next non-implicit order can still be known. */
109 if (!order->IsType(OT_IMPLICIT)) {
110 if (travelling || i != start) {
111 if (!CanDetermineTimeTaken(order, true)) return;
112 sum += order->GetTimetabledTravel();
113 table[i].arrival = sum;
114 }
115
116 if (!CanDetermineTimeTaken(order, false)) return;
117 sum += order->GetTimetabledWait();
118 table[i].departure = sum;
119 }
120
121 ++i;
122 order = order->next;
123 if (i >= v->GetNumOrders()) {
124 i = 0;
125 assert(order == NULL);
126 order = v->orders.list->GetFirstOrder();
127 }
128 } while (i != start);
129
130 /* When loading at a scheduled station we still have to treat the
131 * travelling part of the first order. */
132 if (!travelling) {
133 if (!CanDetermineTimeTaken(order, true)) return;
134 sum += order->GetTimetabledTravel();
135 table[i].arrival = sum;
136 }
137 }
138
139
145 static void ChangeTimetableStartCallback(const Window *w, Date date)
146 {
147 DoCommandP(0, w->window_number, date, CMD_SET_TIMETABLE_START | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
148 }
149
150
151 struct TimetableWindow : Window {
152 int sel_index;
153 const Vehicle *vehicle;
154 bool show_expected;
155 uint deparr_time_width;
156 uint deparr_abbr_width;
157 Scrollbar *vscroll;
158 bool query_is_speed_query;
159
160 TimetableWindow(WindowDesc *desc, WindowNumber window_number) :
161 Window(desc),
162 sel_index(-1),
163 vehicle(Vehicle::Get(window_number)),
164 show_expected(true)
165 {
166 this->CreateNestedTree();
167 this->vscroll = this->GetScrollbar(WID_VT_SCROLLBAR);
168 this->UpdateSelectionStates();
169 this->FinishInitNested(window_number);
170
171 this->owner = this->vehicle->owner;
172 }
173
180 static bool BuildArrivalDepartureList(const Vehicle *v, TimetableArrivalDeparture *table)
181 {
182 assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED));
183
184 bool travelling = (!v->current_order.IsType(OT_LOADING) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE);
185 Ticks start_time = _date_fract - v->current_order_time;
186
187 FillTimetableArrivalDepartureTable(v, v->cur_real_order_index % v->GetNumOrders(), travelling, table, start_time);
188
189 return (travelling && v->lateness_counter < 0);
190 }
191
192 virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
193 {
194 switch (widget) {
195 case WID_VT_ARRIVAL_DEPARTURE_PANEL:
196 SetDParamMaxValue(0, MAX_YEAR * DAYS_IN_YEAR, 0, FS_SMALL);
197 this->deparr_time_width = GetStringBoundingBox(STR_JUST_DATE_TINY).width;
198 this->deparr_abbr_width = max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_ABBREVIATION).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_ABBREVIATION).width);
199 size->width = WD_FRAMERECT_LEFT + this->deparr_abbr_width + 10 + this->deparr_time_width + WD_FRAMERECT_RIGHT;
200 FALLTHROUGH;
201
202 case WID_VT_ARRIVAL_DEPARTURE_SELECTION:
203 case WID_VT_TIMETABLE_PANEL:
204 resize->height = FONT_HEIGHT_NORMAL;
205 size->height = WD_FRAMERECT_TOP + 8 * resize->height + WD_FRAMERECT_BOTTOM;
206 break;
207
208 case WID_VT_SUMMARY_PANEL:
209 size->height = WD_FRAMERECT_TOP + 2 * FONT_HEIGHT_NORMAL + WD_FRAMERECT_BOTTOM;
210 break;
211 }
212 }
213
214 int GetOrderFromTimetableWndPt(int y, const Vehicle *v)
215 {
216 int sel = (y - this->GetWidget<NWidgetBase>(WID_VT_TIMETABLE_PANEL)->pos_y - WD_FRAMERECT_TOP) / FONT_HEIGHT_NORMAL;
217
218 if ((uint)sel >= this->vscroll->GetCapacity()) return INVALID_ORDER;
219
220 sel += this->vscroll->GetPosition();
221
222 return (sel < v->GetNumOrders() * 2 && sel >= 0) ? sel : INVALID_ORDER;
223 }
224
230 virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
231 {
232 switch (data) {
233 case VIWD_AUTOREPLACE:
234 /* Autoreplace replaced the vehicle */
235 this->vehicle = Vehicle::Get(this->window_number);
236 break;
237
238 case VIWD_REMOVE_ALL_ORDERS:
239 /* Removed / replaced all orders (after deleting / sharing) */
240 if (this->sel_index == -1) break;
241
242 this->DeleteChildWindows();
243 this->sel_index = -1;
244 break;
245
246 case VIWD_MODIFY_ORDERS:
247 if (!gui_scope) break;
248 this->UpdateSelectionStates();
249 this->ReInit();
250 break;
251
252 default: {
253 if (gui_scope) break; // only do this once; from command scope
254
255 /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
256 * the order is being created / removed */
257 if (this->sel_index == -1) break;
258
259 VehicleOrderID from = GB(data, 0, 8);
260 VehicleOrderID to = GB(data, 8, 8);
261
262 if (from == to) break; // no need to change anything
263
264 /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
265 uint old_num_orders = this->vehicle->GetNumOrders() - (uint)(from == INVALID_VEH_ORDER_ID) + (uint)(to == INVALID_VEH_ORDER_ID);
266
267 VehicleOrderID selected_order = (this->sel_index + 1) / 2;
268 if (selected_order == old_num_orders) selected_order = 0; // when last travel time is selected, it belongs to order 0
269
270 bool travel = HasBit(this->sel_index, 0);
271
272 if (from != selected_order) {
273 /* Moving from preceding order? */
274 selected_order -= (int)(from <= selected_order);
275 /* Moving to preceding order? */
276 selected_order += (int)(to <= selected_order);
277 } else {
278 /* Now we are modifying the selected order */
279 if (to == INVALID_VEH_ORDER_ID) {
280 /* Deleting selected order */
281 this->DeleteChildWindows();
282 this->sel_index = -1;
283 break;
284 } else {
285 /* Moving selected order */
286 selected_order = to;
287 }
288 }
289
290 /* recompute new sel_index */
291 this->sel_index = 2 * selected_order - (int)travel;
292 /* travel time of first order needs special handling */
293 if (this->sel_index == -1) this->sel_index = this->vehicle->GetNumOrders() * 2 - 1;
294 break;
295 }
296 }
297 }
298
299
300 virtual void OnPaint()
301 {
302 const Vehicle *v = this->vehicle;
303 int selected = this->sel_index;
304
305 this->vscroll->SetCount(v->GetNumOrders() * 2);
306
307 if (v->owner == _local_company) {
308 bool disable = true;
309 if (selected != -1) {
310 const Order *order = v->GetOrder(((selected + 1) / 2) % v->GetNumOrders());
311 if (selected % 2 == 1) {
312 disable = order != NULL && (order->IsType(OT_CONDITIONAL) || order->IsType(OT_IMPLICIT));
313 } else {
314 disable = order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL));
315 }
316 }
317 bool disable_speed = disable || selected % 2 != 1 || v->type == VEH_AIRCRAFT;
318
319 this->SetWidgetDisabledState(WID_VT_CHANGE_TIME, disable);
320 this->SetWidgetDisabledState(WID_VT_CLEAR_TIME, disable);
321 this->SetWidgetDisabledState(WID_VT_CHANGE_SPEED, disable_speed);
322 this->SetWidgetDisabledState(WID_VT_CLEAR_SPEED, disable_speed);
323 this->SetWidgetDisabledState(WID_VT_SHARED_ORDER_LIST, !v->IsOrderListShared());
324
325 this->SetWidgetDisabledState(WID_VT_START_DATE, v->orders.list == NULL);
326 this->SetWidgetDisabledState(WID_VT_RESET_LATENESS, v->orders.list == NULL);
327 this->SetWidgetDisabledState(WID_VT_AUTOFILL, v->orders.list == NULL);
328 } else {
329 this->DisableWidget(WID_VT_START_DATE);
330 this->DisableWidget(WID_VT_CHANGE_TIME);
331 this->DisableWidget(WID_VT_CLEAR_TIME);
332 this->DisableWidget(WID_VT_CHANGE_SPEED);
333 this->DisableWidget(WID_VT_CLEAR_SPEED);
334 this->DisableWidget(WID_VT_RESET_LATENESS);
335 this->DisableWidget(WID_VT_AUTOFILL);
336 this->DisableWidget(WID_VT_SHARED_ORDER_LIST);
337 }
338
339 this->SetWidgetLoweredState(WID_VT_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE));
340
341 this->DrawWidgets();
342 }
343
344 virtual void SetStringParameters(int widget) const
345 {
346 switch (widget) {
347 case WID_VT_CAPTION: SetDParam(0, this->vehicle->index); break;
348 case WID_VT_EXPECTED: SetDParam(0, this->show_expected ? STR_TIMETABLE_EXPECTED : STR_TIMETABLE_SCHEDULED); break;
349 }
350 }
351
352 virtual void DrawWidget(const Rect &r, int widget) const
353 {
354 const Vehicle *v = this->vehicle;
355 int selected = this->sel_index;
356
357 switch (widget) {
358 case WID_VT_TIMETABLE_PANEL: {
359 int y = r.top + WD_FRAMERECT_TOP;
360 int i = this->vscroll->GetPosition();
361 VehicleOrderID order_id = (i + 1) / 2;
362 bool final_order = false;
363
364 bool rtl = _current_text_dir == TD_RTL;
365 SetDParamMaxValue(0, v->GetNumOrders(), 2);
366 int index_column_width = GetStringBoundingBox(STR_ORDER_INDEX).width + 2 * GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + 3;
367 int middle = rtl ? r.right - WD_FRAMERECT_RIGHT - index_column_width : r.left + WD_FRAMERECT_LEFT + index_column_width;
368
369 const Order *order = v->GetOrder(order_id);
370 while (order != NULL) {
371 /* Don't draw anything if it extends past the end of the window. */
372 if (!this->vscroll->IsVisible(i)) break;
373
374 if (i % 2 == 0) {
375 DrawOrderString(v, order, order_id, y, i == selected, true, r.left + WD_FRAMERECT_LEFT, middle, r.right - WD_FRAMERECT_RIGHT);
376
377 order_id++;
378
379 if (order_id >= v->GetNumOrders()) {
380 order = v->GetOrder(0);
381 final_order = true;
382 } else {
383 order = order->next;
384 }
385 } else {
386 StringID string;
387 TextColour colour = (i == selected) ? TC_WHITE : TC_BLACK;
388 if (order->IsType(OT_CONDITIONAL)) {
389 string = STR_TIMETABLE_NO_TRAVEL;
390 } else if (order->IsType(OT_IMPLICIT)) {
391 string = STR_TIMETABLE_NOT_TIMETABLEABLE;
392 colour = ((i == selected) ? TC_SILVER : TC_GREY) | TC_NO_SHADE;
393 } else if (!order->IsTravelTimetabled()) {
394 if (order->GetTravelTime() > 0) {
395 SetTimetableParams(0, 1, order->GetTravelTime());
396 string = order->GetMaxSpeed() != UINT16_MAX ?
397 STR_TIMETABLE_TRAVEL_FOR_SPEED_ESTIMATED :
398 STR_TIMETABLE_TRAVEL_FOR_ESTIMATED;
399 } else {
400 string = order->GetMaxSpeed() != UINT16_MAX ?
401 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED_SPEED :
402 STR_TIMETABLE_TRAVEL_NOT_TIMETABLED;
403 }
404 } else {
405 SetTimetableParams(0, 1, order->GetTimetabledTravel());
406 string = order->GetMaxSpeed() != UINT16_MAX ?
407 STR_TIMETABLE_TRAVEL_FOR_SPEED : STR_TIMETABLE_TRAVEL_FOR;
408 }
409 SetDParam(2, order->GetMaxSpeed());
410
411 DrawString(rtl ? r.left + WD_FRAMERECT_LEFT : middle, rtl ? middle : r.right - WD_FRAMERECT_LEFT, y, string, colour);
412
413 if (final_order) break;
414 }
415
416 i++;
417 y += FONT_HEIGHT_NORMAL;
418 }
419 break;
420 }
421
422 case WID_VT_ARRIVAL_DEPARTURE_PANEL: {
423 /* Arrival and departure times are handled in an all-or-nothing approach,
424 * i.e. are only shown if we can calculate all times.
425 * Excluding order lists with only one order makes some things easier.
426 */
427 Ticks total_time = v->orders.list != NULL ? v->orders.list->GetTimetableDurationIncomplete() : 0;
428 if (total_time <= 0 || v->GetNumOrders() <= 1 || !HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) break;
429
430 TimetableArrivalDeparture *arr_dep = AllocaM(TimetableArrivalDeparture, v->GetNumOrders());
431 const VehicleOrderID cur_order = v->cur_real_order_index % v->GetNumOrders();
432
433 VehicleOrderID earlyID = BuildArrivalDepartureList(v, arr_dep) ? cur_order : (VehicleOrderID)INVALID_VEH_ORDER_ID;
434
435 int y = r.top + WD_FRAMERECT_TOP;
436
437 bool show_late = this->show_expected && v->lateness_counter > DAY_TICKS;
438 Ticks offset = show_late ? 0 : -v->lateness_counter;
439
440 bool rtl = _current_text_dir == TD_RTL;
441 int abbr_left = rtl ? r.right - WD_FRAMERECT_RIGHT - this->deparr_abbr_width : r.left + WD_FRAMERECT_LEFT;
442 int abbr_right = rtl ? r.right - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT + this->deparr_abbr_width;
443 int time_left = rtl ? r.left + WD_FRAMERECT_LEFT : r.right - WD_FRAMERECT_RIGHT - this->deparr_time_width;
444 int time_right = rtl ? r.left + WD_FRAMERECT_LEFT + this->deparr_time_width : r.right - WD_FRAMERECT_RIGHT;
445
446 for (int i = this->vscroll->GetPosition(); i / 2 < v->GetNumOrders(); ++i) { // note: i is also incremented in the loop
447 /* Don't draw anything if it extends past the end of the window. */
448 if (!this->vscroll->IsVisible(i)) break;
449
450 if (i % 2 == 0) {
451 if (arr_dep[i / 2].arrival != INVALID_TICKS) {
452 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_ARRIVAL_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
453 if (this->show_expected && i / 2 == earlyID) {
454 SetDParam(0, _date + arr_dep[i / 2].arrival / DAY_TICKS);
455 DrawString(time_left, time_right, y, STR_JUST_DATE_TINY, TC_GREEN);
456 } else {
457 SetDParam(0, _date + (arr_dep[i / 2].arrival + offset) / DAY_TICKS);
458 DrawString(time_left, time_right, y, STR_JUST_DATE_TINY,
459 show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
460 }
461 }
462 } else {
463 if (arr_dep[i / 2].departure != INVALID_TICKS) {
464 DrawString(abbr_left, abbr_right, y, STR_TIMETABLE_DEPARTURE_ABBREVIATION, i == selected ? TC_WHITE : TC_BLACK);
465 SetDParam(0, _date + (arr_dep[i/2].departure + offset) / DAY_TICKS);
466 DrawString(time_left, time_right, y, STR_JUST_DATE_TINY,
467 show_late ? TC_RED : i == selected ? TC_WHITE : TC_BLACK);
468 }
469 }
470 y += FONT_HEIGHT_NORMAL;
471 }
472 break;
473 }
474
475 case WID_VT_SUMMARY_PANEL: {
476 int y = r.top + WD_FRAMERECT_TOP;
477
478 Ticks total_time = v->orders.list != NULL ? v->orders.list->GetTimetableDurationIncomplete() : 0;
479 if (total_time != 0) {
480 SetTimetableParams(0, 1, total_time);
481 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, v->orders.list->IsCompleteTimetable() ? STR_TIMETABLE_TOTAL_TIME : STR_TIMETABLE_TOTAL_TIME_INCOMPLETE);
482 }
483 y += FONT_HEIGHT_NORMAL;
484
485 if (v->timetable_start != 0) {
486 /* We are running towards the first station so we can start the
487 * timetable at the given time. */
488 SetDParam(0, STR_JUST_DATE_TINY);
489 SetDParam(1, v->timetable_start);
490 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_START_AT);
491 } else if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) {
492 /* We aren't running on a timetable yet, so how can we be "on time"
493 * when we aren't even "on service"/"on duty"? */
494 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_NOT_STARTED);
495 } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) {
496 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_TIMETABLE_STATUS_ON_TIME);
497 } else {
498 SetTimetableParams(0, 1, abs(v->lateness_counter));
499 DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE);
500 }
501 break;
502 }
503 }
504 }
505
506 static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected, bool speed)
507 {
508 uint order_number = (selected + 1) / 2;
509 ModifyTimetableFlags mtf = (selected % 2 == 1) ? (speed ? MTF_TRAVEL_SPEED : MTF_TRAVEL_TIME) : MTF_WAIT_TIME;
510
511 if (order_number >= v->GetNumOrders()) order_number = 0;
512
513 return v->index | (order_number << 20) | (mtf << 28);
514 }
515
516 virtual void OnClick(Point pt, int widget, int click_count)
517 {
518 const Vehicle *v = this->vehicle;
519
520 switch (widget) {
521 case WID_VT_ORDER_VIEW: // Order view button
522 ShowOrdersWindow(v);
523 break;
524
525 case WID_VT_TIMETABLE_PANEL: { // Main panel.
526 int selected = GetOrderFromTimetableWndPt(pt.y, v);
527
528 this->DeleteChildWindows();
529 this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
530 break;
531 }
532
533 case WID_VT_START_DATE: // Change the date that the timetable starts.
534 ShowSetDateWindow(this, v->index | (v->orders.list->IsCompleteTimetable() && _ctrl_pressed ? 1U << 20 : 0), _date, _cur_year, _cur_year + 15, ChangeTimetableStartCallback);
535 break;
536
537 case WID_VT_CHANGE_TIME: { // "Wait For" button.
538 int selected = this->sel_index;
539 VehicleOrderID real = (selected + 1) / 2;
540
541 if (real >= v->GetNumOrders()) real = 0;
542
543 const Order *order = v->GetOrder(real);
544 StringID current = STR_EMPTY;
545
546 if (order != NULL) {
547 uint time = (selected % 2 == 1) ? order->GetTravelTime() : order->GetWaitTime();
548 if (!_settings_client.gui.timetable_in_ticks) time /= DAY_TICKS;
549
550 if (time != 0) {
551 SetDParam(0, time);
552 current = STR_JUST_INT;
553 }
554 }
555
556 this->query_is_speed_query = false;
557 ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED);
558 break;
559 }
560
561 case WID_VT_CHANGE_SPEED: { // Change max speed button.
562 int selected = this->sel_index;
563 VehicleOrderID real = (selected + 1) / 2;
564
565 if (real >= v->GetNumOrders()) real = 0;
566
567 StringID current = STR_EMPTY;
568 const Order *order = v->GetOrder(real);
569 if (order != NULL) {
570 if (order->GetMaxSpeed() != UINT16_MAX) {
571 SetDParam(0, ConvertKmhishSpeedToDisplaySpeed(order->GetMaxSpeed()));
572 current = STR_JUST_INT;
573 }
574 }
575
576 this->query_is_speed_query = true;
577 ShowQueryString(current, STR_TIMETABLE_CHANGE_SPEED, 31, this, CS_NUMERAL, QSF_NONE);
578 break;
579 }
580
581 case WID_VT_CLEAR_TIME: { // Clear waiting time.
582 uint32 p1 = PackTimetableArgs(v, this->sel_index, false);
583 DoCommandP(0, p1, 0, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
584 break;
585 }
586
587 case WID_VT_CLEAR_SPEED: { // Clear max speed button.
588 uint32 p1 = PackTimetableArgs(v, this->sel_index, true);
589 DoCommandP(0, p1, UINT16_MAX, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
590 break;
591 }
592
593 case WID_VT_RESET_LATENESS: // Reset the vehicle's late counter.
594 DoCommandP(0, v->index, 0, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
595 break;
596
597 case WID_VT_AUTOFILL: { // Autofill the timetable.
598 uint32 p2 = 0;
599 if (!HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) SetBit(p2, 0);
600 if (_ctrl_pressed) SetBit(p2, 1);
601 DoCommandP(0, v->index, p2, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
602 break;
603 }
604
605 case WID_VT_EXPECTED:
606 this->show_expected = !this->show_expected;
607 break;
608
609 case WID_VT_SHARED_ORDER_LIST:
610 ShowVehicleListWindow(v);
611 break;
612 }
613
614 this->SetDirty();
615 }
616
617 virtual void OnQueryTextFinished(char *str)
618 {
619 if (str == NULL) return;
620
621 const Vehicle *v = this->vehicle;
622
623 uint32 p1 = PackTimetableArgs(v, this->sel_index, this->query_is_speed_query);
624
625 uint64 val = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
626 if (this->query_is_speed_query) {
627 val = ConvertDisplaySpeedToKmhishSpeed(val);
628 } else {
629 if (!_settings_client.gui.timetable_in_ticks) val *= DAY_TICKS;
630 }
631
632 uint32 p2 = minu(val, UINT16_MAX);
633
634 DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE));
635 }
636
637 virtual void OnResize()
638 {
639 /* Update the scroll bar */
640 this->vscroll->SetCapacityFromWidget(this, WID_VT_TIMETABLE_PANEL, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM);
641 }
642
646 void UpdateSelectionStates()
647 {
648 this->GetWidget<NWidgetStacked>(WID_VT_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : SZSP_NONE);
649 this->GetWidget<NWidgetStacked>(WID_VT_EXPECTED_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : 1);
650 }
651 };
652
653 static const NWidgetPart _nested_timetable_widgets[] = {
654 NWidget(NWID_HORIZONTAL),
655 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
656 NWidget(WWT_CAPTION, COLOUR_GREY, WID_VT_CAPTION), SetDataTip(STR_TIMETABLE_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
657 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_ORDER_VIEW), SetMinimalSize(61, 14), SetDataTip( STR_TIMETABLE_ORDER_VIEW, STR_TIMETABLE_ORDER_VIEW_TOOLTIP),
658 NWidget(WWT_SHADEBOX, COLOUR_GREY),
659 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
660 NWidget(WWT_STICKYBOX, COLOUR_GREY),
661 EndContainer(),
662 NWidget(NWID_HORIZONTAL),
663 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(),
664 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_ARRIVAL_DEPARTURE_SELECTION),
665 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_ARRIVAL_DEPARTURE_PANEL), SetMinimalSize(110, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_TIMETABLE_TOOLTIP), SetScrollbar(WID_VT_SCROLLBAR), EndContainer(),
666 EndContainer(),
667 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_VT_SCROLLBAR),
668 EndContainer(),
669 NWidget(WWT_PANEL, COLOUR_GREY, WID_VT_SUMMARY_PANEL), SetMinimalSize(400, 22), SetResize(1, 0), EndContainer(),
670 NWidget(NWID_HORIZONTAL),
671 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
672 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
673 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),
674 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),
675 EndContainer(),
676 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
677 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),
678 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),
679 EndContainer(),
680 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
681 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_START_DATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_STARTING_DATE, STR_TIMETABLE_STARTING_DATE_TOOLTIP),
682 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),
683 EndContainer(),
684 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
685 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_AUTOFILL), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP),
686 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_VT_EXPECTED_SELECTION),
687 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_EXPECTED), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_BLACK_STRING, STR_TIMETABLE_EXPECTED_TOOLTIP),
688 NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), SetFill(1, 1), EndContainer(),
689 EndContainer(),
690 EndContainer(),
691 EndContainer(),
692 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
693 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),
694 NWidget(WWT_RESIZEBOX, COLOUR_GREY), SetFill(0, 1),
695 EndContainer(),
696 EndContainer(),
697 };
698
699 static WindowDesc _timetable_desc(
700 WDP_AUTO, "view_vehicle_timetable", 400, 130,
701 WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW,
702 WDF_CONSTRUCTION,
703 _nested_timetable_widgets, lengthof(_nested_timetable_widgets)
704 );
705
710 void ShowTimetableWindow(const Vehicle *v)
711 {
712 DeleteWindowById(WC_VEHICLE_DETAILS, v->index, false);
713 DeleteWindowById(WC_VEHICLE_ORDERS, v->index, false);
714 AllocateWindowDescFront<TimetableWindow>(&_timetable_desc, v->index);
715 }