maedhros@6981: /* $Id$ */ maedhros@6981: rubidium@9111: /** @file timetable_gui.cpp GUI for time tabling. */ maedhros@6981: maedhros@6981: #include "stdafx.h" maedhros@6981: #include "openttd.h" maedhros@6981: #include "variables.h" rubidium@8116: #include "command_func.h" maedhros@6981: #include "gui.h" rubidium@8107: #include "window_gui.h" rubidium@8107: #include "textbuf_gui.h" maedhros@6981: #include "cargotype.h" rubidium@8114: #include "strings_func.h" rubidium@8144: #include "vehicle_base.h" rubidium@8214: #include "string_func.h" rubidium@8224: #include "gfx_func.h" rubidium@10208: #include "company_func.h" maedhros@8929: #include "order_func.h" rubidium@8270: #include "settings_type.h" maedhros@6981: rubidium@8264: #include "table/strings.h" rubidium@8264: rubidium@8722: enum TimetableViewWindowWidgets { rubidium@8722: TTV_WIDGET_CLOSEBOX = 0, rubidium@8722: TTV_CAPTION, rubidium@8745: TTV_ORDER_VIEW, rubidium@8722: TTV_STICKY, rubidium@8722: TTV_TIMETABLE_PANEL, rubidium@8722: TTV_SCROLLBAR, rubidium@8722: TTV_SUMMARY_PANEL, rubidium@8722: TTV_CHANGE_TIME, rubidium@8722: TTV_CLEAR_TIME, rubidium@8722: TTV_RESET_LATENESS, rubidium@8722: TTV_AUTOFILL, rubidium@8722: TTV_EMPTY, rubidium@8722: TTV_RESIZE, rubidium@8722: }; rubidium@8722: maedhros@8929: void SetTimetableParams(int param1, int param2, uint32 time) maedhros@6981: { rubidium@9413: if (_settings_client.gui.timetable_in_ticks) { maedhros@6981: SetDParam(param1, STR_TIMETABLE_TICKS); maedhros@6981: SetDParam(param2, time); maedhros@6981: } else { maedhros@6981: SetDParam(param1, STR_TIMETABLE_DAYS); maedhros@6981: SetDParam(param2, time / DAY_TICKS); maedhros@6981: } maedhros@6981: } maedhros@6981: rubidium@9253: struct TimetableWindow : Window { rubidium@9253: int sel_index; smatz@10184: const Vehicle *vehicle; maedhros@6981: rubidium@9253: TimetableWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number) rubidium@9253: { smatz@10184: this->vehicle = GetVehicle(window_number); smatz@10184: this->caption_color = this->vehicle->owner; rubidium@9253: this->vscroll.cap = 8; rubidium@9253: this->resize.step_height = 10; rubidium@9253: this->sel_index = -1; peter1138@9333: peter1138@9333: this->FindWindowPlacementAndResize(desc); maedhros@6981: } maedhros@6981: rubidium@9253: int GetOrderFromTimetableWndPt(int y, const Vehicle *v) rubidium@9253: { rubidium@9253: /* rubidium@9253: * Calculation description: rubidium@9253: * 15 = 14 (this->widget[TTV_ORDER_VIEW].top) + 1 (frame-line) rubidium@9253: * 10 = order text hight rubidium@9253: */ rubidium@9253: int sel = (y - 15) / 10; maedhros@6981: rubidium@9253: if ((uint)sel >= this->vscroll.cap) return INVALID_ORDER; maedhros@6981: rubidium@9253: sel += this->vscroll.pos; maedhros@6981: smatz@10093: return (sel < v->num_orders * 2 && sel >= 0) ? sel : INVALID_ORDER; rubidium@9253: } rubidium@9253: smatz@10184: virtual void OnInvalidateData(int data) rubidium@9253: { smatz@10184: switch (data) { smatz@10184: case 0: smatz@10184: /* Autoreplace replaced the vehicle */ smatz@10184: this->vehicle = GetVehicle(this->window_number); smatz@10184: break; smatz@10184: smatz@10184: case -1: smatz@10184: /* Removed / replaced all orders (after deleting / sharing) */ smatz@10184: if (this->sel_index == -1) break; smatz@10184: smatz@10184: this->DeleteChildWindows(); smatz@10184: this->sel_index = -1; smatz@10184: break; smatz@10184: smatz@10184: default: { smatz@10184: /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then smatz@10184: * the order is being created / removed */ smatz@10184: if (this->sel_index == -1) break; smatz@10184: smatz@10184: VehicleOrderID from = GB(data, 0, 8); smatz@10184: VehicleOrderID to = GB(data, 8, 8); smatz@10184: smatz@10184: if (from == to) break; // no need to change anything smatz@10184: smatz@10184: /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */ smatz@10184: uint old_num_orders = this->vehicle->num_orders - (uint)(from == INVALID_VEH_ORDER_ID) + (uint)(to == INVALID_VEH_ORDER_ID); smatz@10184: smatz@10184: VehicleOrderID selected_order = (this->sel_index + 1) / 2; smatz@10184: if (selected_order == old_num_orders) selected_order = 0; // when last travel time is selected, it belongs to order 0 smatz@10184: smatz@10184: bool travel = HasBit(this->sel_index, 0); smatz@10184: smatz@10184: if (from != selected_order) { smatz@10184: /* Moving from preceeding order? */ smatz@10184: selected_order -= (int)(from <= selected_order); smatz@10184: /* Moving to preceeding order? */ smatz@10184: selected_order += (int)(to <= selected_order); smatz@10184: } else { smatz@10184: /* Now we are modifying the selected order */ smatz@10184: if (to == INVALID_VEH_ORDER_ID) { smatz@10184: /* Deleting selected order */ smatz@10184: this->DeleteChildWindows(); smatz@10184: this->sel_index = -1; smatz@10184: break; smatz@10184: } else { smatz@10184: /* Moving selected order */ smatz@10184: selected_order = to; smatz@10184: } smatz@10184: } smatz@10184: smatz@10184: /* recompute new sel_index */ smatz@10184: this->sel_index = 2 * selected_order - (int)travel; smatz@10184: /* travel time of first order needs special handling */ smatz@10184: if (this->sel_index == -1) this->sel_index = this->vehicle->num_orders * 2 - 1; smatz@10184: } break; smatz@10184: } smatz@10184: } smatz@10184: smatz@10184: smatz@10184: virtual void OnPaint() smatz@10184: { smatz@10184: const Vehicle *v = this->vehicle; rubidium@9253: int selected = this->sel_index; rubidium@9253: rubidium@9253: SetVScrollCount(this, v->num_orders * 2); rubidium@9253: rubidium@10207: if (v->owner == _local_company) { rubidium@9641: bool disable = true; rubidium@9641: if (selected != -1) { rubidium@9665: const Order *order = GetVehicleOrder(v, ((selected + 1) / 2) % v->num_orders); rubidium@9641: if (selected % 2 == 1) { rubidium@9641: disable = order != NULL && order->IsType(OT_CONDITIONAL); rubidium@9641: } else { rubidium@9641: disable = order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL)); rubidium@9641: } rubidium@9641: } maedhros@6981: rubidium@9641: this->SetWidgetDisabledState(TTV_CHANGE_TIME, disable); rubidium@9641: this->SetWidgetDisabledState(TTV_CLEAR_TIME, disable); maedhros@6981: rubidium@9253: this->EnableWidget(TTV_RESET_LATENESS); rubidium@9253: this->EnableWidget(TTV_AUTOFILL); rubidium@9253: } else { rubidium@9253: this->DisableWidget(TTV_CHANGE_TIME); rubidium@9253: this->DisableWidget(TTV_CLEAR_TIME); rubidium@9253: this->DisableWidget(TTV_RESET_LATENESS); rubidium@9253: this->DisableWidget(TTV_AUTOFILL); maedhros@6981: } maedhros@6981: rubidium@9253: this->SetWidgetLoweredState(TTV_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)); maedhros@6981: rubidium@9253: SetDParam(0, v->index); rubidium@9273: this->DrawWidgets(); maedhros@7073: rubidium@9253: int y = 15; rubidium@9253: int i = this->vscroll.pos; rubidium@9253: VehicleOrderID order_id = (i + 1) / 2; rubidium@9253: bool final_order = false; rubidium@9253: rubidium@9253: const Order *order = GetVehicleOrder(v, order_id); rubidium@9253: rubidium@9253: while (order != NULL) { rubidium@9253: /* Don't draw anything if it extends past the end of the window. */ rubidium@9253: if (i - this->vscroll.pos >= this->vscroll.cap) break; rubidium@9253: rubidium@9253: if (i % 2 == 0) { rubidium@9639: DrawOrderString(v, order, order_id, y, i == selected, true, this->widget[TTV_TIMETABLE_PANEL].right - 4); rubidium@9253: rubidium@9253: order_id++; rubidium@9253: rubidium@9253: if (order_id >= v->num_orders) { rubidium@9253: order = GetVehicleOrder(v, 0); rubidium@9253: final_order = true; rubidium@9253: } else { rubidium@9253: order = order->next; rubidium@9253: } rubidium@9253: } else { rubidium@9253: StringID string; rubidium@9253: rubidium@9641: if (order->IsType(OT_CONDITIONAL)) { rubidium@9641: string = STR_TIMETABLE_NO_TRAVEL; rubidium@9641: } else if (order->travel_time == 0) { rubidium@9253: string = STR_TIMETABLE_TRAVEL_NOT_TIMETABLED; rubidium@9253: } else { rubidium@9253: SetTimetableParams(0, 1, order->travel_time); rubidium@9253: string = STR_TIMETABLE_TRAVEL_FOR; rubidium@9253: } rubidium@9253: rubidium@9639: DrawStringTruncated(2, y, string, (i == selected) ? TC_WHITE : TC_BLACK, this->widget[TTV_TIMETABLE_PANEL].right - 4); rubidium@9253: rubidium@9253: if (final_order) break; rubidium@9253: } rubidium@9253: rubidium@9253: i++; rubidium@9253: y += 10; maedhros@7073: } maedhros@7073: rubidium@9253: y = this->widget[TTV_SUMMARY_PANEL].top + 1; rubidium@9253: rubidium@9253: { rubidium@9253: uint total_time = 0; rubidium@9253: bool complete = true; rubidium@9253: rubidium@9253: for (const Order *order = GetVehicleOrder(v, 0); order != NULL; order = order->next) { rubidium@9253: total_time += order->travel_time + order->wait_time; rubidium@9641: if (order->travel_time == 0 && !order->IsType(OT_CONDITIONAL)) complete = false; rubidium@9253: if (order->wait_time == 0 && order->IsType(OT_GOTO_STATION) && !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) complete = false; rubidium@9253: } rubidium@9253: rubidium@9253: if (total_time != 0) { rubidium@9253: SetTimetableParams(0, 1, total_time); rubidium@9253: DrawString(2, y, complete ? STR_TIMETABLE_TOTAL_TIME : STR_TIMETABLE_TOTAL_TIME_INCOMPLETE, TC_BLACK); rubidium@9253: } rubidium@9253: } rubidium@9253: y += 10; rubidium@9253: rubidium@9413: if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) { rubidium@9253: DrawString(2, y, STR_TIMETABLE_STATUS_ON_TIME, TC_BLACK); rubidium@9253: } else { rubidium@9253: SetTimetableParams(0, 1, abs(v->lateness_counter)); rubidium@9253: DrawString(2, y, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE, TC_BLACK); maedhros@7073: } maedhros@7073: } maedhros@6981: rubidium@9253: static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected) rubidium@9253: { rubidium@9253: uint order_number = (selected + 1) / 2; rubidium@9253: uint is_journey = (selected % 2 == 1) ? 1 : 0; maedhros@7066: rubidium@9253: if (order_number >= v->num_orders) order_number = 0; maedhros@6981: rubidium@9253: return v->index | (order_number << 16) | (is_journey << 24); rubidium@9253: } maedhros@6981: rubidium@9253: virtual void OnClick(Point pt, int widget) rubidium@9253: { smatz@10184: const Vehicle *v = this->vehicle; maedhros@6981: rubidium@9253: switch (widget) { rubidium@9253: case TTV_ORDER_VIEW: /* Order view button */ rubidium@9253: ShowOrdersWindow(v); rubidium@9253: break; maedhros@6981: rubidium@9253: case TTV_TIMETABLE_PANEL: { /* Main panel. */ rubidium@9253: int selected = GetOrderFromTimetableWndPt(pt.y, v); rubidium@9253: smatz@10184: this->DeleteChildWindows(); smatz@10184: this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected; rubidium@9253: } break; rubidium@9253: rubidium@9253: case TTV_CHANGE_TIME: { /* "Wait For" button. */ rubidium@9253: int selected = this->sel_index; rubidium@9253: VehicleOrderID real = (selected + 1) / 2; rubidium@9253: rubidium@9253: if (real >= v->num_orders) real = 0; rubidium@9253: rubidium@9253: const Order *order = GetVehicleOrder(v, real); rubidium@9253: StringID current = STR_EMPTY; rubidium@9253: rubidium@9253: if (order != NULL) { rubidium@9253: uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time; rubidium@9413: if (!_settings_client.gui.timetable_in_ticks) time /= DAY_TICKS; rubidium@9253: rubidium@9253: if (time != 0) { rubidium@9253: SetDParam(0, time); rubidium@9253: current = STR_CONFIG_PATCHES_INT32; rubidium@9253: } rubidium@9253: } rubidium@9253: smatz@10145: ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, 150, this, CS_NUMERAL, QSF_NONE); rubidium@9253: } break; rubidium@9253: rubidium@9253: case TTV_CLEAR_TIME: { /* Clear waiting time button. */ rubidium@9253: uint32 p1 = PackTimetableArgs(v, this->sel_index); rubidium@9253: DoCommandP(0, p1, 0, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); rubidium@9253: } break; rubidium@9253: rubidium@9253: case TTV_RESET_LATENESS: /* Reset the vehicle's late counter. */ rubidium@9253: DoCommandP(0, v->index, 0, NULL, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); rubidium@9253: break; rubidium@9253: rubidium@10341: case TTV_AUTOFILL: { /* Autofill the timetable. */ rubidium@10341: uint32 p2 = 0; rubidium@10341: if (!HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) SetBit(p2, 0); rubidium@10341: if (_ctrl_pressed) SetBit(p2, 1); rubidium@10341: DoCommandP(0, v->index, p2, NULL, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); rubidium@10341: } break; rubidium@9253: } rubidium@9253: rubidium@9253: this->SetDirty(); maedhros@6981: } rubidium@9253: rubidium@9253: virtual void OnQueryTextFinished(char *str) rubidium@9253: { rubidium@9253: if (str == NULL) return; rubidium@9253: smatz@10184: const Vehicle *v = this->vehicle; rubidium@9253: rubidium@9253: uint32 p1 = PackTimetableArgs(v, this->sel_index); rubidium@9253: rubidium@9253: uint64 time = StrEmpty(str) ? 0 : strtoul(str, NULL, 10); rubidium@9413: if (!_settings_client.gui.timetable_in_ticks) time *= DAY_TICKS; rubidium@9253: skidd13@9605: uint32 p2 = minu(time, UINT16_MAX); rubidium@9253: rubidium@9253: DoCommandP(0, p1, p2, NULL, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE)); rubidium@9253: } rubidium@9253: rubidium@9253: virtual void OnResize(Point new_size, Point delta) rubidium@9253: { rubidium@9253: /* Update the scroll + matrix */ rubidium@9253: this->vscroll.cap = (this->widget[TTV_TIMETABLE_PANEL].bottom - this->widget[TTV_TIMETABLE_PANEL].top) / 10; rubidium@9253: } rubidium@9253: }; maedhros@6981: maedhros@6981: static const Widget _timetable_widgets[] = { belugas@9778: { WWT_CLOSEBOX, RESIZE_NONE, COLOUR_GREY, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, // TTV_WIDGET_CLOSEBOX belugas@9778: { WWT_CAPTION, RESIZE_RIGHT, COLOUR_GREY, 11, 326, 0, 13, STR_TIMETABLE_TITLE, STR_018C_WINDOW_TITLE_DRAG_THIS}, // TTV_CAPTION belugas@9778: { WWT_PUSHTXTBTN, RESIZE_LR, COLOUR_GREY, 327, 387, 0, 13, STR_ORDER_VIEW, STR_ORDER_VIEW_TOOLTIP}, // TTV_ORDER_VIEW belugas@9778: { WWT_STICKYBOX, RESIZE_LR, COLOUR_GREY, 388, 399, 0, 13, STR_NULL, STR_STICKY_BUTTON}, // TTV_STICKY maedhros@6981: belugas@9778: { WWT_PANEL, RESIZE_RB, COLOUR_GREY, 0, 387, 14, 95, STR_NULL, STR_TIMETABLE_TOOLTIP}, // TTV_TIMETABLE_PANEL belugas@9778: { WWT_SCROLLBAR, RESIZE_LRB, COLOUR_GREY, 388, 399, 14, 95, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, // TTV_SCROLLBAR maedhros@7057: belugas@9778: { WWT_PANEL, RESIZE_RTB, COLOUR_GREY, 0, 399, 96, 117, STR_NULL, STR_NULL}, // TTV_SUMMARY_PANEL rubidium@8722: belugas@9778: { WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_GREY, 0, 109, 118, 129, STR_TIMETABLE_CHANGE_TIME, STR_TIMETABLE_WAIT_TIME_TOOLTIP}, // TTV_CHANGE_TIME belugas@9778: { WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_GREY, 110, 219, 118, 129, STR_CLEAR_TIME, STR_TIMETABLE_CLEAR_TIME_TOOLTIP}, // TTV_CLEAR_TIME belugas@9778: { WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_GREY, 220, 337, 118, 129, STR_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP}, // TTV_RESET_LATENESS belugas@9778: { WWT_PUSHTXTBTN, RESIZE_TB, COLOUR_GREY, 338, 387, 118, 129, STR_TIMETABLE_AUTOFILL, STR_TIMETABLE_AUTOFILL_TOOLTIP}, // TTV_AUTOFILL belugas@9778: belugas@9778: { WWT_PANEL, RESIZE_RTB, COLOUR_GREY, 388, 387, 118, 129, STR_NULL, STR_NULL}, // TTV_EMPTY belugas@9778: { WWT_RESIZEBOX, RESIZE_LRTB, COLOUR_GREY, 388, 399, 118, 129, STR_NULL, STR_RESIZE_BUTTON}, // TTV_RESIZE maedhros@6981: maedhros@6981: { WIDGETS_END } maedhros@6981: }; maedhros@6981: maedhros@6981: static const WindowDesc _timetable_desc = { rubidium@7341: WDP_AUTO, WDP_AUTO, 400, 130, 400, 130, maedhros@6981: WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW, maedhros@6981: WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, maedhros@6981: _timetable_widgets, maedhros@6981: }; maedhros@6981: maedhros@6981: void ShowTimetableWindow(const Vehicle *v) maedhros@6981: { rubidium@9253: AllocateWindowDescFront(&_timetable_desc, v->index); maedhros@6981: }